Verilog实现流水灯及与C语言的对比

描述

一、 软件平台与硬件平台

  软件平台:

  1、操作系统:Windows-8.1

  2、开发套件:ISE14.7

  3、仿真工具:ModelSim-10.4-SE

  硬件平台:

  1、FPGA型号:XC6SLX45-2CSG324

二、 原理介绍

  我的开发板上有4个LED灯,原理图如下:

Verilog

  由原理图可知仅当FPGA的对应管脚输入低电平时LED才会亮,流水灯的效果可以轮流让四个对应管脚输出低电平来产生。

三、 目标任务

  编写四个LED流水的Verilog代码并用ModelSim进行仿真,仿真通过以后下载到开发板进行测试,要求开发板上每个LED亮的时间为1s。

四、 设计思路与Verilog代码编写

  由于每个LED亮的时间为1s,所以首先很自然想到产生一个1s的时钟用来驱动后续逻辑,有了这个1s的时钟以后,就可以在这个1s时钟的节拍下对LED的输出进行以移位操作来产生流水灯的效果。

  1、1s时钟的分频逻辑

   由于主时钟是50MHz,周期为20ns,所以可以利用50MHz主时钟驱动一个计数器,当计数器的值每次到达24999999时,消耗的时间为25000000*20ns=0.5s,这时把分频器的输出反转,并把计数值清0,这样分频器的输出就会每隔0.5s翻转一次,产生了一个1s的时钟。

  Verilog代码如下:

////////////////////////////////////////////////////////////////// // 功能:产生1s的时钟 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_cnt_ls <= 32'd0 ; R_clk_ls_reg <= 1'b1 ; end else if(R_cnt_ls == 32'd24_999_999) begin R_cnt_ls <= 32'd0 ; R_clk_ls_reg <= ~R_clk_ls_reg ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end assign W_clk_ls = R_clk_ls_reg ;

  2、移位逻辑

  有了1s的时钟信号以后,就在这个1s时钟信号的驱动下对输出的LED寄存器进行移位操作产生流水效果。

  Verilog代码如下:

////////////////////////////////////////////////////////////////// // 功能:对输出寄存器进行移位产生流水效果 ////////////////////////////////////////////////////////////////// always @(posedge W_clk_ls or negedge I_rst_n) begin if(!I_rst_n) R_led_out_reg <= 4'b0001 ; else if(R_led_out_reg == 4'b1000) R_led_out_reg <= 4'b0001 ; else R_led_out_reg <= R_led_out_reg << 1 ; end assign O_led_out = ~R_led_out_reg ;

五、 ModelSim仿真

  写好逻辑以后,为了确定时序是正确的,最好写一个测试文件对功能进行仿真,为了加快仿真速度,修改分频逻辑计数器的计数值为24,然后编写测试文件,测试文件中激励产生的Verilog代码如下:

initial begin // Initialize Inputs I_clk = 0; I_rst_n = 0; // Wait 100 ns for global reset to finish #100; I_rst_n = 1; // Add stimulus here end always #10 I_clk = ~I_clk ;

  仿真的时序图如下图所示:

Verilog

可以看到时序完全正确,接下来就是绑定管脚,生成bit文件下载到开发板测试了。

六、 进一步思考——C语言流水灯与Verilog流水灯区别

  看完网上《Verilog那些事》系列博文以后,作者提出了一种“仿顺序操作”方法,其实以前自己写代码的时候无形之中一直在用这种思想,但是一直没有提炼出来,看完作者的介绍以后才发现确实是有那个“仿顺序”的味道。详细的博文请参考博客园博主akuei2的系列博文。这里我在总结一遍,给以后留个印象。

  C语言实现流水灯的大致代码框架如下:

    while(1)

    {

      1、让第1个LED亮,其他的灭;

      2、延时1s

      3、让第2个LED亮,其他的灭

      4、延时1s

      5、让第3个LED亮,其他的灭;

      6、延时1s

      7、让第4个LED亮,其他的灭

      8、延时1s

  }

  在while(1)里面代码是一行一行的执行,最后一行执行完毕以后在回到第一行重新开始新一轮的执行。就这样产生了流水的效果。

  看到这里,有人应该突然明白了吧,这不正好就是Verilog中的一个状态机么。对应的Verilog代码也可以写出来了 

  always @(posedge I_clk)

  begin

  case(R_state)

  第1个状态:让第1个LED亮,其他的灭,下一状态是第2个状态;

  第2个状态:延时1s,下一状态是第3个状态;

  第3个状态:让第2个LED亮,其他的灭,下一状态是第4个状态;

  第4个状态:延时1s,下一状态是第5个状态;

  第5个状态:让第3个LED亮,其他的灭,下一状态是第6个状态;

  第6个状态:延时1s,下一状态是第7个状态;

  第7个状态:让第4个LED亮,其他的灭,下一状态是第8个状态;

  第8个状态:延时1s,下一状态是第1个状态;

  default :;

  endcase

  end

  具体的代码如下:

////////////////////////////////////////////////////////////////// // 功能:“仿顺序操作” ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 3'b000 ; R_cnt_ls <= 32'd0 ; end else begin case(R_state) C_S0: begin R_led_out_reg <= 4'b0001 ; R_state <= C_S1 ; end C_S1: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S2 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S2: begin R_led_out_reg <= 4'b0010 ; R_state <= C_S3 ; end C_S3: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S4 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S4: begin R_led_out_reg <= 4'b0100 ; R_state <= C_S5 ; end C_S5: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S6 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S6: begin R_led_out_reg <= 4'b1000 ; R_state <= C_S7 ; end C_S7: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S0 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end default: R_state <= 3'b000 ; endcase end end assign O_led_out = ~R_led_out_reg ;

  时序图如下图:

Verilog

  时序图仍然正确,实现了流水灯的效果

七、 总结

  1、所谓的“仿顺序操作”实际上就是一个状态机,通过状态的跳变实现“顺序执行”的效果。这种思想在后面写接口时序的时候还是挺管用的,今后可以多多琢磨琢磨。

  2、 C语言的while(1)和Verilog语言的always @(posedge I_clk)有类似的地方,只要CPU的时钟存在,它们就一直执行下去。书上都说C语言是一种串行语言,Verilog是一种并行语言,实际上这里也能有体会:C语言里只能有1个while(1)语句,进入while(1)以后CPU就出不来了,而Verilog中可以有多个always @(posedge I_clk)语句,并且每个always @(posedge I_clk)同时运行的,这就是两种语言最大的区别吧。

八、 附录

  1、分频1s产生流水灯的完整代码

module led_work_top ( input I_clk , input I_rst_n , output [3:0] O_led_out ); reg [31:0] R_cnt_ls ; wire W_clk_ls ; reg R_clk_ls_reg ; reg [3:0] R_led_out_reg ; ////////////////////////////////////////////////////////////////// // 功能:产生1s的时钟 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_cnt_ls <= 32'd0 ; R_clk_ls_reg <= 1'b1 ; end else if(R_cnt_ls == 32'd24_999_999) begin R_cnt_ls <= 32'd0 ; R_clk_ls_reg <= ~R_clk_ls_reg ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end assign W_clk_ls = R_clk_ls_reg ; ////////////////////////////////////////////////////////////////// // 功能:对输出寄存器进行移位产生流水效果 ////////////////////////////////////////////////////////////////// always @(posedge W_clk_ls or negedge I_rst_n) begin if(!I_rst_n) R_led_out_reg <= 4'b0001 ; else if(R_led_out_reg == 4'b1000) R_led_out_reg <= 4'b0001 ; else R_led_out_reg <= R_led_out_reg << 1 ; end assign O_led_out = ~R_led_out_reg ; endmodule

  2、 “仿顺序操作”产生流水灯完整代码

module led_work_top ( input I_clk , input I_rst_n , output [3:0] O_led_out ); reg [31:0] R_cnt_ls ; reg [3:0] R_led_out_reg ; reg [2:0] R_state ; parameter C_CNT_1S = 32'd49_999_999 ; parameter C_S0 = 3'b000 , C_S1 = 3'b001 , C_S2 = 3'b010 , C_S3 = 3'b011 , C_S4 = 3'b100 , C_S5 = 3'b101 , C_S6 = 3'b110 , C_S7 = 3'b111 ; ////////////////////////////////////////////////////////////////// // 功能:仿顺序操作 ////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 3'b000 ; R_cnt_ls <= 32'd0 ; end else begin case(R_state) C_S0: begin R_led_out_reg <= 4'b0001 ; R_state <= C_S1 ; end C_S1: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S2 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S2: begin R_led_out_reg <= 4'b0010 ; R_state <= C_S3 ; end C_S3: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S4 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S4: begin R_led_out_reg <= 4'b0100 ; R_state <= C_S5 ; end C_S5: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S6 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end C_S6: begin R_led_out_reg <= 4'b1000 ; R_state <= C_S7 ; end C_S7: begin if(R_cnt_ls == C_CNT_1S) begin R_cnt_ls <= 32'd0 ; R_state <= C_S0 ; end else R_cnt_ls <= R_cnt_ls + 1'b1 ; end default: R_state <= 3'b000 ; endcase end end assign O_led_out = ~R_led_out_reg ; endmodule

  3、测试记录文件完整代码

module tb_led_work_top; // Inputs reg I_clk; reg I_rst_n; // Outputs wire [3:0] O_led_out; // Instantiate the Unit Under Test (UUT) led_work_top U_led_work_top ( .I_clk(I_clk), .I_rst_n(I_rst_n), .O_led_out(O_led_out) ); initial begin // Initialize Inputs I_clk = 0; I_rst_n = 0; // Wait 100 ns for global reset to finish #100; I_rst_n = 1; // Add stimulus here end always #5 I_clk = ~I_clk ; endmodule

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表德赢Vwin官网 网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分