【课程记录】用Ego1开发板做流水灯实验并理解程序的模块调用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hiha_hero2333/article/details/79768195

  第四节课的实验内容其实和之前的实验内容相似,都是流水灯实验,但是这次换了一块开发版——Xilinx EGO1 ,芯片型号是 xc7a35tcsg324-1
  这块实验板是2016教育部Xilinx产学合作协同育人项目的数字电路课程改革成果,硬件平台由依元素(E-ELEMENT)科技订制完成,是一款便携式的FPGA实验平台。同时也是Xilinx 大学计划在国内第一个支持的本土型大规模课程改革项目。


第一步新建工程,板子的型号可以直接在板子上看到注明:
这里写图片描述
  
  这里写图片描述

  本节实验课编程突出了一个重心,就是“自顶向下”的编程结构,这在中、大型的工程项目中非常重要。在以前的工程中我们常常写完仅仅一个.v源文件就直接综合、运行等就结束了,在大型的工程中,我们往往无法在一个源文件中写完所有的工程所需的模块功能,最常见的做法是分别对同一类的模块进行单独的定义(理解为“底层模块”),最后在重新定义一个“顶层文件”,实现对底层模块的调用。这样做的好处是大大增强了工程的可移植性,稳定性,降低了代码调试修改与维护的难度。

  这节课我们的实验内容是流水灯,编程逻辑如下:

1. 时钟部分使用计数器 counter

  我们这块板子的系统时钟是100兆HZ,也就是100 * 10^6, 也就是 1亿Hz。显然这样的时钟太快了,如果用这样的时钟频率做灯的亮灭切换,则只能看到所有的灯都是亮的(因为人眼具有视觉暂留的特性)。
  我们要实现流水灯,让眼睛能够看到灯相继亮起,必须要慢一点的时钟信号,虽然fpga内部有实现分频的模块,但是可能任然无法得到我们想要的频率。这里很容易想到的,就是直接定义一个计数器counter,每当系统时钟产生一个上升沿,counter 就+1,假如说我们希望流水灯按照每半秒钟切换一个灯,那么就是 counter 等于 1亿/ 2 = 5 千万 的时候,产生一个信号(定义为clk_bps),让流水灯切换。
  

