Fpga 學(xué)習(xí)筆記7(狀態(tài)機(jī)設(shè)計(jì)實(shí)例):因內(nèi)容比較簡(jiǎn)單,而且在這篇日志中也有相關(guān)的知識(shí)點(diǎn),就不寫(xiě)了。
該集主要知識(shí)點(diǎn):
1、利用狀態(tài)機(jī)實(shí)現(xiàn)濾除物理按鍵所產(chǎn)生的抖動(dòng)波形。
2、非阻塞賦值的巧妙運(yùn)用
3、將狀態(tài)機(jī)與計(jì)數(shù)器功能組合使用
4、在仿真代碼中利用隨機(jī)數(shù)產(chǎn)生延時(shí)隨機(jī)的時(shí)間。
5、task 運(yùn)用方式、以及將仿真測(cè)試代碼模塊化
按鍵抖動(dòng)的現(xiàn)象與狀態(tài)機(jī)對(duì)應(yīng)的狀態(tài):
一、源程序
/* 實(shí)驗(yàn)名稱(chēng):按鍵消抖模塊設(shè)計(jì)與驗(yàn)證
* 功能實(shí)現(xiàn):濾除按鍵抖動(dòng)的波形
*/
`define DEC_TIME_CNT ((20 * 1000 * 1000) / 20 - 1)
module mytest(clk, rst_n, key_in, key_flag, key_state);
input clk, rst_n, key_in;
output reg key_flag, key_state;
reg[3:0] state; // 狀態(tài)機(jī)狀態(tài)
reg key_old, key_cur; // key狀態(tài)
wire pedge, nedge; // 邊沿狀態(tài)
// 50MHz的時(shí)鐘 = 1/50M = 0.02us = 20ns
// 20ms = 20_000_000ns / 20ns = 1000_000
reg[19:0] time_cnt; // 計(jì)數(shù)器計(jì)數(shù)值
reg time_full; // 計(jì)數(shù)器已經(jīng)達(dá)到指定的時(shí)間
reg time_en; // 計(jì)數(shù)器使能
localparam // 狀態(tài)機(jī)幾種狀態(tài)的標(biāo)志
IDLE = 4'b0001, // 空閑,即高電平狀態(tài),為按下?tīng)顟B(tài)
DING = 4'b0010, // 按下時(shí)抖動(dòng)狀態(tài)
DOWN = 4'b0100, // 可以確定是處于按下?tīng)顟B(tài)而不是抖動(dòng)狀態(tài)
UING = 4'b1000; // 彈起時(shí)抖動(dòng)狀態(tài)
// 邊沿檢測(cè)
always@(posedge clk, negedge rst_n)
if(!rst_n)begin
key_cur <= 1'b0;
key_old <= 1'b0;
end
else
begin
key_cur <= key_in; // 這里由于采用了非阻塞賦值
key_old <= key_cur; // 所以在同一個(gè)時(shí)鐘內(nèi) key_old 采樣 key_curr 的是舊的值
end
// 判斷上升沿、下降沿
assign pedge = !key_old & key_cur; // 原來(lái)為低電平,現(xiàn)在為高電平,則表示檢測(cè)到上升沿
assign nedge = key_old & !key_cur; // 原來(lái)為高電平,現(xiàn)在為低電平,則表示檢測(cè)到下降沿
// 計(jì)數(shù)功能
always@(posedge clk, negedge rst_n)
if(!rst_n)begin
time_cnt <= 20'd0;
// time_en <= 1'b0; // 這里不能再次賦值,因?yàn)樵跔顟B(tài)機(jī)的程序塊中需要對(duì)該信號(hào)賦值
// 一個(gè)信號(hào)不能在多個(gè) always 塊中賦值
end
else if(time_en)
time_cnt <= time_cnt + 1'b1;
else
time_cnt <= 20'd0;
// 檢測(cè)時(shí)間是否已經(jīng)到了 這里指定 20ms 的時(shí)間
always@(posedge clk, negedge rst_n)
if(!rst_n)
time_full <= 1'b0;
else if(time_cnt == `DEC_TIME_CNT)
time_full <= 1'b1;
else
time_full <= 1'b0;
// 狀態(tài)機(jī)
always@(posedge clk, negedge rst_n)
if(!rst_n)begin
state <= IDLE;
key_flag <= 1'b0;
key_state <= 1'b1;
end
else begin
case(state)
IDLE: begin // 空閑狀態(tài):按鍵沒(méi)有被按下
key_flag <= 1'b0; // 在空閑狀態(tài) 按鍵需要清零
if(nedge) begin // 檢測(cè)到下降沿
state <= DING; // 設(shè)置狀態(tài)為 DING,下個(gè)時(shí)鐘上升沿將會(huì)進(jìn)入另外一個(gè)分支
time_en <= 1'b1; // 啟動(dòng)定時(shí)器
end
else
state <= IDLE; // 依據(jù)是高電平,設(shè)置狀態(tài)為 DING
end
DING: begin // 濾波抖動(dòng),按下時(shí)產(chǎn)生的抖動(dòng)狀態(tài)
if(time_full) begin // 如果指定的時(shí)間內(nèi)沒(méi)有上升沿
key_flag <= 1'b1; // 則表示處于穩(wěn)定狀態(tài),進(jìn)入按下?tīng)顟B(tài)
key_state <= 1'b0; // 表示按下
state <= DOWN; // 設(shè)置狀態(tài)為按下
time_en <= 1'b0; // 關(guān)閉定時(shí)器
end
else if(pedge) begin // 如果指定的時(shí)間內(nèi)出現(xiàn)上升沿說(shuō)明是處于抖動(dòng)狀態(tài)
state <= IDLE; // 重新設(shè)置為空閑狀態(tài)
time_en = 1'b0; // 并且關(guān)閉定時(shí)器
end
else // 時(shí)間未到,但也沒(méi)有出現(xiàn)電平變化則進(jìn)行維持此狀態(tài)
state <= DING;
end
DOWN: begin // 按鍵按下?tīng)顟B(tài):此時(shí)經(jīng)過(guò)濾波之后處于按下?tīng)顟B(tài)
key_flag <= 1'b0; // 將標(biāo)志位清0
if(pedge) begin // 如果出現(xiàn)上升沿則表示要彈起
state <= UING;
time_en = 1'b1; // 啟動(dòng)定時(shí)器
end
else // 如果沒(méi)有出現(xiàn)上升沿則維持此狀態(tài)
state <= DOWN;
end
UING: begin
if(time_full)begin // 到達(dá)指定時(shí)間
key_flag <= 1'b1; // 設(shè)置按鍵標(biāo)志
key_state <= 1'b1; // 設(shè)置按鍵狀態(tài)為彈起狀態(tài)
state <= IDLE; // 將狀態(tài)設(shè)置為空閑
time_en <= 1'b0; // 關(guān)閉定時(shí)器
end
else if(nedge)begin // 如果出現(xiàn)下降沿
time_en <= 1'b0; // 關(guān)閉定時(shí)器
state <= DOWN; // 仍設(shè)置為按下?tīng)顟B(tài),即跳回之前的狀態(tài)
end
else
state <= UING; // 如果時(shí)間未到并且未出現(xiàn)電平變化則維持此狀態(tài)
end
default: begin // 如果出現(xiàn)其他狀態(tài),意味著被干擾出現(xiàn)錯(cuò)誤的狀態(tài)
time_en <= 1'b0; // 關(guān)閉定時(shí)器
state <= IDLE;
key_flag <= 1'b0; // 設(shè)置按鍵標(biāo)志
key_state <= 1'b1; // 設(shè)置按鍵狀態(tài)為彈起狀態(tài)
end
endcase
end
endmodule
源碼中的知識(shí)點(diǎn):
1、一個(gè)信號(hào)不允許在兩個(gè)或兩個(gè)以上的always 程序塊中被賦值
2、利用非阻塞賦值語(yǔ)句實(shí)現(xiàn)識(shí)別上升沿下降沿
// 邊沿檢測(cè) …
always@(posedge clk, negedge rst_n)
// 代碼省略..
begin
key_cur <= key_in;
key_old <= key_cur;
end
// 判斷上升沿、下降沿
assign pedge = !key_old & key_cur;
assign nedge = key_old & !key_cur;
3、狀態(tài)機(jī)的使用
二、RTL視圖(黃色是狀態(tài)機(jī)模塊)

