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


用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 + 1'b1;       //計數器加一
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 = 26'd49;
     `else                  //板級驗證情況下
        parameter   CNT_MAX = 26'd49_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 + 1'b1;
        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 = 26'd49;
     `else                  //板級驗證情況下
        parameter   CNT_MAX = 26'd49_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 <= 26'h0001;
        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仿真測試文件和分頻器相同;

   測試結果如下:

  綜合出的電路圖如下:

  例4.BCD計數器

   計數器是二進制的,但我們所熟悉的是十進制,所以4bit BCD計數器相當於十進制的計數器:從0計到9,然后進位,清零,設計圖如下:

      

    BCD計數器常常應用在數碼管顯示中,比如要要是1234,在單片機中通常會用C語言中的除法和取余提取每一位送到數碼管顯示,但是在數字邏輯電路中,除法器很耗費資源且效率不高,所以不采用除法,那如何進行顯示呢?這個時候就可以將1234轉換為對應的BCD碼:0001_0010_0011_0100;這樣只需要每次送入一個4bitBCD碼就可以顯示(如果以1ms的頻率刷新,就是數碼管動態掃描顯示,也是下一個例子);

    verilog代碼:

//////////////////////////////////////////////////////////////////////////////////// Module Name: BCDcounter
// Description: BCD計數器
//////////////////////////////////////////////////////////////////////////////////
module BCDcounter(
    input   clk,    //時鍾信號輸入
    input   rst,    //復位信號輸入
    input   cin,    //進位信號輸入
    output  cout,   //進位信號輸出
    output  [3:0]q  //4bitBCD碼輸出 
    );
   reg  [3:0]cnt;   //計數器最大值為9,所以需要4bit計數器    
   
   //BCD計數器計數功能描述
   always@(posedge clk,posedge rst)
        if(rst)
            cnt <= 0;
        else if(cin)begin
            if(cnt == 4'd9)
                cnt <= 0;
            else
                cnt <= cnt + 1'b1;
        end
    
   assign cout = cin && (cnt == 4'd9);      /** 此處不能使用時序邏輯,需要在計滿9的時候同步輸出cout信號,然后在下次清零是cout恢復為0 **/
    
   assign q = cnt;      //BCD計數值輸出
                 
endmodule

  testbench測試文件如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: BCDcounter_tb
// Description: BCDcounter模塊測試文件
//////////////////////////////////////////////////////////////////////////////////
`define clk_period 10   //100M時鍾信號
module BCDcounter_tb();
   reg   clk;    //時鍾信號輸入
   reg   rst;    //復位信號輸入
   reg   cin;    //進位信號輸入
   wire  cout;   //進位信號輸出
   wire  [3:0]q;  //4bitBCD碼輸出 
    
   //例化測試模塊
   BCDcounter BCDcounter_test(
       .clk(clk),     //時鍾信號輸入
      .rst(rst),      //復位信號輸入
       .cin(cin),     //進位信號輸入
       .cout(cout),   //進位信號輸出
       .q(q)          //4bitBCD碼輸出 
       );
   //產生100M時鍾信號
    initial clk = 1;
    always #(`clk_period/2) clk = ~clk;
    
    //開始測試
    initial begin
        rst = 1;    //復位
        cin = 0;    //無進位
    #(`clk_period * 2);
        rst = 0;    
    #(`clk_period * 2);
        cin = 1;    //產生進位,計數器開始計數
    #(`clk_period * 15);      //1個clk計一次數,總共計10次,所以延時15個clk觀察波形
        $stop;
   end
 
endmodule

  測試結果如下:

  綜合電路圖如下:

  至此一個4bitBCDcounter就設計完成了,一般使用時,都是通過級聯實現多位BCD計數器,比如四個BCD計數器級聯起來最大值為9999,可以很方便的應用在數碼管動態顯示上;

  verilog代碼如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: BCDcounter_top
// Description: 4個4bit計數器進行級聯,實現計數最大值為9999
//////////////////////////////////////////////////////////////////////////////////

module BCDcounter_top(
        input clk,          //時鍾信號輸入
        input rst,          //復位信號輸入
        input cin,          //進位信號輸入
        output cout,        //進位信號輸出
        output [15:0]q      //BCD碼輸出
    );
    
    wire cout0,cout1,cout2;     //4個BCD計數器級聯之間連接線
    wire [3:0]q0,q1,q2,q3;      //每個BCD計數器的輸出
    
    assign q = {q3,q2,q1,q0};   //拼接成16位輸出
    
    //例化4個BCD計數器
    BCDcounter BCDcounter0(
        .clk(clk),        //時鍾信號輸入
        .rst(rst),        //復位信號輸入
        .cin(cin),        //進位信號輸入
        .cout(cout0),     //進位信號輸出
        .q(q0)             //4bitBCD碼輸出 
        );
    BCDcounter BCDcounter1(
        .clk(clk),    //時鍾信號輸入
        .rst(rst),    //復位信號輸入
        .cin(cout0),  //進位信號輸入
        .cout(cout1), //進位信號輸出
        .q(q1)         //4bitBCD碼輸出 
            );
    BCDcounter BCDcounter2(
        .clk(clk), //時鍾信號輸入
        .rst(rst),        //復位信號輸入
              .cin(cout1),        //進位信號輸入
              .cout(cout2),      //進位信號輸出
              .q(q2)             //4bitBCD碼輸出 
                );
    BCDcounter BCDcounter3(
              .clk(clk),        //時鍾信號輸入
              .rst(rst),        //復位信號輸入
              .cin(cout2),        //進位信號輸入
              .cout(cout),      //進位信號輸出
              .q(q3)             //4bitBCD碼輸出 
               );
endmodule

 

  testbench測試文件與之前一樣,但是需要增大延時時長,因為要計數到9999;

  測試結果如下:

  

  綜合電路圖如下:

  

  例5.數碼管動態掃描顯示

   

   設計圖如上圖,在數碼管靜態顯示的基礎上只需要添加一個分頻器,將系統時鍾分頻為1ms的時鍾信號,然后每1ms同時改變段選和位選,刷新顯示,這就是數碼管動態顯示的原理;位選信號已經由2-4譯碼器控制,只有輸入2位選擇信號,所以還需一個四選一數據選擇器,和2-4譯碼器共享一個選擇信號sel,這樣只需要改變sel的值就可以了;

  verilog代碼如下(這是頂層文件,還需要之前):

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: Dynamic_seg_display 
// Description: 4位數碼管動態掃描顯示
//////////////////////////////////////////////////////////////////////////////////
`define SIMULATION      /*** 仿真時保留,板級驗證時注釋 ***/
module Dynamic_seg_display(
        input  clk,                  //時鍾信號
        input  rst,                  //復位信號
        input  [15:0]data_display,   //16位待顯示數據顯示(4個BCD碼)
        output [6:0]segments,        //數碼管段碼
        output [3:0]wei_sel          //數碼管位碼
    );
      `ifdef SIMULATION      //仿真情況下            
       parameter   CNT_MAX = 26'd49;
    `else                  //板級驗證情況下
       parameter   CNT_MAX = 26'd99_999;
    `endif   
      
    reg [22:0]cnt;                //最大計數值4_999_999,需要23位計數器
    reg [1:0]wei;                 //選擇哪一位顯示
    wire [3:0]data_display_temp;  //顯示信號傳輸
  
   //計數器功能描述
   always@(posedge clk,posedge rst)
       if(rst)
           cnt <= 0;
       else if(cnt == CNT_MAX)
           cnt <= 0;
       else
           cnt <= cnt + 1'b1;
  
   //控制數碼管動態掃描顯示(每隔1ms加一,刷新段選和位選)
   always@(posedge clk,posedge rst)
       if(rst)
           wei <= 2'b00;
       else if(cnt == CNT_MAX)
           wei <= wei + 1'b1;    //每計滿100_000個時鍾周期(1ms),位選加一,切換下一位
       else
           wei <=  wei;
    //例化四選一多路器,選擇輸入哪一位顯示段碼信號
             mux4 mux4_1(
                 .sel(wei),
                 .data_in(data_display),
                 .data_out(data_display_temp)        
                 );
   //例化數碼管譯碼模塊
   seg_display seg_display0(
              .data_display(data_display_temp),      //數碼管待顯示數據
              .wei(wei),               //選擇哪一位顯示
               .segments(segments),           //數碼管段碼
               .wei_sel(wei_sel)             //數碼管位碼
               );
            
   
endmodule
    //四選一多路器
module mux4(
    input        [1:0]sel,
    input        [15:0]data_in,
    output reg   [3:0]data_out        
    );
    always@(*)
        case(sel)
            //對應位
            4'h0:       data_out  = data_in[3:0];
            4'h1:       data_out  = data_in[7:4];
            4'h2:       data_out  = data_in[11:8];
            4'h3:       data_out  = data_in[15:12];
            default:    data_out  = 4'bz;
        endcase
endmodule

  testbench測試代碼如下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////// Module Name: Dynamic_seg_display_tb
// Description: 數碼管動態掃描顯示控制模塊測試文件
//////////////////////////////////////////////////////////////////////////////////
`define clk_period 10   //100M時鍾信號

module Dynamic_seg_display_tb();
       reg  clk;                  //時鍾信號
       reg  rst;                  //復位信號
       reg  [15:0]data_display;   //16位待顯示數據顯示(4個BCD碼)
       wire [6:0]segments;        //數碼管段碼
       wire [3:0]wei_sel;         //數碼管位碼
       
       //例化測試模塊
       Dynamic_seg_display Dynamic_seg_display_test(
               .clk(clk),                   //時鍾信號
               .rst(rst),                   //復位信號
              .data_display(data_display),  //16位待顯示數據顯示(4個BCD碼)
               .segments(segments),         //數碼管段碼
              .wei_sel(wei_sel)             //數碼管位碼
           );
      //產生100M時鍾信號
      initial   clk = 1;
      always #(`clk_period/2)   clk = ~clk;
      
      //開始測試
      initial begin
            rst = 1;
            data_display = 16'h4321;    //數碼管上顯示"1234"
      #(`clk_period * 2);
            rst = 0;
      #(`clk_period * 50 * 5);         //計50次數刷新一下,觀察5次
            $stop;
      end
