FPGA學習筆記(六)—— 時序邏輯電路設計
阿新 • • 發佈:2018-05-23
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 + 1‘b1; //計數器加一 endmodule
testbench測試代碼如下:
`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Module Name: counter_4bit_tb // Description: 4bit計數器模塊測試文件 ////////////////////////////////////////////////////////////////////////////////// `define clk_period 20 //宏定義時鐘周期 modulecounter_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; endmoduletestbench測試文件如下:
`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仿真測試文件和分頻器相同;
測試結果如下:
綜合出的電路圖如下:
FPGA學習筆記(六)—— 時序邏輯電路設計