三、狀態(tài)機(jī)

從源碼和圖中可以看到狀態(tài)機(jī)實(shí)際上就是利用多個(gè)標(biāo)志位去對(duì)應(yīng)多種狀態(tài),按照我們的邏輯進(jìn)行組合,每一種狀態(tài)對(duì)應(yīng)一系列行為,每一種狀態(tài)都依賴(lài)前一種狀態(tài)的變化,從而實(shí)現(xiàn)模擬順序執(zhí)行的邏輯。
四、仿真測(cè)試代碼
/* 實(shí)驗(yàn)名稱(chēng):按鍵消抖模塊的驗(yàn)證
* 功能實(shí)現(xiàn):驗(yàn)證按鍵消抖模塊是否符合設(shè)計(jì)要求
*/
`timescale 1ns/1ns
`define clock_period 20
module mytest_tb;
reg clk, rst_n, key_in;
wire key_flag, key_state;
mytest u1(clk, rst_n, key_in, key_flag, key_state);
initial clk = 1;
always #(`clock_period / 2) clk = ~clk;
initial begin
rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period * 10) rst_n = 1'b1;
// 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬
#(`clock_period * 10 + 1);
key_Event; // 模擬按鍵事件
#10000;
key_Event; // 模擬按鍵事件
#10000;
key_Event; // 模擬按鍵事件
#10000;
key_Event; // 模擬按鍵事件
#10000;
key_Event; // 模擬按鍵事件
#10000;
$stop;
end
reg[15:0] myrand;
// 按鍵事件
task key_Event;
begin
key_down; // 按鍵按下
key_up; // 按鍵彈起
end
endtask
// 按鍵按下
task key_down;
begin
repeat(50) begin // 模擬按下時(shí)的抖動(dòng)
myrand = {$random} % 65536; // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)
//myrand = $random % 65536; // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)
#myrand key_in = ~key_in;
end
key_in = 0;
#50000000;
end
endtask
// 按鍵彈起
task key_up;
begin
repeat(50) begin // 模擬彈起時(shí)的抖動(dòng)
myrand = {$random} % 65536; // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)
//myrand = $random} % 65536; // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)
#myrand key_in = ~key_in;
end
key_in = 1;
#50000000;
end
endtask
endmodule
源碼中的知識(shí)點(diǎn):
1、隨機(jī)數(shù)的使用
reg[15:0] myrand;
myrand = {$random} % 65536; // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)
//myrand = $random % 65536; // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)
2、task的使用方法,task沒(méi)有返回值。
3、初始化延時(shí)時(shí),不延時(shí)一個(gè)完整的時(shí)鐘周期可以更加真實(shí)的模擬實(shí)際電路的波形
initial begin
rst_n = 1'b0;
key_in = 1'b1;
#(`clock_period * 10) rst_n = 1'b1;
// 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬
#(`clock_period * 10 + 1);
五、波形圖