endmodule

   測試結果如下:

  綜合后的電路圖如圖:

 注:

  將上一例中四個級聯BCD計數器的16bit輸出和該例中數碼管動態掃描控制模塊的16bit輸入接起來,控制BCD計數的頻率為1hz,就形成一個簡易計時器,從0000一直計到9999;

  verilog代碼如下(這是頂層文件,底層模塊調用之前的數碼管動態掃描模塊和4個級聯BCD計數器模塊):

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Module Name: easyclock
// Description: 簡易數字鍾:按1hz的頻率從0000-9999顯示
//////////////////////////////////////////////////////////////////////////////////
//`define SIMULATION      /*** 仿真時保留,板級驗證時注釋 ***/
module easyclock(
    input clk,                   //時鍾信號輸入
    input rst,                   //復位信號輸入
    output [6:0]segments,        //數碼管段碼
    output [3:0]wei_sel          //數碼管位碼

    );
   `ifdef SIMULATION      //仿真情況下
        parameter   CNT_MAX = 26'd49;
    `else                  //板級驗證情況下
        parameter   CNT_MAX = 26'd49_999_999;
    `endif   
       
        reg  clk_1hz;               //1hz時鍾信號
        reg  [25:0]cnt;                   //計數器計數寄存起(用於分頻器)
        wire [15:0]data_display;    //待顯示數據
    
    //計數器
    always@(posedge clk,posedge rst)
        if(rst)
             cnt <= 0;
        else  if(cnt == CNT_MAX)
             cnt <= 0;
        else
             cnt <= cnt + 1'b1;
    //產生1hz信號
     always@(posedge clk,posedge rst)
       if(rst)
           clk_1hz <= 0;
       else if(cnt == CNT_MAX)
           clk_1hz <= ~clk_1hz;    //每計滿50_000_000個時鍾周期(500ms),輸出信號翻轉一下
       else
            clk_1hz <=  clk_1hz;
    
    //例化4個BCD計數器
    BCDcounter_top BCDcounter_top0(
           .clk(clk_1hz),             /** 因為BCD需要按1hz計數,所以需要一個1hz時鍾信號 **/
           .rst(rst),           //復位信號輸入
           .cin(1'b1),          //進位信號輸入
           .cout(),             //進位信號輸出
           .q(data_display)     //BCD碼輸出
        );
    //例化數碼管動態掃描顯示控制模塊
    Dynamic_seg_display Dynamic_seg_display0(
            .clk(clk),                      //時鍾信號
            .rst(rst),                     //復位信號
            .data_display(data_display),   //16位待顯示數據顯示(4個BCD碼)
            .segments(segments),           //數碼管段碼
            .wei_sel(wei_sel)              //數碼管位碼
        );
     
endmodule

  綜合電路圖如下:

 

  例6.74HC595驅動

   在上一例中講述了數碼管動態掃描的設計,但是僅僅是4位數碼管顯示,就占用了8+4個IO,如果是8段8位數碼管,就會占用16個IO,造成IO浪費,所以可以采用74HC595串轉並芯片兩片級聯,只采用3個IO完成8段8位數碼管的顯示;

    verilog描述如下:

   

//////////////////////////////////////////////////////////////////////////
//module: 74HC595驅動模塊
//description:    將輸入的16位數據串行輸出到74HC595
//////////////////////////////////////////////////////////////////////////
module HC595drive(
    input  clk,                //50M系統時鍾
    input  rst,                //低電平復位
    input  [7:0]seg,        //8bit段選信號
    input  [7:0]sel,        //8bit位選信號
    output reg DIO,        //74HC595數據引腳
    output reg SHCP,        //74HC595移位脈沖引腳
    output reg STCP         //74HC595輸出脈沖引腳
    );
    
    reg clk_595;
    reg [1:0]divid_cnt;
    reg [4:0]count;
    
    //產生74HC595工作時鍾12.5M
    always@(posedge clk,negedge rst)
        if(!rst)begin
            divid_cnt <= 0;
            clk_595 <= 0;
        end
        else if(divid_cnt == 2'd3)begin
            clk_595 <= 1;
            divid_cnt <= 0;
        end
        else begin
            divid_cnt <= divid_cnt + 1'b1;
            clk_595 <= 0;
        end
    
    //對74HC595工作時鍾clk_595計數
    always@(posedge clk,negedge rst)
        if(!rst)
            count <= 0;
        else if(clk_595 == 1)begin            //一共串行發送16bit數據
            if(count == 5'd31)
                count <= 0;
            else 
                count <= count + 1'b1;
            end
        else
            count <= count;        //count清零
    
    //功能描述
    always@(posedge clk,negedge rst)
        if(!rst)begin
            DIO  <= 0;
            SHCP <= 0;
            STCP <= 0;
        end
        else 
            case(count)
                5'd0:        begin     DIO <= seg[7];    SHCP <= 0;    STCP <= 1;     end    //HEX_DP,輸出
                5'd1:        begin                       SHCP <= 1;    STCP <= 0;     end    //移位
                5'd2:        begin     DIO <= seg[6];    SHCP <= 0;    STCP <= 0;     end    //HEX_G
                5'd3:        begin                       SHCP <= 1;                    end        
                5'd4:        begin     DIO <= seg[5];    SHCP <= 0;                    end    //HEX_F
                5'd5:        begin                       SHCP <= 1;                    end    
                5'd6:        begin     DIO <= seg[4];    SHCP <= 0;                    end    //HEX_E
                5'd7:        begin                       SHCP <= 1;                    end    
                5'd8:        begin     DIO <= seg[3];    SHCP <= 0;                    end    //HEX_D
                5'd9:        begin                       SHCP <= 1;                    end    
                5'd10:      begin     DIO <= seg[2];    SHCP <= 0;                    end    //HEX_C
                5'd11:      begin                       SHCP <= 1;                    end    
                5'd12:      begin     DIO <= seg[1];    SHCP <= 0;                    end    //HEX_B
                5'd13:      begin                       SHCP <= 1;                    end    
                5'd14:      begin     DIO <= seg[0];    SHCP <= 0;                    end    //HEX_A
                5'd15:      begin                       SHCP <= 1;                    end                    
                5'd16:      begin     DIO <= sel[7];    SHCP <= 0;                    end    //第7位
                5'd17:      begin                       SHCP <= 1;                    end    
                5'd18:      begin     DIO <= sel[6];    SHCP <= 0;                    end    //第6位
                5'd19:      begin                       SHCP <= 1;                    end    
                5'd20:      begin     DIO <= sel[5];    SHCP <= 0;                    end    //第5位
                5'd21:      begin                       SHCP <= 1;                    end                    
                5'd22:      begin     DIO <= sel[4];    SHCP <= 0;                    end    //第4位
                5'd23:      begin                       SHCP <= 1;                    end    
                5'd24:      begin     DIO <= sel[3];    SHCP <= 0;                    end    //第3位
                5'd25:      begin                       SHCP <= 1;                    end    
                5'd26:      begin     DIO <= sel[2];    SHCP <= 0;                    end    //第2位
                5'd27:      begin                       SHCP <= 1;                    end    
                5'd28:      begin     DIO <= sel[1];    SHCP <= 0;                    end    //第1位
                5'd29:      begin                       SHCP <= 1;                    end    
                5'd30:      begin     DIO <= sel[0];    SHCP <= 0;                    end    //第0位
                5'd31:      begin                       SHCP <= 1;                    end        
            endcase 
endmodule 

    testbench如下:

 

`timescale 1ns/1ps
//////////////////////////////////////////////////////////////////////////
//module: 74HC595驅動測試模塊
//////////////////////////////////////////////////////////////////////////
`define clk_period 20        //50M系統時鍾

module HC595drive_tb();
    
    reg  clk;                //50M系統時鍾
    reg  rst;                //低電平復位
    reg  [7:0]seg;            //8bit段選信號
    reg  [7:0]sel;            //8bit位選信號
    wire DIO;                //74HC595數據引腳
    wire SHCP;                //74HC595移位脈沖引腳
    wire STCP;                 //74HC595輸出脈沖引腳
    
    //例化測試模塊
    HC595drive HC595drive_test(
        .clk(clk),                
        .rst(rst),                
        .seg(seg),        
        .sel(sel),        
        .DIO(DIO),        
        .SHCP(SHCP),        
        .STCP(STCP)         
    );
    
    //產生50M時鍾信號
    initial clk = 1;
    always #(`clk_period / 2)    clk = ~clk;
    
    //開始測試
    initial begin
        rst = 0;            //復位
        seg = 8'hc0;      //顯示數字"1" 
        sel = 8'ha5;      //10100101
    #(`clk_period * 5);    
        rst = 1;
    #(`clk_period * 32 * 4);        //傳送16位需要32個clk,clk又被進行4分頻
    #(`clk_period * 10);
        $stop;
    end
