本文為明德揚原創及錄用文章,轉載請注明出處!
1.1 總體設計
1.1.1 概述
學習了明德揚至簡設計法和明德揚設計規范,本人設計了一個基于FPGA的出租車計費系統。該系統由一個按鍵表示出租車上是否有乘客,再通過檢測出租車的檔位和輪胎的轉速來獲得乘客所需支付的總費用。在本案例的設計過程中,包含了按鍵定義和消抖、計數器、數碼管顯示等技術。經過逐步改進、調試等一系列操作之后,完成了此設計,下面將完整的設計記錄與大家分享。
本工程使用VIVADO進行仿真,未進行上板。
1.1.2 設計目標
此設計可以完成出租車計費的功能。在按鍵按下后(乘客上車),開始按如下規則計費:
起步價5元,超過3KM以每公里2元計費,如果遇到紅綠燈、堵車等需要停車等待時,則以每20分鐘1元計費。
再按一下按鍵(乘客下車)則計費結束,算出乘客所需支付的總費用,并在數碼管上顯示。
1.1.3 系統結構框圖
系統結構框圖如下所示:
1.1.4 模塊功能
由于按鍵信號是異步信號,所以對該將信號打兩拍處理,將異步信號同步化;
實現10ms按鍵消抖功能,并輸出按鍵信號key_flag,每按一次按鍵,key_flag取反。
其中key_flag=1表示有乘客,key_flag=0表示沒有乘客。
通過按鍵信號key_flag有效,對輪胎轉速進行每秒取樣,進而獲得乘客行駛總的路程和判斷按時計費的使能信號en_0是否有效。
通過speed模塊的總路程和按時計費有效使能信號來獲得乘客所需支付的總費用,再取得總費用的個位、十位和百位數值并輸出。
采用動態掃描3個數碼管的方式顯示fare模塊中獲得的三個數值即為乘客所需支付的總費用。
1.1.5 頂層信號
|
|
|
|
|
|
|
|
|
|
|
|
|
|
出租車輪胎轉速信號。通過該信號,可以用來計算汽車行駛的距離。
|
|
|
|
|
|
|
|
|
段選信號,共8位。由低到高,分別表示數碼管的a,b,c,d,e,f,g,點。當該比特為0時,表示點亮相應位置;為1時熄滅。
|
1.1.6 頂層代碼
module taxi_fare(
clk ,
rst_n ,
d_w ,
rev ,
key_in ,
seg_sel ,
segment
);
input clk ;
input rst_n ;
input d_w ; //檔位
input [15:0] rev ; //轉速
input key_in ; //按鍵,表示乘客是否上車
output seg_sel ; //位選信號,在低電平是該位置數碼管亮。
output segment ; //段選信號,共8位。由低到高,分別表示數碼管的a,b,c,d,e,f,g,點。當該比特為0時,表示點亮相應位置;為1時熄滅。
wire [3-1:0] seg_sel ;
wire [8-1:0] segment ;
//中間量
wire key_flag ; //有乘客為1,無乘客為0
wire en_0 ; //按時間計費使能信號
wire [19:0] distance ; //行駛路程
wire [3:0] x_g ; //總費用的個位值
wire [3:0] x_s ; //總費用的十位值
wire [3:0] x_b ; //總費用的百位值
key_litter key_litter_0(
.clk (clk) ,
.rst_n (rst_n) ,
.key_in (key_in) ,
.key_flag (key_flag)
);
speed speed_0(
.clk (clk) ,
.rst_n (rst_n) ,
.d_w (d_w) ,
.rev (rev) ,
.key_flag (key_flag) ,
//輸出信號
.en_0 (en_0) ,
.distance (distance)
);
fare fare_0(
.clk (clk) ,
.rst_n (rst_n) ,
.distance (distance) ,
.en_0 (en_0) ,
.key_flag (key_flag) ,
.x_g (x_g) ,
.x_s (x_s) ,
.x_b (x_b)
);
show show_0(
.clk (clk) ,
.rst_n (rst_n) ,
.din ({x_b,x_s,x_g}) ,
.din_vld (3'b111) ,
.disp_en (1) ,
.seg_sel (seg_sel) ,
.segment (segment)
);
endmodule
|
1.2 key模塊設計
1.2.1 接口信號
1.2.2 設計思路
此模塊通過一個按鍵來表示車上是否有乘客,復位時輸出信號key_flag為0,當乘客上車時,司機按一下按鍵,key_flag由0變,1,表明乘客已上車,系統開始計費。到達目的地時,司機再按一下按鍵,key_flag由1變0,表明到達目的地乘客下車,系統結束計費。
獨立式按鍵工作原理如上圖所示,4條輸入線接到FPGA的IO口上,當按鍵K1按下時,VCC通過電阻R1再通過按鍵K1最終進入GND形成一條通路,這條線路的全部電壓都加在R1上,則引腳P14是低電平。當松開按鍵后,線路斷開,就不會有電流通過,P14和VCC就應該是等電位,為高電平。我們可以通過P14這個IO口的高低電平狀態來判斷是否有按鍵按下。其它按鍵原理與K1一致,當然本實驗只需要一個按鍵即可,任選一個按鍵都可以。
從圖中可以看出,如果我們按下按鍵,那么按鍵就會接通并連接到低電平GND,如果我們沒有按下,那么按鍵就會斷開并接到VCC,因此按鍵為低電平有效。通常的按鍵所用開關為機械彈性開關,當機械觸點斷開或者閉合時,由于機械觸點的彈性作用,一個按鍵開關在閉合時不會馬上穩定地接通,在斷開時也不會一下子斷開。因而機械式按鍵在閉合及斷開的瞬間均伴隨有一連串的抖動,如果不進行處理,會使系統識別到抖動信號而進行不必要的反應,導致模塊功能不正常,為了避免這種現象的產生,需要進行按鍵消抖的操作。
按鍵消抖主要分為硬件消抖和軟件消抖。兩個“與非”門構成一個RS觸發器為常用的硬件消抖。軟件方法消抖,即檢測出鍵閉合后執行一個延時程序,抖動時間的長短由按鍵的機械特性決定,一般為5ms~20ms,讓前沿抖動消失后再一次檢測鍵的狀態,如果仍保持閉合狀態電平,則確認按下按鍵操作有效。當檢測到按鍵釋放后,也要給5ms~20ms的延時,待后沿抖動消失后才能轉入該鍵的處理程序。經過按鍵消抖的行人優先按鍵,判斷按鍵有效后,按鍵信號傳遞給控制系統,控制系統再進入相應的處理程序。如還不明白之處,見實驗的PDF。
1.2.3 參考代碼
使用明德揚的計數器模板,可以很快速很熟練地寫出按鍵消抖模塊。
每10ms掃描一次按鍵輸入key_in,可以達到消抖的目的,再用寄存器緩存一下,按鍵為低電平有效;但本實驗是需要按鍵按下松開這樣一次完整的按按鍵操作后輸出key_flag才發生變化,所以檢測當檢測到按鍵有上升沿變化時,代表該按鍵被按下松開,按鍵輸出key_flag才發生變化。
本模塊設計了一個狀態機,用于按鍵的檢測。該狀態機共包括4個狀態。
其各個狀態的含義如下。
空閑狀態(IDLE):表示按鍵沒有被按下,檢測到低電平進入下一狀態。
延時確認狀態(S1):開始10ms延時計數,若計數完成且依然為低電平則進入下一狀態,若計數期間按鍵出現高電平說明為抖動回到初始狀態。
檢測釋放狀態(S2):表示按鍵按下未松開,檢測到高電平進入下一狀態。
延時確認狀態(S3):開始10ms延時計數,若計數完成且依然為高電平視為有效松手行為進入初始狀態再檢測下一次按鍵按下,若計數期間按鍵出現低電平說明為抖動回到S2狀態。
代碼如下:
module key_litter(
clk ,
rst_n ,
key_in ,
key_flag
);
//消抖的狀態
parameter IDLE = 4'b0000 ;
parameter S1 = 4'b0001 ;
parameter S2 = 4'b0010 ;
parameter S3 = 4'b0100 ;
parameter S4 = 4'b1000 ;
//輸入信號定義
input clk ;
input rst_n ;
input key_in ;
//輸出信號定義
output key_flag; //輸出,表示是否有乘客
//輸出信號reg定義
reg key_flag;
//中間信號定義
reg [19:0] cnt ;
reg key_in_1; //寄存器
reg key_in_2; //寄存器
reg [3:0] state_c ;
reg [3:0] state_n ;
wire idl2s1_start ;
wire s12s2_start ;
wire s12s2_end ;
wire s22s3_start ;
wire s32s4_start ;
wire s32s4_end ;
wire add_cnt ; //計數進行
wire end_cnt ; //計數清零
wire cnt_during ; //計數過程中
//打兩拍
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_in_1 <= 0;
key_in_2 <= 0;
end
else begin
key_in_1 <= key_in;
key_in_2 <= key_in_1;
end
end
//消抖
//四段式狀態機
//第一段:同步時序always模塊,格式化描述次態寄存器遷移到現態寄存器(不需更改)
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段:組合邏輯always模塊,描述狀態轉移條件判斷
always@(*)begin
case(state_c)
IDLE:begin
if(idl2s1_start)begin
state_n = S1;
end
else begin
state_n = state_c;
end
end
S1:begin
if(s12s2_start)begin
state_n = S2;
end
else if(s12s2_end)begin
state_n = IDLE ;
end
else begin
state_n = state_c;
end
end
S2:begin
if(s22s3_start)begin
state_n = S3;
end
else begin
state_n = state_c;
end
end
S3:begin
if(s32s4_start)begin
state_n = IDLE;
end
else if(s32s4_end)begin
state_n = S2;
end
else begin
state_n = state_c;
end
end
default:begin
state_n = IDLE;
end
endcase
end
//第三段:設計轉移條件
assign idl2s1_start = key_in_2 == 0;
assign s12s2_start = key_in_2==0 && end_cnt == 1;
assign s12s2_end = key_in_2==1 && cnt_during ;
assign s22s3_start = key_in_2 == 1;
assign s32s4_start = key_in_2 == 1 && end_cnt == 1;
assign s32s4_end = key_in_2 == 0 && cnt_during ;
//第四段:同步時序always模塊,格式化描述寄存器輸出(可有多個輸出)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_flag <=1'b0 ; //初始化
end
else if(s32s4_start)begin
key_flag = ~key_flag;
end
else begin
key_flag <= key_flag;
end
end
//計數器,計數10ms
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else
cnt <= 0;
end
assign add_cnt = state_c == S1 || state_c == S3;
assign end_cnt = add_cnt && cnt== /*仿真時使用以減少仿真時間5 - 1;*/ 500000-1;
assign cnt_during = add_cnt && end_cnt == 0;
endmodule
|
1.3 speed模塊設計
1.3.1 接口信號
|
|
|
|
|
|
|
|
|
|
|
|
|
|
出租車輪胎轉速信號。通過該信號,可以用來計算汽車行駛的距離。
|
|
|
|
|
|
表示出租車按時間計費信號,1位按時間計費,0則不按時間計費。等待紅綠燈或堵車等需要停車過程中,使用時間計費;行駛過程中,使用距離計費。
|
|
|
|
1.3.2 設計思路
消抖后的按鍵信號輸入到本模塊中,同樣使用明德揚至簡設計法和計數器模板,可以快速寫出計算總路程和獲得按時計費信號en_0有效的代碼。當key_flag為1有效且轉速rev>=3r/s時,計數器開始計數,每計一秒鐘對轉速信號rev取樣,獲得每秒行駛路程并累加,當key_flag為0時,計數停止,累加也停止,此時獲得的累加值即為總路程。當key_flag為1有效且rev<3r/s時,en_0拉高為1,表示此時需要按時計費。
1.3.3 參考代碼
module speed(
clk ,
rst_n ,
d_w ,
rev ,
key_flag,
//輸出信號
en_0 ,
distance
);
//參數定義
parameter DATA_W = 20;
//輸入信號定義
input clk ;
input rst_n ;
input d_w ; //檔位
input rev ; //轉速
input key_flag; //有無乘客信號
//輸入信號定義
wire [15:0] rev ;
//輸出信號定義
output[DATA_W-1:0] distance ; //路程
output en_0 ; //按時計費信號
//輸出信號reg定義
reg [DATA_W-1:0] distance ;
reg en_0 ;
//中間信號定義
reg [15:0] rev_1 ; //每秒取樣轉速信號
reg [25:0] cnt ; //一秒的計數器
wire high_en_0 ; //拉高按時計費信號的信號
wire add_cnt ;
wire end_cnt ;
//按時計費
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
en_0 <= 0;
end
else if(high_en_0) begin
en_0 <= 1;
end
else
en_0 <= 0;
end
assign high_en_0 = ( d_w == 0 && rev < 3 || rev < 3 ) && key_flag == 1;
//計數1秒
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
else if(key_flag == 0)
cnt <= 0;
end
assign add_cnt = rev >= 3 && key_flag == 1;
assign end_cnt = add_cnt && cnt == /*仿真時使用以節省仿真時間500-1;*/50000000-1 ;
//每秒取樣rev
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rev_1 <= 0;
end
else if(end_cnt) begin
rev_1 <= rev;
end
end
//distance
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
distance <= 0;
end
else if(end_cnt) begin
distance <=distance + rev_1/3;
end
end
endmodule
|
1.4 fare模塊設計
1.4.1 接口信號
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
表示是否有乘客,有為1,沒有為0,且在沒有乘客時,所有信號歸0
|
|
|
|
|
|
|
|
|
|
1.4.2 設計思路
從speed模塊中得到乘客乘坐總路程distance和按時計費使能信號en_0,然后以5元起步價,超過3KM以每滿1公里2元的計費方式計算出按路程計費的總費用,再通過en_0按20分鐘1元的計費方式計算出按時間計費的總費用,再求和獲得總費用,最后得到總費用得個位、十位、百位,分別是x_g、x_s、x_b。
1.4.3 參考代碼
module fare(
clk ,
rst_n ,
distance,
en_0 ,
key_flag,
x_g ,
x_s ,
x_b
);
//參數定義
parameter DATA_W = 4;
//輸入信號定義
input clk ;
input rst_n ;
input en_0 ; //按時計費
input [19:0] distance; //路程
input key_flag; //有無乘客
//輸出信號定義
output[DATA_W-1:0] x_g ; //數碼管顯示個位
output[DATA_W-1:0] x_s ; //數碼管顯示十位
output[DATA_W-1:0] x_b ; //數碼管顯示百位
//輸出信號reg定義
reg [DATA_W-1:0] x_g ;
reg [DATA_W-1:0] x_s ;
reg [DATA_W-1:0] x_b ;
//中間信號定義
reg [9:0] taxi_fare ; //總車費
reg [9:0] taxi_fare_1; //按路程車費
reg [9:0] taxi_fare_2; //按時間車費
reg [35:0] cnt ; //計數20分鐘
reg key_flag1 ; //寄存器
reg key_flag2 ; //寄存器
reg key_flag_low ; //乘客下車信號
reg key_flag_low1 ; //乘客下車后隔一個時鐘周期信號
reg [2:0] state ;
wire add_taxi_fare_1;
wire add_taxi_fare_2;
wire add_cnt ;
wire end_cnt ;
//獲取key_flag下降沿,表示乘客下車
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag1 <= 0;
end
else begin
key_flag1 <= key_flag;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag2 <= 0;
end
else begin
key_flag2 <= key_flag1;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag_low <= 0;
end
else if(key_flag2==1&&key_flag1==0)begin
key_flag_low <= 1;
end
else begin
key_flag_low <= 0;
end
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
key_flag_low1 <= 0;
end
else
key_flag_low1 <= key_flag_low;
end
//按路程計費
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare_1 <= 0;
end
else if(add_taxi_fare_1)begin
if(distance<=3000)begin
taxi_fare_1 <= 5;
end
else if(distance>3000)begin
taxi_fare_1 <= 3'd5+ (distance-3000) / 500;//* 0.002;
end
end
end
assign add_taxi_fare_1 = key_flag_low && en_0 == 0 ;
//按時間計費
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare_2 <= 0;
end
else if(add_taxi_fare_2)begin
taxi_fare_2 <= taxi_fare_2 + 1;
end
end
assign add_taxi_fare_2 = end_cnt == 1;
//計數20分鐘
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)
cnt <= 0;
else
cnt <= cnt + 1;
end
end
assign add_cnt = en_0 == 1 && key_flag == 1;
assign end_cnt = add_cnt && cnt== /*仿真時使用以節省仿真時間500*1200-1;*/ 50000000*1200-1;
//獲得總車費
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
taxi_fare <= 0;
end
else if(key_flag_low1==1)begin
taxi_fare <= taxi_fare_1+ taxi_fare_2;
end
end
//輸出
always @(*)begin
if(rst_n == 1'b0)begin
x_g <= 0;
x_s <= 0;
x_b <= 0;
state <= 1;
end
else if(taxi_fare != 0)begin
case(state)
1: begin x_g <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 2;end
2: begin x_s <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 3;end
3: begin x_b <= taxi_fare%10;taxi_fare <= taxi_fare/10;state <= 1;end
endcase
end
end
endmodule
|
1.5 show模塊設計
1.5.1 接口信號
|
|
|
|
|
|
|
|
|
|
|
打開數碼管顯示的使能信號。1表示打開顯示,0表示不顯示。
|
|
|
|
|
|
數碼管顯示數據刷新使能信號。當其值為1有效時,刷新要顯示的數據值。
|
|
|
|
|
|
段選信號,共8位。由低到高,分別表示數碼管的a,b,c,d,e,f,g,點。當該比特為0時,表示點亮相應位置;為1時熄滅。
|
1.5.2 設計思路
由于在fare模塊中已經獲得總費用的個位、十位和百位的值,所以在本模塊只需要控制3個數碼管對其數值進行顯示即可。
本模塊在設計過程中采用動態掃描3個數碼管的方式進行顯示,并且直接使用明德楊提供的數碼管顯示規范代碼。動態掃描方式相比于使用3個獨立的數碼管顯示會節約資源,硬件電路更簡單,且數碼管越多優勢越明顯。
1.5.3 參考代碼
接口定義:
clk : 時鐘信號,頻率是50MHz
rst_n : 復位信號,在低電平時有效
seg_sel : 位選信號,在低電平是該位置數碼管亮。
segment : 段選信號,共8位。由低到高,分別表示數碼管的a,b,c,d,e,f,g,點。當該比特為0時,表示點亮相應位置;為1時熄滅。
module show(
rst_n ,
clk ,
disp_en ,
din ,
din_vld ,
seg_sel ,
segment
);
參數定義,明德揚規范要求,verilog內的用到的數字,都使用參數表示。
參數信號全部大寫
parameter SEG_WID = 8;
parameter SEG_NUM = 3;
parameter COUNT_WID = 26;
parameter TIME_20US = 20'd1000;
parameter NUM_0 = 8'b1100_0000;
parameter NUM_1 = 8'b1111_1001;
parameter NUM_2 = 8'b1010_0100;
parameter NUM_3 = 8'b1011_0000;
parameter NUM_4 = 8'b1001_1001;
parameter NUM_5 = 8'b1001_0010;
parameter NUM_6 = 8'b1000_0010;
parameter NUM_7 = 8'b1111_1000;
parameter NUM_8 = 8'b1000_0000;
parameter NUM_9 = 8'b1001_0000;
parameter NUM_F = 8'b1011_1111;
parameter NUM_ERR = 8'b1000_0110;
input clk ;
input rst_n ;
input disp_en ;
input [SEG_NUM*4-1:0] din ;
input [SEG_NUM-1:0] din_vld ;
output [SEG_NUM-1:0] seg_sel ;
output [SEG_WID-1:0] segment ;
reg [SEG_NUM-1:0] seg_sel ;
reg [SEG_WID-1:0] segment ;
reg [COUNT_WID-1:0] cnt0 ;
wire add_cnt0 ;
wire end_cnt0 ;
reg [SEG_NUM-1:0] cnt1 ;
wire add_cnt1 ;
wire end_cnt1 ;
reg [4*SEG_NUM-1:0] din_ff0 ;
reg [ 4-1:0] seg_tmp ;
wire flag_20us ;
integer ii ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt0 <= 0;
end
else if(add_cnt0)begin
if(end_cnt0)
cnt0 <= 0;
else
cnt0 <= cnt0 + 1;
end
end
assign add_cnt0 = 1;
assign end_cnt0 = add_cnt0 && cnt0==TIME_20US-1 ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt1 <= 0;
end
else if(add_cnt1)begin
if(end_cnt1)
cnt1 <= 0;
else
cnt1 <= cnt1 + 1;
end
end
assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==SEG_NUM-1 ;
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
seg_sel <= {SEG_NUM{1'b1}};
end
else if(disp_en)
seg_sel <= ~(1'b1 << cnt1);
else
seg_sel <= {SEG_NUM{1'b1}};
end
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
din_ff0 <= 0;
end
else begin
for(ii=0;ii<SEG_NUM;ii=ii+1)begin
if(din_vld[ii]==1'b1)begin
din_ff0[(ii+1)*4-1 -:4] <= din[(ii+1)*4-1 -:4];
end
else begin
din_ff0[(ii+1)*4-1 -:4] <= din_ff0[(ii+1)*4-1 -:4];
end
end
end
end
always @(*)begin
seg_tmp = din_ff0[(cnt1+1)*4-1 -:4];
end
always@(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
segment<=NUM_0;
end
else if(seg_tmp==0)begin
segment<=NUM_0;
end
else if(seg_tmp==1)begin
segment<=NUM_1;
end
else if(seg_tmp==2)begin
segment<=NUM_2;
end
else if(seg_tmp==3)begin
segment<=NUM_3;
end
else if(seg_tmp==4)begin
segment<=NUM_4;
end
else if(seg_tmp==5)begin
segment<=NUM_5;
end
else if(seg_tmp==6)begin
segment<=NUM_6;
end
else if(seg_tmp==7)begin
segment<=NUM_7;
end
else if(seg_tmp==8)begin
segment<=NUM_8;
end
else if(seg_tmp==9)begin
segment<=NUM_9;
end
else if(seg_tmp==4'hf)begin
segment<=NUM_F;
end
else begin
segment<=NUM_ERR;
end
end
endmodule
|
1.6 效果和總結
由于本系統涉及1秒、20分鐘等時間節點,時間很長,所以在仿真時有一定的困難,為此我將系統中的時間節點全部乘上10^(-5),讓所有狀態提前到達,方便我們仿真驗證。
由于乘上了10^(-5),所以原消抖10ms在仿真中為100ns;
系統中原1s取樣轉速rev在仿真中為10us;
原按時計費20分鐘/元在仿真中為12ms/元;
在測試文件中轉速rev>=3r/s的時間是50000000ns;
總路程distance=15/3*50000000/10000=25km;
由于在測試文件中按鍵消抖有一段時間且這一段時間在rev>=3r/s的時間內,所以系統實際對速度采樣的次數會少一次,所以最終的總路程會少5m;
所以,按路程計費的總費用taxi_fare_1=5+(25 - 3)*2 – 0.005*2=48.99(元);
去除小數部分taxi_fare_1=48(元);
在測試文件中轉速rev<3r/s的時間是24000000ns;
按時間計費的總費用taxi_fare_2=24000000/12000000=2(元);
所以總車費taxi_fare_1=taxi_fare_1+taxi_fare_2=50(元);
`define clk_period 20
module taxi_fare_tb(
);
reg clk ;
reg rst_n ;
reg d_w ;
reg [15:0] rev ;
reg key_in ;
wire [3-1:0] seg_sel ;
wire [8-1:0] segment ;
taxi_fare taxi_fare_0(
.clk (clk) ,
.rst_n (rst_n) ,
.d_w (d_w) ,
.rev (rev) ,
.key_in (key_in) ,
.seg_sel (seg_sel) ,
.segment (segment)
);
initial clk = 1;
always#(`clk_period/2) clk = ~clk;
initial begin
rst_n = 0;
#100;
rst_n = 1;
end
initial begin
d_w = 0;
#10000;
d_w = 1;
#200000000;
d_w = 0;
end
initial begin
rev = 0;
#10000;
rev = 15;
#50000000;
rev = 1;
#24000000;
rev = 0;
end
initial begin
key_in = 1;
#1000;
key_in = 0;
#10;
key_in = 1;
#10;
key_in = 0;
#10;
key_in = 1;
#10;
key_in = 0;
#9000;
key_in = 1;
#10;
key_in = 0;
#10;
key_in = 1;
#74000000;
key_in = 0;
#9000;
key_in = 1;
end
endmodule
|
由仿真結果可以看到seg_sel=110第1位數碼管顯示時segment=11000000、seg_sel=101第2位數碼管顯示時segment=10010010、seg_sel=011第3位數碼管顯示時segment=11000000,在數碼管上分別對應數值為0、5、0,表示為50元。
仿真結果和理論結構都是50元,符合我們的預期,驗證成功。
在這個設計中,使用明德楊的至簡設計法,讓我的思路非常清晰,邏輯非常嚴謹,雖然沒有做到一遍成功,但在調試過程中我都比較快速的找到問題,并快速解決。對于學習FPGA的同學,我非常推薦使用明德楊至簡設計法和明德楊模塊進行學習和設計。
感興趣的朋友也可以訪問明德揚論壇(http://www.fpgabbs.cn/)進行FPGA相關工程設計學習,也歡迎大家在評論與我們進行討論!