六、波形分析
全局分析

局部分析

七、仿真測(cè)試代碼模塊化
模擬手動(dòng)按下和釋放按鍵的仿真測(cè)試模塊(這里我對(duì)代碼做了一些修改)
1、模擬按鍵的模塊代碼
/* 模塊名稱(chēng):模擬物理按鍵按下釋放
* 功能描述:利用 random 產(chǎn)生隨機(jī)數(shù)來(lái)產(chǎn)生隨機(jī)的時(shí)間模擬按鍵抖動(dòng)
* 端口描述:
* i_key_in: 輸入,連接被測(cè)試模塊所檢測(cè)的按鍵信號(hào)
* o_key_end: 輸出,1:啟動(dòng)模擬 0:模擬完成
*/
`timescale 1ns/1ns
`define clock_period 20
// i_key_in:按鍵信號(hào)輸入 o_key_end:啟動(dòng)結(jié)束標(biāo)志
module key_module_tb(i_key_in, o_key_end);
output reg i_key_in;
output reg o_key_end;
initial begin
key_start;
i_key_in = 1'b1;
key_Event;
#10000;
key_Event;
#10000;
key_Event;
#10000;
key_stop;
end
task key_start;
o_key_end <= 1'b1; // 修改端口狀態(tài)來(lái)通知其他模塊啟動(dòng)測(cè)試
endtask
task key_stop;
o_key_end <= 1'b0; // 修改端口狀態(tài)來(lái)通知其他模塊測(cè)試結(jié)束
endtask
reg[15:0] myrand;
task key_Event;
begin
key_down; // 按鍵按下
key_up; // 按鍵彈起
end
endtask
task key_down;
begin
repeat(50) begin // 模擬按下時(shí)的抖動(dòng)
myrand = {$random} % 65536; // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)
//myrand = $random} % 65536; // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)
#myrand i_key_in = ~i_key_in;
end
i_key_in = 0;
#50000000;
end
endtask
task key_up;
begin
repeat(50) begin // 模擬彈起時(shí)的抖動(dòng)
myrand = {$random} % 65536; // 產(chǎn)生 0 ~ 65535 隨機(jī)數(shù)
//myrand = $random} % 65536; // 產(chǎn)生 -65535 ~ 65535 隨機(jī)數(shù)
#myrand i_key_in = ~i_key_in;
end
i_key_in = 1;
#50000000;
end
endtask
endmodule
該仿真代碼并不是完全照搬視頻中的代碼,我覺(jué)得既然要將仿真代碼模塊化,那么就不應(yīng)該在模塊里面調(diào)用 $stop 去終止仿真執(zhí)行。這樣頂層仿真模塊,調(diào)用這些子模塊而不會(huì)因?yàn)樽幽K調(diào)用 $stop 終止仿真,導(dǎo)致之后的仿真無(wú)法繼續(xù)。為此我利用自己目前所掌握的知識(shí)做了一點(diǎn)改動(dòng),增加了輸出端口,這個(gè)端口是為了標(biāo)識(shí)仿真模塊啟動(dòng)和停止的狀態(tài)。在頂層模塊中只需要通過(guò)always檢測(cè)下降沿就能得知模塊什么時(shí)候結(jié)束。
2、頂層仿真測(cè)試代碼
/* 實(shí)驗(yàn)名稱(chēng):仿真代碼模塊化的驗(yàn)證
* 功能實(shí)現(xiàn):驗(yàn)證按鍵消抖模塊是否符合設(shè)計(jì)要求
*/
`timescale 1ns/1ns
`define clock_period 20
module mytest_tb;
reg clk, rst_n;
wire key_flag, key_state;
wire key_in, key_end;
mytest u1(clk, rst_n, key_in, key_flag, key_state);
key_module_tb key(key_in, key_end);
initial clk = 1;
always #(`clock_period / 2) clk = ~clk;
initial begin
rst_n = 1'b0;
#(`clock_period * 10) rst_n = 1'b1;
// 延時(shí)10時(shí)鐘周期,再加1ns 錯(cuò)開(kāi)完整的時(shí)鐘這樣可以更加真實(shí)的模擬
#(`clock_period * 10 + 1);
end
always@(negedge key_end)
$stop; // 檢測(cè)到模擬按鍵的模塊完成模擬,所以停止仿真
endmodule
3、波形圖