endmodule

  測試結果如下:

 例7.定時器

    玩過單片機的小伙伴都很熟悉,單片機的基本外設就是定時器,定時器的核心也是計數器,設定工作方式(單次計數和循環計數)和定時值后,就開始計數,計滿之后產生計數慢標志信號,提示設定的定時時間到達;

    所以,如果要用計數器來設計一個定時器,需要實現的功能有:

    1、定時時間參數通過一個端口輸入,調節該值可以修改定時時間;

    2、設置一個計數模式控制信號,當該信號為1時為循環定時模式,當該信號為0時為單次定時模式;

    3、設置一個計數啟動信號,在循環定時模式下,該信號為高電平使能計時,為低電平則停止計時;當單次計數模式下,該信號的一個單基准時鍾周期的脈沖使能一次定時;

    4、輸出計數器定時計數值,該值用於產生特定占空比的方波;

    設計圖如下:
    

  verilog代碼如下:

module    timer(
        input  clk,                       //50M時鍾信號
        input  rst,                       //低電平復位
        input  [31:0]timer_value,         //定時值
        input  mode,                      //定時器模式選擇
        input  en,                        //定時器使能控制端
        output [31:0]cnt_value,           //定時器計數值實時輸出
        output full_flag                  //定時器計滿輸出標志位
);
    
        reg [31:0]cnt;            //32bit計數器
        reg oneshot;              //在單次定時模式中,使能端一個脈沖就啟動,但是計數器需要en保持為1才會計數,所以需要另外一個單次使能信號,當外部一個脈沖時,它會變為1,保持至定時結束;
        
        //功能描述
        assign full_flag = (cnt == timer_value)?1'b1:1'b0;        //輸出計滿標志位
        assign cnt_value = cnt;                                   //實時輸出計數值
        
        always@(posedge clk,negedge rst)
            if(!rst)
                cnt <= 0;
            else if(mode)begin             //mode = 1,循環定時模式
                if(en && cnt < timer_value)    
                    cnt <= cnt +1'b1;      //使能狀態下且計數值未滿,加一
                else
                    cnt <= 0;              //其余情況下cnt清零
            end
            else begin                     //mode = 0,單次定時模式
                if(oneshot)
                    cnt <= cnt + 1'b1;
                else
                    cnt <= 0;
            end

        //利用外部一個脈沖產生oneshot信號供定時器單次定時模式下使用
        //功能:當外部en產生一個高脈沖時,它會變為1,保持至定時結束;
        always@(posedge clk,negedge rst)
            if(!rst)
                oneshot <= 0;
            else if(en)
                oneshot <= 1'b1;
            else if(cnt >= timer_value)    //計滿清零
                oneshot <= 0;
