以下文章來源于數(shù)字站 ,作者數(shù)字站
01概括
本文通過FPGA實現(xiàn)8位十進(jìn)制數(shù)的加、減、乘、除運(yùn)算,通過矩陣鍵盤輸入數(shù)據(jù)和運(yùn)算符,矩陣鍵盤的布局圖如下所示。該計算器可以進(jìn)行連續(xù)運(yùn)算,當(dāng)按下等號后,可以直接按數(shù)字進(jìn)行下次運(yùn)算,或者按運(yùn)算符,把上次運(yùn)算結(jié)果作為本次運(yùn)算的第一個操作數(shù)。
圖1 矩陣鍵盤
通過clr可以清除之前輸入的所有數(shù)據(jù),在輸入運(yùn)算符時,可以輸入多次,但是只有最后一次輸入的運(yùn)算符有效。計算器輸入的數(shù)字和計算結(jié)果通過8個數(shù)碼管進(jìn)行顯示。
該工程的資源消耗圖如下所示,總共消耗六百多個LUT,實現(xiàn)27位除法運(yùn)算和14位乘法運(yùn)算,以及二進(jìn)制轉(zhuǎn)BCD碼等。消耗的其余資源均不超過百分之二十。
圖2 資源利用率
02頂層模塊設(shè)計
頂層模塊的信號列表如下所示:
表1 頂層模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
key_row | O | 4 | 矩陣鍵盤行輸出信號。 |
key_col | I | 4 | 矩陣鍵盤列輸入信號,低電平有效。 |
sclk | O | 1 | 74HC595移位時鐘信號,上升沿有效。 |
rclk | O | 1 | 74HC595鎖存時鐘信號,上升沿有效。 |
ds | O | 1 | 74HC595串行數(shù)據(jù)信號,在sclk下降沿更新數(shù)據(jù)。 |
頂層模塊只對子模塊進(jìn)行連線,框圖如下所示,總共包括7個子模塊,每個子模塊執(zhí)行不同功能。
圖3 頂層框圖
Key_scan模塊:對按鍵消抖,并且檢測被按下按鍵的位號,按鍵編號取值范圍[0,15],key_vld為高電平表示有按鍵被按下一次,key_out表示被按下按鍵的位號。
Operation_ctrl模塊:計算器的控制和計算模塊,通過檢測被按下的按鍵,執(zhí)行相應(yīng)的功能,并且把數(shù)碼管需要顯示的數(shù)據(jù)通過operat_out信號輸出,該信號為二進(jìn)制數(shù)據(jù)。
Mult模塊:當(dāng)使能信號為高電平時開始對輸入的數(shù)據(jù)進(jìn)行乘法運(yùn)算。乘數(shù)和被乘數(shù)均支持14位(4位十進(jìn)制數(shù)大小),最大輸出27位數(shù)據(jù)(8位十進(jìn)制最大數(shù)據(jù)的位寬)。因為只有8個數(shù)碼管,所以最大支持8位十進(jìn)制數(shù)據(jù)輸出。該模塊通過移位和加法器實現(xiàn)乘法,能夠運(yùn)行的頻率會更高。
Div模塊:當(dāng)使能信號為高電平時對輸入的數(shù)據(jù)進(jìn)行除法運(yùn)算,除數(shù)和被除數(shù)均支持27位,通過移位和加法器實現(xiàn)除法運(yùn)算。當(dāng)quotient_vld為高電平表示除法運(yùn)算結(jié)束。
Hex2bcd模塊:通過移位的方式實現(xiàn)二進(jìn)制轉(zhuǎn)bcd碼,輸入數(shù)據(jù)高達(dá)27位,如果使用除法和取余實現(xiàn)二進(jìn)制轉(zhuǎn)BCD碼,將消耗大量資源且時鐘運(yùn)行頻率較低。
Seg_disp:數(shù)碼管的刷新控制模塊,8個數(shù)碼共用同一組數(shù)據(jù)線,每個數(shù)碼管通過位選的方式工作,該模塊實現(xiàn)位選及數(shù)據(jù)的控制,將輸入的8個BCD碼顯示在對應(yīng)的8個數(shù)碼管上。
Hc595_drive模塊:該模塊實現(xiàn)74HC595芯片的驅(qū)動,通過三個管腳控制8個數(shù)碼管的顯示。
03矩陣鍵盤檢測模塊
本文只對模塊設(shè)計方法進(jìn)行簡要概括,端口信號含義如下表所示。
表2 按鍵檢測模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
key_row | O | 4 | 矩陣鍵盤行輸出信號。 |
key_col | I | 4 | 矩陣鍵盤列輸入信號,低電平有效。 |
key_out | O | 4 | 被按下按鍵的位號。 |
key_vld | O | 1 | 高電平表示有按鍵被按下。 |
通過一個狀態(tài)機(jī)采用逐行掃描的方式對按鍵進(jìn)行檢測,首先初始狀態(tài)四行全部輸出低電平,檢測列輸入信號是否全部為高電平(列信號被上拉到VCC)。如果列輸入不全為高電平,則表示有按鍵被按下,則啟用一個計數(shù)器cnt,對列輸入不全為高電平的時間進(jìn)行計數(shù)。
如果時間能夠達(dá)到20MS,則認(rèn)為按鍵真的被按下。如果沒有達(dá)到20MS就檢測到列輸入全為高電平,則判定為抖動,此時將計數(shù)器清零,繼續(xù)檢測。當(dāng)確認(rèn)按鍵被按下后,狀態(tài)機(jī)跳轉(zhuǎn)到行檢測,此時需要逐行輸出低電平,其余行輸出高電平,對所有行檢測一遍,從而確定出被按下按鍵的位置,輸出位號。最后狀態(tài)機(jī)跳轉(zhuǎn)到一個等待狀態(tài),直到所有按鍵被釋放后,狀態(tài)機(jī)回到空閑狀態(tài)繼續(xù)檢測。
我只對頂層模塊寫了測試文件,所以其余模塊仿真通過modelsim添加對應(yīng)模塊信號即可,該模塊仿真結(jié)果如下圖所示。TestBench文件里面通過調(diào)用編寫的任務(wù)實現(xiàn)按鍵按下的信號模擬,按下按鍵的前后都是模擬了抖動的,如下圖所示。
紅框部分都是按鍵按下前的抖動,計數(shù)器shake_cnt沒有計數(shù)到最大值key_col所有位都變?yōu)楦唠娖搅耍藭r計數(shù)器就會清零重新檢測,直到檢測到橙色框處,按鍵按下是俺才超過設(shè)定時間,狀態(tài)機(jī)才會跳轉(zhuǎn)。
圖4 抖動檢測仿真
行掃描的細(xì)節(jié)如下所示,此時對每行進(jìn)行檢測,每行掃描時間持續(xù)16個時鐘周期,通過計數(shù)器row_cnt記錄一行掃描的時間,計數(shù)器row_index記錄掃描第幾行了。當(dāng)掃描第0行時,列輸入不全為高電平,表示被按下的按鍵在第0行。
列輸入值為4’hd,第一列為低電平,表示被按下按鍵在第1列,則計算出位號為1,此時key_out輸出1,key_vld拉高一個時鐘周期,表示1號按鍵被按下一次。
圖5 按鍵檢測
04控制模塊
該模塊主要實現(xiàn)被按下按鍵的識別,然后進(jìn)行相應(yīng)的計算,并且輸出對應(yīng)的數(shù)據(jù)給數(shù)碼管進(jìn)行顯示,模塊端口信號如下所示:
表3 控制模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
din_key | I | 4 | 被按下按鍵位號。 |
din_key_vld | I | 1 | 高電平表示有按鍵被按下1次。 |
div_vld | I | 1 | 高電平表示除法器計算結(jié)束。 |
div_in | I | 27 | 除法器運(yùn)算結(jié)果。 |
mult_vld | I | 1 | 高電平表示乘法器計算結(jié)束。 |
mult_in | I | 27 | 乘法器計算結(jié)果。 |
div_en | O | 1 | 高電平驅(qū)動除法器開始工作。 |
mult_en | O | 1 | 高電平驅(qū)動乘法器開始工作。 |
data1 | O | 27 | 計算器的1號運(yùn)算數(shù),作為被除數(shù)/被乘數(shù)/被加數(shù)/被減數(shù)。 |
data2 | O | 27 | 計算器的2號運(yùn)算數(shù),作為除數(shù)/乘數(shù)/加數(shù)/減數(shù)。 |
dout | O | 27 | 數(shù)碼管需要顯示的二進(jìn)制數(shù)據(jù)。 |
該模塊的功能稍微復(fù)雜一點,既要實現(xiàn)按鍵的識別,又要進(jìn)行加法、減法計算,還要進(jìn)行數(shù)碼管顯示數(shù)據(jù)的控制。均通過一個狀態(tài)機(jī)實現(xiàn),該狀態(tài)機(jī)的跳轉(zhuǎn)與按下的按鍵有關(guān)。
狀態(tài)機(jī)的狀態(tài)轉(zhuǎn)換圖如下所示,狀態(tài)機(jī)初始位于空閑狀態(tài)(IDLE),當(dāng)有數(shù)字按鍵被按下后,跳轉(zhuǎn)到輸入操作數(shù)1的狀態(tài)(IDTA1),在此狀態(tài)下,需要數(shù)據(jù)1,最多輸入8位十進(jìn)制數(shù)據(jù),檢測到有加減乘除運(yùn)算符按下時,跳轉(zhuǎn)到運(yùn)算符輸入狀態(tài)(OPERAT),該狀態(tài)下可以輸入很多運(yùn)算符,但是只有離開該狀態(tài)時,輸入的最后一個運(yùn)算符有效。
圖6 狀態(tài)轉(zhuǎn)換圖
在輸入運(yùn)算符的狀態(tài)下,如果檢測到數(shù)字按鍵被按下,則跳轉(zhuǎn)到輸入數(shù)字的狀態(tài)(IDTA2),也就是輸入第2個操作數(shù),該狀態(tài)最多輸入8位十進(jìn)制數(shù)據(jù),否則會溢出。
在該狀態(tài)下,如果檢測到等號被按下,則跳轉(zhuǎn)到計算結(jié)果的狀態(tài)(RESULT),該狀態(tài)會根據(jù)輸入的運(yùn)算符,對輸入的數(shù)據(jù)進(jìn)行相應(yīng)運(yùn)算,運(yùn)算符為乘法或除法時,將乘法器使能或除法器使能信號拉高一個時鐘,讓乘法器或除法器模塊工作,當(dāng)乘法器或者除法器計算結(jié)束時,更新計算結(jié)果。
在該狀態(tài)下,如果檢測到數(shù)字按鍵被按下,則跳轉(zhuǎn)到輸入數(shù)據(jù)1狀態(tài),進(jìn)行下次運(yùn)算。如果檢測到運(yùn)算符按鍵被按下,說明用戶進(jìn)行連續(xù)運(yùn)算,則將本次運(yùn)算結(jié)果賦值給操作數(shù)1,狀態(tài)機(jī)跳轉(zhuǎn)到輸入運(yùn)算符狀態(tài),繼續(xù)下次運(yùn)算。
所有狀態(tài),只要清零按鍵被按下,狀態(tài)機(jī)回到空閑狀態(tài)。
再說模塊輸出信號dout,當(dāng)狀態(tài)機(jī)處于IDTA1狀態(tài)時,數(shù)碼管需要顯示輸入的數(shù)據(jù)1,則dout等于data1數(shù)值。當(dāng)狀態(tài)機(jī)處于IDATA2狀態(tài)時,需要顯示輸入的數(shù)據(jù)2,則dout等于data2數(shù)值。當(dāng)狀態(tài)機(jī)處于運(yùn)算結(jié)果狀態(tài)時,則dout根據(jù)運(yùn)算符不同,進(jìn)行相應(yīng)運(yùn)算,輸出不同數(shù)值。
該模塊內(nèi)部還包括一些數(shù)字信號、運(yùn)算符檢測、信號對齊、輸入數(shù)據(jù)的變化等等,這些不做細(xì)講,可以通過源代碼自行閱讀,源代碼均有注釋,閱讀應(yīng)該沒有難度。
對該模塊進(jìn)行總體仿真,在TestBench文件中依次按下這些按鍵,先實現(xiàn)26+290,然后再減82。
圖7 仿真按下按鍵
仿真結(jié)果如下所示,dout是數(shù)碼管需要顯示的數(shù)據(jù),din_key是按鍵消抖模塊檢測被按下的按鍵,通過位號譯碼,首先輸入26,數(shù)碼管現(xiàn)需要先顯示2,然后顯示26,3號按鍵代表加號,然后輸入290作為第二運(yùn)算數(shù),14號按鍵是等號,計算結(jié)果316。然后7號按鍵是減號,之后dout的值賦值給data1,然后輸入82,完成316-82的運(yùn)算,最后數(shù)碼管顯示234。
圖8 加減法仿真
連續(xù)乘法運(yùn)算仿真,如圖所示,上次運(yùn)算結(jié)果為234,然后按下乘號(11號按鍵),將上次運(yùn)算結(jié)果賦值給data1,然后輸入乘數(shù)為13,按下等號后,mult_en拉高,使能乘法器模塊,乘法器計算結(jié)束(mult_vld為高),輸出運(yùn)算結(jié)果3042。
圖9 乘法仿真
連續(xù)除法運(yùn)算仿真,如圖所示,生詞運(yùn)算結(jié)果為3042,然后按下除號(15號按鍵),將上次運(yùn)算結(jié)果賦值給data1,然后輸入除數(shù)為71,按下等號后,div_en拉高,使能除法器模塊,除法器計算結(jié)束(div_vld為高),輸出運(yùn)算結(jié)果42。
圖10 除法仿真
05乘法器模塊
由于篇幅原因,本文不對乘法器的具體實現(xiàn)做講解。端口信號列表如下所示:
表4 乘法器模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
start | I | 1 | 開始計算,高電平有效。 |
multiplicand | I | 14 | 被乘數(shù)輸入。 |
multiplier | I | 14 | 乘數(shù)輸入。 |
product | O | 27 | 乘積輸出。 |
product_vld | O | 1 | 乘積有效指示信號。 |
rdy | O | 1 | 高電平表示模塊空閑,可以進(jìn)行運(yùn)算。 |
由于人使用計算器的頻率較低,所以沒有使用rdy信號,沒有影響。
乘法器模塊仿真如下所示,start為高電平時,被乘數(shù)為234,乘數(shù)為13,通過幾個時鐘周期后,product_v ld拉高,表示計算結(jié)束,輸出乘積為3042。仿真正常,這個模塊輸出延遲與乘數(shù)的值有關(guān),最多不會超過乘數(shù)位寬那么多個時鐘周期,最少1個時鐘周期。
圖11 乘法器仿真
06除法器模塊
由于篇幅原因,本文不對除法器的具體實現(xiàn)做講解。端口信號列表如下所示:
表5 除法器模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
start | I | 1 | 開始計算,高電平有效。 |
dividend | I | 27 | 被除數(shù)輸入。 |
divisor | I | 27 | 除數(shù)輸入。 |
quotient | O | 27 | 商輸出。 |
remainder | O | 27 | 余數(shù)輸出。 |
quotient_vld | O | 1 | 商和余數(shù)除數(shù)有效指示信號。 |
ready | O | 1 | 高電平表示模塊空閑,可以進(jìn)行運(yùn)算。 |
error | O | 1 | 輸入除數(shù)為0。 |
該模塊沒有使用余數(shù)、error、ready信號,對應(yīng)仿真結(jié)果如下圖所示。開始信號有效時,被除數(shù)為3042,除數(shù)為71,經(jīng)過幾個時鐘周期后,quobient_vld拉高,表示除法計算結(jié)束,計算商為42,余數(shù)為60,經(jīng)過驗算后沒有問題。
圖12 除法器仿真
07二進(jìn)制轉(zhuǎn)BCD模塊
該模塊輸入27位二進(jìn)制數(shù)據(jù),輸出32位BCD碼,如果直接使用除法和取余操作,將消耗大量邏輯資源,并且時鐘頻率還不能提高。
所以就采用移位和加法的算法來實現(xiàn)轉(zhuǎn)換,本文不對該模塊具體實現(xiàn)方式進(jìn)行講解,設(shè)計的時候考慮了參數(shù)化,直接修改輸入?yún)?shù)位寬即可實現(xiàn)任意位寬的轉(zhuǎn)換,端口列表如下:
表6 二進(jìn)制轉(zhuǎn)BCD碼模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
din | I | 27 | 二進(jìn)制輸入數(shù)據(jù)。 |
din_vld | I | 27 | 二進(jìn)制輸入數(shù)據(jù)有效指示信號。 |
dout | O | 36 | 轉(zhuǎn)換后BCD碼 |
dout_vld | O | 1 | 轉(zhuǎn)換后的BCD碼有效指示信號。 |
該模塊的din_vld恒為高電平,需要對前文提到模塊稍作修改,修改后的代碼如下所示:
module hex2bcd #( parameter IN_DATA_W = 27 ,//輸入數(shù)據(jù)位寬; parameter OUT_DATA_W = clogb2({{IN_DATA_W}{1'b1}})//自動計算輸出數(shù)據(jù)對應(yīng)的十進(jìn)制位數(shù); )( input clk ,//系統(tǒng)時鐘; input rst_n ,//系統(tǒng)復(fù)位,低電平有效; input [IN_DATA_W-1:0] din ,//輸入二進(jìn)制數(shù)據(jù); input din_vld ,//輸入數(shù)據(jù)有效指示信號,高電平有效; output reg [4*OUT_DATA_W-1:0] dout ,//輸出8421BCD碼; output reg dout_vld //輸出數(shù)據(jù)有效指示信號,高電平有效; ); localparam CNT_W = clogb(IN_DATA_W-3);//根據(jù)輸入數(shù)據(jù)的位寬自動計算需要移動的輪數(shù); //localparam OUT_DATA_W = clogb2({{IN_DATA_W}{1'b1}});//自動計算輸出數(shù)據(jù)對應(yīng)的十進(jìn)制位數(shù); reg [IN_DATA_W-1:0] din_ff0 ; reg flag ; reg [CNT_W-1:0] cnt ; reg [IN_DATA_W+OUT_DATA_W*4-1:0]data_shift ; reg end_cnt_ff0 ; wire [OUT_DATA_W*4-1:0] data_compare; wire add_cnt ; wire end_cnt ; function integer clogb2(input integer depth); begin if(depth==0) clogb2 = 1; else if(depth!=0) for(clogb2=0;depth>0;clogb2=clogb2+1) depth=depth/10; end endfunction //自動計算位寬 function integer clogb(input integer depth);begin if(depth==0) clogb = 1; else if(depth!=0) for(clogb=0;depth>0;clogb=clogb+1) depth=depth>>1; end endfunction //當(dāng)輸入數(shù)據(jù)有效并且此時該模塊空閑時保存輸入數(shù)據(jù),否則不保存輸入數(shù)據(jù),這樣可以保證本次轉(zhuǎn)換數(shù)據(jù)完全正確; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin din_ff0 <= 0; end else if(din_vld)begin din_ff0 <= din; end end //標(biāo)志信號flag,當(dāng)輸入數(shù)據(jù)有效時拉高,當(dāng)計數(shù)器計數(shù)完成時清零; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin flag <= 1'b0; end else if(din_vld)begin flag <= 1'b1; end else if(end_cnt)begin flag <= 1'b0; end end //移位計數(shù)器,每次轉(zhuǎn)換需要移動IN_DATA_W-2次,初始值為0,加一條件flag信號有效,結(jié)束條件是計數(shù)到IN_DATA_W-2次; 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 = flag; assign end_cnt = add_cnt && cnt == IN_DATA_W-3; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin data_shift <= 0; end else if(add_cnt)begin if(cnt==0)begin//初始時將輸入數(shù)據(jù)左移三位保存; data_shift <= {{{OUT_DATA_W-3}{1'b0}},din_ff0,3'b0}; end else begin//計數(shù)器加一條件有效時,將移位寄存器數(shù)據(jù)左移一位; data_shift <= {data_compare[OUT_DATA_W*4-2:0],data_shift[IN_DATA_W-1:0],1'b0}; end end end //移位后大于等于5之后加3; generate genvar bit_num; for(bit_num = 0 ; bit_num < OUT_DATA_W ; bit_num = bit_num + 1)begin : DATA assign data_compare[4*bit_num+3 : 4*bit_num] = data_shift[IN_DATA_W+4*bit_num+3 : IN_DATA_W+4*bit_num] + (data_shift[IN_DATA_W+4*bit_num+3 : IN_DATA_W+4*bit_num]>=5 ? 4'd3 : 4'd0); end endgenerate //將計數(shù)器延遲一拍,用于生成輸出信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin end_cnt_ff0 <= 1'b0; end else begin end_cnt_ff0 <= end_cnt; end end //通過計數(shù)器結(jié)束條件產(chǎn)生輸出信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout <= 0; end else if(end_cnt_ff0)begin dout <= data_shift[IN_DATA_W+OUT_DATA_W*4-1 : IN_DATA_W]; end end //通過計數(shù)器結(jié)束條件生成輸出有效指示信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin dout_vld <= 1'b0; end else begin dout_vld <= end_cnt_ff0; end end endmodule
該模塊的端口仿真如下圖所示,輸入數(shù)據(jù)按照十進(jìn)制顯示,輸出數(shù)據(jù)按照十六進(jìn)制數(shù)據(jù)顯示,輸入數(shù)據(jù)為17’d29時,輸出數(shù)據(jù)為36’h000000029,轉(zhuǎn)換完成。
圖13 二進(jìn)制轉(zhuǎn)BCD碼仿真
08數(shù)碼管刷新模塊
該模塊用的也比較多了,8個數(shù)碼管共用同一組數(shù)據(jù)線,那么這組數(shù)據(jù)線就只能通過時分復(fù)用的方式傳遞數(shù)據(jù)。
對應(yīng)的端口列表如下所示:
表7 數(shù)碼管刷新模塊信號列表
信號名 | I/O | 位寬 | 含義 |
---|---|---|---|
clk | I | 1 | 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 |
rst_n | I | 1 | 系統(tǒng)復(fù)位信號,低電平有效。 |
din | I | 32 | 需要顯示的32位BCD碼 |
segment | O | 8 | 數(shù)碼管的數(shù)據(jù)線 |
seg_sel | O | 8 | 數(shù)碼管位選信號 |
dout_vld | O | 1 | 數(shù)碼管刷新指示信號 |
該模塊后面還有一個74HC595驅(qū)動模塊,所以需要固定一段時間產(chǎn)生數(shù)據(jù)和位選信號,一般數(shù)碼管刷新時間采用20us,所以每隔20us產(chǎn)生一個刷新信號,輸出對應(yīng)數(shù)碼管數(shù)據(jù)和位選信號給74HC595模塊的驅(qū)動,再生成相應(yīng)輸出。
修改后的代碼如下所示:
module seg_disp #( parameter TCLK = 20 ,//系統(tǒng)時鐘周期,單位ns。 parameter TIME_20US = 20_000 ,//數(shù)碼管刷新時間,默認(rèn)20us。 parameter SEG_NUM = 8 //需要顯示的數(shù)碼管個數(shù)。 )( //輸入信號定義 input clk ,//系統(tǒng)時鐘,50MHz。 input rst_n ,//系統(tǒng)復(fù)位,低電平有效。 input [(SEG_NUM * 4) - 1 :0] din ,//需要數(shù)碼管顯示的BCD碼數(shù)碼; //輸出信號定義 output reg [7 : 0] segment ,//數(shù)碼管的數(shù)據(jù)線; output reg [SEG_NUM - 1 : 0] seg_sel ,//數(shù)碼管的位選信號; output reg dout_vld //為高電平時,表示段選和位選信號有效; ); //參數(shù)定義 localparam TIME = TIME_20US/TCLK ; localparam TIME_W = clogb2(TIME-1) ;//計算數(shù)碼管掃描時間的時鐘數(shù)據(jù)位寬; localparam SEG_W = clogb2(SEG_NUM) ; localparam ZERO = 8'h3F ; //8'hC0;前面的數(shù)據(jù)是共陰數(shù)碼管使用的,后面數(shù)據(jù)是共陽數(shù)碼管使用的; localparam ONE = 8'h06 ; //8'hF9; localparam TWO = 8'h5B ; //8'hA4; localparam THREE = 8'h4F ; //8'hB0; localparam FOUR = 8'h66 ; //8'h99; localparam FIVE = 8'h6D ; //8'h92; localparam SIX = 8'h7D ; //8'h82; localparam SEVEN = 8'h07 ; //8'hF8; localparam EIGHT = 8'h7F ; //8'h80; localparam NINE = 8'h6F ; //8'h90; localparam ERR = 8'h77 ; //8'h86; //中間信號定義 reg [3 : 0] sel_result ; reg [SEG_W - 1 : 0] sel ; reg [SEG_W - 1 : 0] sel_ff0 ; reg [TIME_W - 1 : 0] cnt_20us ; reg add_sel_r ;// wire end_cnt_20us; wire add_sel ; wire end_sel ; //自動計算位寬的函數(shù); function integer clogb2(input integer depth); begin if(depth==0) clogb2=1; else if(depth!=0) for(clogb2=0; depth>0;clogb2=clogb2+1) depth=depth>>1; end endfunction //20us計數(shù)器,用于對一個數(shù)碼管點亮的持續(xù)時間進(jìn)行計數(shù),計數(shù)器初始值為0,對 always@(posedge clk or negedge rst_n)begin if(!rst_n)begin//計數(shù)器初始值為0; cnt_20us <= 0; end else if(end_cnt_20us)begin//當(dāng)計數(shù)器計數(shù)到20us時,表示一個數(shù)碼管已經(jīng)被點亮20US了,將計數(shù)器清零; cnt_20us <= 0; end else begin//否則,計數(shù)器加一; cnt_20us <= cnt_20us + 1'b1; end end //計數(shù)器結(jié)束條件,當(dāng)計數(shù)器計數(shù)到TIME-1時表示20US已經(jīng)到了,將計數(shù)器清零; assign end_cnt_20us = cnt_20us == TIME - 1; //計數(shù)器sel,用于計數(shù)此時點亮的時第幾個數(shù)碼管,上電復(fù)位時點亮第零個數(shù)碼管,所以初始值為0,之后當(dāng)計數(shù)器cnt_20us計數(shù)結(jié)束時,表示一個數(shù)碼管點亮?xí)r間已經(jīng)到了,此時計數(shù)器sel加一,表示該點亮下一個計數(shù)器了,當(dāng)點亮SEG_NUM-1個計數(shù)器完成(end_sel有效)時表示數(shù)碼管都被點亮了一次,此時計數(shù)器sel清零,又從第一個數(shù)碼管開始點亮; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; sel <= 0; end else if(add_sel) begin if(end_sel)//當(dāng)計數(shù)器sel計數(shù)結(jié)束時,計數(shù)器清零; sel <= 0; else sel <= sel + 1; end end assign add_sel = end_cnt_20us;//計數(shù)器sel的加一條件是,計數(shù)器cnt_20us計數(shù)器結(jié)束; assign end_sel = add_sel && sel == SEG_NUM - 1;//計數(shù)器sel計數(shù)到SEL_NUM-1時,計數(shù)器sel清零; //sel_result信號是當(dāng)前被點亮數(shù)碼管需要顯示的數(shù)據(jù),根據(jù)計數(shù)器sel的值確定此時應(yīng)該將輸入信號的哪幾位數(shù)據(jù)譯碼輸出給數(shù)碼管進(jìn)行顯示; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0; sel_result <= 4'd0; end else if(add_sel)begin//取輸入信號din[4*sel+3 : 4*sel]信號給譯碼部分進(jìn)行譯碼,之后輸出給數(shù)碼管數(shù)據(jù)信號驅(qū)動數(shù)碼管顯示該數(shù)據(jù); sel_result <= din[4*sel+3 -: 4];//{din[4*sel+3],din[4*sel+2],din[4*sel+1],din[4*sel]}; end end //譯碼器部分,將sel_result十進(jìn)制信號譯碼成數(shù)碼管顯示該數(shù)字對應(yīng)的八位數(shù)據(jù)信號; always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始上電時,所有數(shù)碼管顯示數(shù)據(jù)0; segment <= ZERO; end else if(add_sel_r)begin case(sel_result)//將sel_result譯碼成對應(yīng)的segment數(shù)據(jù),segment數(shù)據(jù)驅(qū)動數(shù)碼管才能顯示sel_result代表的數(shù)字; 0: segment <= ZERO ;//想要數(shù)碼管顯示0,就要給數(shù)碼管數(shù)據(jù)信號segment輸入ZERO數(shù)據(jù),其余類似; 1: segment <= ONE ; 2: segment <= TWO ; 3: segment <= THREE; 4: segment <= FOUR ; 5: segment <= FIVE ; 6: segment <= SIX ; 7: segment <= SEVEN; 8: segment <= EIGHT; 9: segment <= NINE ; default: segment <= ERR; endcase end end //為了與段選動態(tài)掃描,保持同步,此時位選應(yīng)該打一拍再賦給位選信號 seg_sel always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin sel_ff0 <= 0; end else if(add_sel)begin sel_ff0 <= sel; end end always@(posedge clk or negedge rst_n)begin if(rst_n==1'b0)begin//初始值為0,全部數(shù)碼管被點亮; seg_sel <= {{SEG_NUM}{1'b0}}; end else if(add_sel_r)begin//將1右移sel_ff0位之后取反,seg_sel的第sel_ff0輸出低電平,對應(yīng)的第sel_ff0個數(shù)碼管被點亮了,其余位輸出高電平,對應(yīng)的數(shù)碼管熄滅; seg_sel <= ~({1'b1,{{SEG_NUM-1}{1'b0}}} >> sel_ff0);//~(6'h1<
該模塊仿真比較簡單,如下圖所示,需要顯示的數(shù)據(jù)為32’h00000234,由于底板上位選最低位對應(yīng)的是最左邊的數(shù)碼管,第1個十進(jìn)制數(shù)需要與位選的最高位對齊,第8個十進(jìn)制數(shù)需要與位選的最低位對齊,仿真結(jié)果如下所示。
位選信號為8’h7f是,此時數(shù)據(jù)段應(yīng)該輸出8’h66,數(shù)碼管顯示4這個數(shù)字。
圖14 數(shù)碼管刷新模塊仿真
0974H595模塊驅(qū)動
數(shù)碼管的驅(qū)動電路如下所示,通過兩片74HC595驅(qū)動8個數(shù)碼管,由于篇幅問題,本文只對該模塊進(jìn)行仿真。
圖15 數(shù)碼管驅(qū)動電路
信號端口列表如下所示。
表8 74HC595驅(qū)動模塊信號列表
信號名 I/O 位寬 含義 clk I 1 系統(tǒng)時鐘頻率,默認(rèn)12MHz。 rst_n I 1 系統(tǒng)復(fù)位信號,低電平有效。 segment I 8 數(shù)碼管的數(shù)據(jù)線 seg_sel I 8 數(shù)碼管位選信號 dout_vld I 1 數(shù)碼管刷新指示信號 ds O 1 74hc595芯片串行數(shù)據(jù)。 sclk O 1 74hc595芯片移位寄存器時鐘信號。 rclk O 1 74hc595芯片鎖存器時鐘信號。 該模塊整體仿真結(jié)果如下所示:
圖16 整體仿真
該模塊細(xì)節(jié)仿真如下所示:
圖17 細(xì)節(jié)仿真
10上板實測
上述仿真了各個模塊,下文在開發(fā)板上對這個工程進(jìn)行實測。
首先驗證正常的加減乘除運(yùn)算,如下視頻所示,每次運(yùn)算后使用清除按鍵清零運(yùn)算結(jié)果,然后開始下次運(yùn)算。
下面是進(jìn)行連續(xù)運(yùn)算的演示,使用該計算器實現(xiàn)以下運(yùn)算((3320 + 2551) - 771) * 7 / 9,得到計算結(jié)果為3966,與真實結(jié)果一致。
同時在輸入運(yùn)算符時,如果運(yùn)算符輸入錯誤,則可以再次輸入運(yùn)算符,以最后輸入的運(yùn)算符為準(zhǔn),之后輸入第二個操作數(shù),與一般的計算器機(jī)制一致。
來源:本文轉(zhuǎn)載自數(shù)字站公眾號
-
FPGA
+關(guān)注
關(guān)注
1625文章
21636瀏覽量
601314 -
矩陣鍵盤
+關(guān)注
關(guān)注
7文章
206瀏覽量
31415 -
計算器
+關(guān)注
關(guān)注
16文章
437瀏覽量
37256 -
運(yùn)算符
+關(guān)注
關(guān)注
0文章
171瀏覽量
11052
原文標(biāo)題:基于FPGA的計算器
文章出處:【微信號:FPGA研究院,微信公眾號:FPGA研究院】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論