1. 程式人生 > >FPGA學習筆記(六)—— 時序邏輯電路設計

FPGA學習筆記(六)—— 時序邏輯電路設計

code 是我 使用 param efi sof src img lse

用always@(posedge clk)描述 時序邏輯電路的基礎——計數器在每個時鐘的上升沿遞增1) 技術分享圖片

  例1.四位計數器(同步使能、異步復位)

// Module Name: counter_4bit
// Description: 4bit異步復位同步使能二進制計數器

module counter_4bit(
    input    clk,            //系統時鐘信號
    input    rst,            //系統復位按鍵
    input    en,             //計數器使能端
    output   reg [3:0]q      //
計數器計數值輸出 );   //同步使能,異步復位 always@(posedge clk,posedge rst) if(rst) q <= 0; else if(en) q <= q + 1b1; //計數器加一 endmodule

  testbench測試代碼如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: counter_4bit_tb 
// Description: 4bit計數器模塊測試文件
//////////////////////////////////////////////////////////////////////////////////
`define clk_period 20   //宏定義時鐘周期  
module
counter_4bit_tb(); reg clk; //用於產生時鐘信號 reg rst; //用於產生復位信號 reg en; //用於產生使能信號 wire [3:0]q; //計數器計數值輸出 //例化測試模塊 counter_4bit counter_4bit_test( .clk(clk), //系統時鐘信號 .rst(rst), //系統復位按鍵 .en(en), //計數器使能端 .q(q) //
計數器計數值輸出 ); //開始測試 //生成時鐘信號 initial clk = 1; always #(`clk_period/2) clk = ~clk; //clk每5ns翻轉一次,產生100M時鐘信號 initial begin rst = 1; en = 0; #(`clk_period * 5 + 1 ); rst = 0; #(`clk_period * 5); en = 1; #(`clk_period *20); //因為4bit計數16個clk就清零,所以延時20個時鐘周期 $stop; end endmodule

  測試結果如下:

技術分享圖片

  綜合的電路圖如下:

技術分享圖片

  計數器是我們設計的第一個時序邏輯電路,也是最基本、最重要的時序邏輯電路,由圖中可以看到一個計數器由加法器和D觸發器組成;

  特別要註意的一點,在用verilog描述計數寄存器加一的時候,我們沒有先寫一個加法器,然後例化調用,而是直接采用 q <= q + 1‘b1這樣描述加法器,這點在綜合出的電路圖也可以看出,加法器不再是門電路的組合,而是用一個圓圈替代,這時,數字邏輯設計的思想就又抽象了一層,不再是門電路的組合,而是這些具有邏輯功能的小方塊的組合,所以,抽象的思想在數字邏輯設計中至關重要;

  在編寫testbench仿真測試的時候,我們也不再像測試組合邏輯電路那樣利用窮舉法測試功能,而是利用時鐘測試,比如測試4bit的加法器,一要測試它能不能在每個時鐘沿加一,而要測試它計滿時可不可以自動清零;所以,在測試時序邏輯電路的時候要準確把握clk時鐘信號

   註:計數器只是基本電路,比如分頻器、定時器、巴克碼序列發生器在計數器基本電路上設計即可; 例2.基本分頻操作——分頻器(閃爍燈:LED以1hz的頻率閃爍)   分頻器是計數器的一大應用,在FPGA中沒有像C語言中delay()這樣的延時,那如何延時呢?可以通過分頻器,將系統100M的高速時鐘信號經過分頻器,變為1hz的輸出信號,這樣,LED就會按1s的周期閃爍;   1hz的時鐘信號是每500ms翻轉1次,而在100M的時鐘下,1個clk是10ns,所以應該每計數 500_000_000/10 = 50_000_000 次輸出信號翻轉,就產生了1hz的信號;50_000_000大概估算需要一個26位的寄存器;又因為計數器是從0開始計數的,所以最大計數值應該為49_999_999,設計如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Create Date: 2018/05/22 20:32:26
// Module Name: flashled 
// Description: 產生一個時鐘信號,讓LED燈按1hz的頻率閃爍
//////////////////////////////////////////////////////////////////////////////////
 //`define SIMULATION      /*** 仿真時保留,板級驗證時註釋 ***/
module flashled(
    input clk,          //時鐘信號輸入
    input rst,          //復位信號輸入
    input en,           //使能信號輸入
    output [15:0]led    //led信號輸出
    );
    
     
     `ifdef SIMULATION      //仿真情況下
        parameter   CNT_MAX = 26d49;
     `else                  //板級驗證情況下
        parameter   CNT_MAX = 26d49_999_999;
     `endif   
       
    reg [25:0]cnt;      //最大計數值49_999_999
    reg [15:0]led_clk;  //輸出驅動led的1hz信號
   
    //計數器功能描述
    always@(posedge clk,posedge rst)
        if(rst)
            cnt <= 0;
        else if(en)begin
            if(cnt == CNT_MAX)
                cnt <= 0;
            else
                cnt <= cnt + 1b1;
        end
    
    //產生1hz信號
    always@(posedge clk,posedge rst)
        if(rst)
            led_clk <= 0;
        else if(cnt == CNT_MAX)
            led_clk <= ~led_clk;    //每計滿50_000_000個時鐘周期(500ms),輸出信號翻轉一下
        else
            led_clk <=  led_clk;
    
    //經1hz信號輸出到led
    assign led = led_clk;
 
endmodule 
  testbench測試文件如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: flashled_tb
// Description: flashled模塊測試文件
//////////////////////////////////////////////////////////////////////////////////
`define clk_period 10   //100M時鐘信號
module flashled_tb();
    reg clk;          //用於產生時鐘信號
    reg rst;          //用於產生復位信號
    reg en;           //用於產生使能信號
    wire [15:0]led;   //觀察led輸出
    
    //例化測試模塊
    flashled flashled_test(
        .clk(clk),          //時鐘信號輸入
        .rst(rst),          //復位信號輸入
        .en(en),           //使能信號輸入
        .led(led)           //led信號輸出
        );
    
    //產生時鐘信號
    initial clk = 1;
    always #(`clk_period/2) clk = ~clk;
    
    //開始測試
    initial begin
        rst = 1;                 //復位;
        en  = 0;
    #(`clk_period * 5);
         rst = 0;
    #(`clk_period * 5);   
          en = 1;                //使能
    #(`clk_period * 50 * 5 );   //仿真情況下計數器每計50次翻轉,所以應該延時50 * 5個時鐘周次觀察翻轉情況
          $stop;      
   end
     
endmodule

  測試結果如下: 技術分享圖片

  綜合電路圖如下:

技術分享圖片

  在編寫testbench測試分頻器模塊時,由於計數器計數值為49_999_999 ,所以花費長時間,因為只需要測試功能,看模塊計到設定值後會不會翻轉信號,所以這樣就是在浪費時間;

  為了加快仿真速度,可以在仿真時將最大值改為49,這樣就非常快,為了方便,可以在verilog中使用條件編譯:

  `ifdef SIMULATION        //仿真情況下
        parameter   CNT_MAX = 26d49;
     `else                  //板級驗證情況下
        parameter   CNT_MAX = 26d49_999_999;
     `endif 

例3.基本移位操作——verilog位操作(流水燈:16個LED循環左移)
      //1.取某一位直接操作
      wire [2:0]m;
      assign m = out[5:3];

        //2.循環移位(移位寄存器)
        reg [7:0] shift_a;
        always@(posedge clk)
            shift_a <= {shifta[0],shift[7:1]};
        
        reg [7:0] shift_a;
        wire data;
        always@(posedge clk)
          shift_a <= {data,shift[7:1]};

        reg [7:0] shift_a;
        wire data;
        always@(posedge clk)
          shift_a <= {shift[7:1],data};

       //3、拼接
        wire [3:0]x;
        wire [3:0]y;
        wire [7:0]z;
        wire [31:0]n;

        assign z = {x,y};
        assign n = {x,7{x}};

  verilog設計代碼如下(計數器部分和分頻器相同):

    //移位寄存器功能描述
    always@(posedge clk,posedge rst)
        if(rst)
            led_temp <= 26h0001;
        else if(cnt == CNT_MAX)
              led_temp <= {led_temp[14:0],led_temp[15]};      //循環左移
        else
              led_temp <= led_temp;
     //輸出到led
     assign led = led_temp; 

  testbench仿真測試文件和分頻器相同;

  測試結果如下:

技術分享圖片

技術分享圖片

  綜合出的電路圖如下:

技術分享圖片

FPGA學習筆記(六)—— 時序邏輯電路設計