endmodule 

  testbench測試文件如下:

`timescale 1ns/1ps
`define clk_period 20

module timer_tb();

        reg  clk;                       //50M時鍾信號
        reg  rst;                       //低電平復位
        reg  [31:0]timer_value;         //定時值
        reg  mode;                      //定時器模式選擇
        reg  en;                        //定時器使能控制端
        wire [31:0]cnt_value;           //定時器計數值實時輸出
        wire full_flag;                 //定時器計滿輸出標志位
        
        //例化測試模塊
        timer timer_test(
         .clk(clk),                       //50M時鍾信號
         .rst(rst),                       //低電平復位
         .timer_value(timer_value),       //定時值
         .mode(mode),                     //定時器模式選擇
         .en(en),                         //定時器使能控制端
         .cnt_value(cnt_value),           //定時器計數值實時輸出
         .full_flag(full_flag)            //定時器計滿輸出標志位
);

        //產生50M時鍾信號
        initial clk = 1;
        always #(`clk_period / 2) clk = ~clk;
        
        //開始測試
        initial begin
            rst  = 0;                //系統復位
            mode = 0;                //首先測試模式0,單次計數模式
            en   = 0;                //未使能
            timer_value = 32'd10;    //定時器計數值為10
        #(`clk_period * 2);
            rst = 1;
        #(`clk_period * 2);
            en  = 1;                 //產生一個高脈沖,使能定時器
        #(`clk_period * 2);
            en  = 0;
        #(`clk_period * 15);         //系統計數10個clk會輸出計滿flag,在延時觀察5個clk觀察定時器是否停止
            mode = 1;                //開始測試定時器模式1,循環計數模式
        #(`clk_period);
            en = 1;                  //使能定時器
        #(`clk_period * 10 * 5);     //系統計數10個clk會輸出計滿flag,一共觀察5次flag
            en = 0;                  //停止使能
        #(`clk_period * 10);         //等待10個clk,觀察定時器是否已停止工作
            $stop;                   //停止測試
        end
endmodule 

   測試結果如下:

  單次計數模式測試:

  循環計數模式測試:

  綜合電路圖如下:

  定時器至此就設計結束,定時器有很多應用,比如產生PWM驅動無源蜂鳴器,驅動直流電機調速,驅動舵機,驅動步進電機,驅動RGBLED變色,總之利用定時器可以很方便產生可變PWM,利用好PWM也是很深的學問,如有興趣自行深入;

 

  

 

  

 

 

 

 

 

    

    

 


免責聲明!

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



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