module counter(
input clk,
input rst,
output clk_bps
    );
    reg [27:0]cnt;  // 定义一个28位的counter,2^28 ≈ 2亿7千万
    always @(posedge clk or posedge rst)  // 当出现 clk 上升沿或者 reset 上升沿的时候,进入这个过程块
    if (rst) cnt<=28'd0;   // reset=1 时,给cnt赋0
    else
        if(cnt>=28'd50000000)  cnt<=28'd0;  // 当counter计数到5千万,也就是过了半秒(系统时钟是1亿Hz)之后,cnt变为0
        else                    cnt<=cnt+1'b1;   // 其他情况下,cnt+1,也就是cnt跟着时钟走,不断计数

    assign clk_bps = (cnt== 28'd50000000 ? 1'b1:1'b0);  // 定义clk_bps时钟在cnt=5千万的那一瞬间变为1,其余是0,也就是产生一个一秒两赫兹的时钟信号
endmodule

这里写图片描述
  有了clk_bps ,我们就可以用这个信号来控制流水灯切换了,只要 clk_bps 产生一个上升沿,就让流水灯切换一下。

2.流水灯控制   led_flash_ctrl

  有了我们定义好的clk_bps, 流水灯的程序就很好写了


module flash_led_ctrl(
input clk,
input rst,
input clk_bps,
input dir,   // 用来定义方向,这里用拨码开关的状态来表示dir
output reg[15:0]led

    );

    always @(posedge clk or posedge rst)  // 当系统时钟clk 或者 复位键 rst 信号出现上升沿,运行这个过程块
    if(rst) led<=16'h8000;   //复位时,赋值16位的16进制数8000,也就是 1000 0000 0000 0000 也就是第一个灯亮了
    else 
        case(dir)  // 用拨码开关的状态来判断
        1'b1:      // 当拨码开关为1 
            if(clk_bps)    // 在counter模块里面定义好的clk_bps, 每秒产生两个脉冲,脉冲一来就进入这个if分支
                if(led!=16'h0001)   led <= led>>1'b1;  // 当16位led状态没到最后一个灯亮之前,led右移1位,如 0100 变成 0010
                else                led <= 16'h8000;    //当移位到最后一个灯,那就让第一个灯亮
         1'b0:   // 当拨码开关为0
             if(clk_bps)
                 if(led!=16'h8000)   led <= led<<1'b1;   // 当16位led状态没到第一个灯亮之前,led左移1位,如 0100 变成 1000
                 else                led <= 16'h0001;   ////当移位到第一个灯,那就让最后一个灯亮
        endcase



endmodule

这里写图片描述

3. 写一个顶部文件调用之前定义好的模块

  各个模块是定义好了,因为模块都定义在不同的.V源文件之中,我们就需要用一个文件调用这些模块
这里写图片描述
代码如下:


module led_flash_top_file(

input clk,
input rst_n,
input sw0,   //引入拨码开关sw0,用来作为改变流水灯方向的信号源
output [15:0]led

    );

    wire clk_bps;
    wire rst;
    assign  rst = ~rst_n;  //// 因为rst是按键开关,我们按下之后是个低电平,我们counter和led_flash_ctrl 都是选用rst信号的上升沿触发,所以这里我们把rst取反


    counter u1(
     .clk(clk),
     .rst(rst),   注意,括号()里的端口rst是top_file 文件的定义的端口rst,.rst()是counter文件里面的端口名rst
     .clk_bps(clk_bps)
     );

    flash_led_ctrl u2(
     .clk(clk),
     .rst(rst),
     .clk_bps(clk_bps),    
     .led(led),
     .dir(sw0)
     );
endmodule

这里写图片描述

程序都编写完了,引入约束文件
这里写图片描述
  
  注意约束文件里面的端口名称和我们的TOP文件所定义的端口名称保持一致。
  

//我的约束文件使能的语句
set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [get_ports clk ]
set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports rst_n  ]

set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports {sw0}]

set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS33} [get_ports {led[0]}]
set_property -dict {PACKAGE_PIN G4 IOSTANDARD LVCMOS33} [get_ports {led[1]}]
set_property -dict {PACKAGE_PIN G3 IOSTANDARD LVCMOS33} [get_ports {led[2]}]
set_property -dict {PACKAGE_PIN J4 IOSTANDARD LVCMOS33} [get_ports {led[3]}]
set_property -dict {PACKAGE_PIN H4 IOSTANDARD LVCMOS33} [get_ports {led[4]}]
set_property -dict {PACKAGE_PIN J3 IOSTANDARD LVCMOS33} [get_ports {led[5]}]
set_property -dict {PACKAGE_PIN J2 IOSTANDARD LVCMOS33} [get_ports {led[6]}]
set_property -dict {PACKAGE_PIN K2 IOSTANDARD LVCMOS33} [get_ports {led[7]}]

set_property -dict {PACKAGE_PIN K1 IOSTANDARD LVCMOS33} [get_ports {led[8]}]
set_property -dict {PACKAGE_PIN H6 IOSTANDARD LVCMOS33} [get_ports {led[9]}]
set_property -dict {PACKAGE_PIN H5 IOSTANDARD LVCMOS33} [get_ports {led[10]}]
set_property -dict {PACKAGE_PIN J5 IOSTANDARD LVCMOS33} [get_ports {led[11]}]
set_property -dict {PACKAGE_PIN K6 IOSTANDARD LVCMOS33} [get_ports {led[12]}]
set_property -dict {PACKAGE_PIN L1 IOSTANDARD LVCMOS33} [get_ports {led[13]}]
set_property -dict {PACKAGE_PIN M1 IOSTANDARD LVCMOS33} [get_ports {led[14]}]
set_property -dict {PACKAGE_PIN K3 IOSTANDARD LVCMOS33} [get_ports {led[15]}]

接下来就是我们熟悉的综合,运行,生成位码流,打开硬件管理等等操作了,不再累述。
如果同学们中途有问题,可以根据底下标签页“Message”的提示来解决,error 和 严重的warning 都是要解决的,而有的warning有可能是因为我们编程不够规范导致的,并不影响我们程序的运行。
  最后的实验现象如下:
这里写图片描述
  流水灯哦,拨动sw0会改变流水方向。
  


中途出现的问题:如果出现只有板子最右边的灯(K3)亮,而且还是常亮状态,在尝试按下reset按键的时候会有流水灯现象,那么就说明你的按键触发逻辑反了——按下reset按钮是低电平,我们是让高电平时候(reset不按下的时候)产生流水灯,这就要注意编写 top_file 的时候的逻辑。
  top文件中,我们是这样定义的:

input rst_n,
wire rst;
    assign  rst = ~rst_n; 
 counter u1(
     ***
     .rst(rst),  // 在counter里面是 always @(posedge clk or posedge rst),上升沿触发
     ***
     );

    flash_led_ctrl u2(
    ***
     .rst(rst),    //在flash_led_ctrl文件里面是always @(posedge clk or posedge rst)   上升沿触发
    ***
     );

以及约束文件里面的

set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports rst_n  ]

重点分析这些逻辑,注意取反操作!!


 补充:视觉暂留:物体在快速运动时, 当人眼所看到的影像消失后,人眼仍能继续保留其影像0.1-0.4秒左右的图像,这种现象被称为视觉暂留现象。这是人眼具有的一种性质。人眼观看物体时,成像于视网膜上,并由视神经输入人脑,感觉到物体的像。但当物体移去时,视神经对物体的印象不会立即消失,而要延续0.1 -0.4秒的时间。作为计算机中的动画或摄像,我们听的最多的是1/24 s。
  

猜你喜欢

转载自blog.csdn.net/hiha_hero2333/article/details/79768195
今日推荐