【接口時序】2、Verilog實現流水燈及與C語言的對比


一、 軟件平台與硬件平台

  軟件平台:

  1、操作系統:Windows-8.1

  2、開發套件:ISE14.7

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

  硬件平台:

  1、FPGA型號:XC6SLX45-2CSG324

二、 原理介紹

  我的開發板上有4個LED燈,原理圖如下:

  

  

  由原理圖可知僅當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 ;

 

  仿真的時序圖如下圖所示:

 

可以看到時序完全正確,接下來就是綁定管腳,生成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 ;

  時序圖如下圖:

  時序圖仍然正確,實現了流水燈的效果

七、 總結

  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

 

歡迎關注我的公眾號:FPGA之禪


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM