本文為明德揚原創及錄用文章,轉載請註明出處!
1.1 總體設計
1.1.1 概述
在微機的發展初期,BIOS都存放在ROM(只讀存儲器)中。ROM內部的資料是在ROM的製造工序中,在工廠裡用特殊的方法燒錄進去的,其中的內容只能讀不能改,一旦燒錄進去,用戶只能驗證寫入的資料是否正確,不能再做任何修改。如果發現資料有任何錯誤,則只有捨棄不用,重新訂做一份。ROM是在生產線上生產的,由於成本高,一般只用在大批量應用的場合。
由於ROM製造和升級的不便,後來人們發明了PROM(Programmable ROM,可編程ROM)。最初從工廠中製作完成的PROM內部並沒有資料,用戶可以用專用的編程器將自己的資料寫入,但是這種機會只有一次,一旦寫入後也無法修改,若是出了錯誤,已寫入的晶片只能報廢。PROM的特性和ROM相同,但是其成本比ROM高,而且寫入資料的速度比ROM的量產速度要慢,一般只適用於少量需求的場合或是ROM量產前的驗證。
EPROM(Erasable Programmable ROM,可擦除可編程ROM)晶片可重複擦除和寫入,解決了PROM晶片只能寫入一次的弊端。EPROM晶片有一個很明顯的特徵,在其正面的陶瓷封裝上,開有一個玻璃窗口,透過該窗口,可以看到其內部的集成電路,紫外線透過該孔照射內部晶片就可以擦除其內的數據,完成晶片擦除的操作要用到EPROM擦除器。EPROM內資料的寫入要用專用的編程器,並且往晶片中寫內容時必須要加一定的編程電壓(VPP=12~24V,隨不同的晶片型號而定)。EPROM的型號是以27開頭的,如27C020(8*256K)是一片2M Bits容量的EPROM晶片。EPROM晶片在寫入資料後,還要以不透光的貼紙或膠布把窗口封住,以免受到周圍的紫外線照射而使資料受損。
由於EPROM操作的不便,後來出的主板上BIOS ROM晶片大部分都採用EEPROM(Electrically Erasable Programmable ROM,電可擦除可編程ROM)。EEPROM的擦除不需要藉助於其它設備,它是以電子信號來修改其內容的,而且是以Byte為最小修改單位,不必將資料全部洗掉才能寫入,徹底擺脫了EPROM Eraser和編程器的束縛。EEPROM在寫入數據時,仍要利用一定的編程電壓,此時,只需用廠商提供的專用刷新程序就可以輕而易舉地改寫內容,所以,它屬於雙電壓晶片。藉助於EEPROM晶片的雙電壓特性,可以使BIOS具有良好的防毒功能,在升級時,把跳線開關打至「on」的位置,即給晶片加上相應的編程電壓,就可以方便地升級;平時使用時,則把跳線開關打至「off」的位置,防止CIH類的病毒對BIOS晶片的非法修改。所以,仍有不少主板採用EEPROM作為BIOS晶片並作為自己主板的一大特色。
1.1.2 設計目標
整個工程由FPGA、矩陣鍵盤/按鍵、數碼管和AT93C46組成,實現一個上電後能重新加載,接著上次計數的數字時鐘,詳細功能如下。
1、 數碼管顯示時鐘值,共使用了6個數碼管,分別表示時十位、時個位、分十位、分個位、秒十位和秒個位。
2、 矩陣鍵盤或者按鍵可以對數字時鐘進行時分秒的設置。
A、 上電後,時鐘默認處於計時狀態,當按鍵1按下,跳到時間設置狀態,當按鍵1再次按下,回到計時狀態。
B、 當處於時間設置狀態時,默認此刻設置的是秒個位,當按鍵2按下,此刻設置秒十位,以此類推,一次設置為分個位、分十位、時個位和時十位。再按下按鍵2,則重新設置秒個位。
C、 當處於時間設置狀態時,按下按鍵3,則設置位的值加1,如果溢出,則變成0。例如當目前小時顯示05時,設置時十位,按下按鍵3,變成15,再按下按鍵3,則變成05.當目前小時顯示為03時,設置時十位,按一下按鍵3,變成13,再按一下按鍵3,則變成23,再按則為03。
3、 AT93C46則用於保存時鐘值,其具有斷電保護功能,斷電數據不丟失。
A、 AT93C46一共可以保存128位元組的數據。工程將AT93C46分成空間1和空間2。空間1佔用的地址為0~3,空間2佔用的地址為4~7。
B、 每隔1秒,保存當前時鐘值。第一次保存到空間1,第二次保存到空間2,第三次保存帶空間1,依此類推。(如果只有一個空間,則可能出現寫數據過程中斷電,從而得不到完整數據情況)
C、 支持8位的CRC,生成多項式
,初始值為全1。
D、 每次保存的值,時十位、時個位、分十位、分個位、秒十位和秒個位各佔4bit,共3個字節,加上1個字節的CRC,一共4個字節。
E、 上電後,FPGA將讀取兩個空間的數值,並作CRC檢驗。如果兩組數據的CRC檢驗均失敗,則不重新加載;如果有一組數據CRC檢驗失敗,則加載正確的一組數據;如果兩組數據CRC檢驗均正確,則加載數值較大的一組數據。
1.1.3 系統結構框圖
系統結構框圖如下所示:
圖一
1.1.4模塊功能
鍵盤(按鍵)掃描模塊實現功能
1、將外來異步信號打兩拍處理,將異步信號同步化。
2、實現20ms按鍵消抖功能。
3、實現矩陣鍵盤或者普通案件的檢測功能,並輸出有效按鍵信號。
時鐘數據產生模塊實現功能
負責產生數字時鐘需要的時鐘信息。包括:
1、 按數字時鐘方式進行計數
2、 設置數字時鐘的值
3、 將時鐘數據輸出給外部模塊使用
4、 從數據處理模塊得到時鐘數據,並重新加載
數碼管顯示模塊實現功能
1、 對時鐘數據進行解碼,然後發到數碼管顯示
2、 逐一顯示時分秒的值
數據處理模塊實現功能
負責寫到AT93C46的數據,或者從AT93C46讀到數據後的處理,包括:
1、 上電後,發送EWEN命令,打開AT93C46的防寫。
2、 發送EWEN命令後,開始讀存儲在AT93C46的兩組時鐘數據;對數據進行檢驗,然後選擇適合的數據給時鐘數據產生模塊加載
3、 每隔1秒從時鐘數據產生模塊獲取時分秒的值,並產生CRC值,最後寫到AT93C46上
CRC處理模塊實現功能
負責CRC算法的模塊,在數據處理模塊內部使用
AT93C46模塊實現功能
根據上遊模塊的EWEN、WRITE和READ命令,產生AT93C46的相應時序,從而寫數據或者讀到數據。至於數據是什麼、有什麼用,不關心,只關心AT93C46的時序。
1.1.5頂層信號
1.1.6參考代碼
`define KEY_SCANmodule at93c46_top_scan( clk , rst_n , key_col , mo , cs , mi , sk , key_row , seg_sel , seg_data ); parameter TIME_1S = 50_000_000; input clk ; input rst_n ; input [3:0] key_col ; input mo ; output cs ; output mi ; output sk ; output[3:0] key_row ; output[5:0] seg_sel ; output[7:0] seg_data ; wire rdy ; wire rdata_vld ; wire [3:0] key_en ; wire [23:0] data_load ; wire data_load_vld; wire [23:0] clk_data_out ; wire [6:0] addr ; wire [1:0] mode ; wire start ; wire [7:0] wdata ; wire [7:0] rdata ; `ifdef KEY_SCAN key_scan u_key_scan( .clk (clk ), .rst_n (rst_n ), .key_col (key_col), .key_row (key_row), .key_en (key_en ) ); `else key_module u_key_module( .clk (clk ), .rst_n (rst_n ), .key_in (~key_col), .key_vld (key_en ) ); `endif clock_data(.SEG_NUM(6)) u_seg_disp( .rst_n (rst_n ), .clk (clk ), .din (clk_data_out), .din_vld ({6{1&(.TIME_1S(TIME_1S)) u_data_pro( .clk (clk ) , .rst_n (rst_n ) , .din (clk_data_out) , .start (start ) , .mode (mode ) , .addr (addr ) , .wdata (wdata ) , .rdata (rdata ) , .rdata_vld (rdata_vld ) , .rdy (rdy ) , .dout (data_load ) , .dout_vld (data_load_vld ) ); at93c46_mix u_at93c46_mix( .clk (clk ) , .rst_n (rst_n ) , .start (start ) , .mode (mode ) , .addr (addr ) , .wdata (wdata ) , .rdata (rdata ) , .rdata_vld (rdata_vld ) , .rdy (rdy ) , .do (mo ) , .di (mi ) , .cs (cs ) , .sk (sk ) );endmodule
本工程會應用於不同的開發板,主要區別在於使用普通按鍵還是矩陣鍵盤,頂層代碼中針對這一點進行了設計,如何開發板使用的是矩陣鍵盤,則頂層代碼不需要改,如果使用的是普通按鍵,只需要將頂層代碼最上面的一行刪除或者注釋掉就可以了。
1.2 鍵盤(按鍵)掃描模塊設計
1.2.1接口信號
下面為使用矩陣鍵盤時的接口信號:
下面是使用普通按鍵時的接口信號:
1.2.2 設計思路
在前面的按鍵控制數字時鐘的案例中已經有介紹,所以這裡不在過多介紹,詳細介紹請看下方連結:
http://fpgabbs.com/forum.php?mod=viewthread&tid=310
1.2.3參考代碼
module key_scan( clk , rst_n , key_col, key_row, key_en ); parameter KEY_W = 4 ; parameter CHK_COL = 0 ; parameter CHK_ROW = 1 ; parameter DELAY = 2 ; parameter WAIT_END = 3 ; parameter COL_CNT = 16 ; parameter TIME_20MS= 1000000; input clk ; input rst_n ; input [3:0] key_col ; output[3:0] key_en ; output[KEY_W-1:0] key_row ; reg [3:0] key_out ; reg [KEY_W-1:0] key_row ; reg key_vld ; reg [3:0] key_col_ff0 ; reg [3:0] key_col_ff1 ; reg [1:0] key_col_get ; reg [3:0] key_en ; wire end_shake_cnt ; reg end_shake_cnt_ff0; reg [3:0] state_c ; reg [19:0] shake_cnt ; reg [3:0] state_n ; reg [1:0] row_index ; reg [15:0] row_cnt ; wire col2row_start ; wire row2del_start ; wire del2wait_start ; wire wait2col_start ; wire add_row_cnt ; wire end_row_cnt ; wire add_shake_cnt ; wire add_row_index ; wire end_row_index ;always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b1111; key_col_ff1 <= 4&39;hf;assign end_shake_cnt = add_shake_cnt && shake_cnt == TIME_20MS-1 ;always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;hf;always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b0; end else if(state_c==CHK_ROW)begin key_row <= ~(1&39;b0; endendalways @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_index <= 0; end else if(add_row_index) begin if(end_row_index) row_index <= 0; else row_index <= row_index+1 ; end else if(state_c!=CHK_ROW)begin row_index <= 0; endendassign add_row_index = state_c==CHK_ROW && end_row_cnt;assign end_row_index = add_row_index && row_index == 4-1 ;always @(posedge clk or negedge rst_n) begin if (rst_n==0) begin row_cnt <= 0; end else if(add_row_cnt) begin if(end_row_cnt) row_cnt <= 0; else row_cnt <= row_cnt+1 ; endendassign add_row_cnt = state_c==CHK_ROW || state_c==DELAY;assign end_row_cnt = add_row_cnt && row_cnt == 16-1 ;always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b1110) key_col_get <= 0; else if(key_col_ff1==4&39;b1011) key_col_get <= 2; else key_col_get <= 3; endendalways @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b0)begin key_vld <= 1&39;b0)begin key_vld <= 1&39;b0; endendalways @(*)begin if(rst_n==1&39;b0001; end else if(key_vld && key_out==1)begin key_en = 4&39;b0100; end else begin key_en = 0; endend
1.3 時間數據產生模塊設計
1.3.1接口信號
1.3.2設計思路
本模塊相對於前面的按鍵控制數字時鐘案例中的時間數據產生模塊來說,總體的設計思路是相同的,只是增加了一個重載的時鐘信號,對於此信號的設計也比較簡單,只需要在時分秒的個位和十位計數器中增加一句:在重載的時鐘數據有效的時候,使計數器輸出重載的時鐘對應的數據即可,比如秒個位計數器應該輸出重載時鐘數據的第0到第3位數據,秒十位計數器應該輸出重載時鐘數據的第4到第7位數據,以此類推。
其他詳細的設計思路可以看一下往期按鍵控制數字時鐘的文章:
1.3.3參考代碼
module clock_data( clk , rst_n , data_load , data_load_vld, key_en , data_out ); parameter TIME_1S = 50_000_000 ; input clk ; input rst_n ; input data_load_vld; input [23:0] data_load ; input [ 3:0] key_en ; output[23:0] data_out ; wire [23:0] data_out ; reg [25:0] cnt_1s ; reg [3:0] miao_ge ; reg [3:0] miao_shi ; reg [3:0] fen_ge ; reg [3:0] fen_shi ; reg [3:0] shi_ge ; reg [3:0] shi_shi ; reg [2:0] set_sel ; reg set_flag ; wire add_set_sel ; wire add_cnt_1s ; wire add_miao_ge ; wire add_miao_shi ; wire add_fen_ge ; wire add_fen_shi ; wire add_shi_ge ; wire add_shi_shi ; wire end_cnt_1s ; wire end_set_sel ; wire end_miao_ge ; wire end_miao_shi ; wire end_fen_ge ; wire end_fen_shi ; wire end_shi_ge ; wire end_shi_shi ; wire set_miao_ge ; wire set_miao_shi ; wire set_fen_ge ; wire set_fen_shi ; wire set_shi_ge ; wire set_shi_shi ; reg [ 3:0] x ; reg [ 2:0] y ; always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b0; end else if(key_en[0]) begin set_flag <= ~ set_flag; end end always @(posedge clk or negedge rst_n)begin if(!rst_n)begin set_sel <= 0; end else if(add_set_sel)begin if(end_set_sel) set_sel <= 0; else set_sel <= set_sel + 1; end else if(set_flag==0)begin set_sel <= 0; end end assign add_set_sel = set_flag && key_en[1]; assign end_set_sel = add_set_sel && set_sel==6-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_1s <= 0; end else if(add_cnt_1s)begin if(end_cnt_1s) cnt_1s <= 0; else cnt_1s <= cnt_1s + 1; end end assign add_cnt_1s = set_flag==0 ; assign end_cnt_1s = add_cnt_1s && cnt_1s==TIME_1S-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin miao_ge <= 0; end else if(add_miao_ge)begin if(end_miao_ge) miao_ge <= 0; else miao_ge <= miao_ge + 1; end else if(data_load_vld)begin miao_ge <= data_load[3:0]; end end assign add_miao_ge = (end_cnt_1s || set_miao_ge) ; assign end_miao_ge = add_miao_ge && miao_ge==10-1 ; assign set_miao_ge = set_flag && set_sel==0 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin miao_shi <= 0; end else if(add_miao_shi)begin if(end_miao_shi) miao_shi <= 0; else miao_shi <= miao_shi + 1; end else if(data_load_vld)begin miao_shi <= data_load[7:4]; end end assign add_miao_shi = (end_miao_ge || set_miao_shi); assign end_miao_shi = add_miao_shi && miao_shi==6-1; assign set_miao_shi = set_flag && set_sel==1 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin fen_ge <= 0; end else if(add_fen_ge)begin if(end_fen_ge) fen_ge <= 0; else fen_ge <= fen_ge + 1; end else if(data_load_vld)begin fen_ge <= data_load[11:8]; end end assign add_fen_ge = (end_miao_shi || set_fen_ge); assign end_fen_ge = add_fen_ge && fen_ge==10-1; assign set_fen_ge = set_flag && set_sel==2 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin fen_shi <= 0; end else if(add_fen_shi)begin if(end_fen_shi) fen_shi <= 0; else fen_shi <= fen_shi + 1; end else if(data_load_vld)begin fen_shi <= data_load[15:12]; end end assign add_fen_shi = (end_fen_ge || set_fen_shi); assign end_fen_shi = add_fen_shi && fen_shi==6-1; assign set_fen_shi = set_flag && set_sel==3 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin shi_ge <= 0; end else if(add_shi_ge)begin if(end_shi_ge) shi_ge <= 0; else shi_ge <= shi_ge + 1; end else if(data_load_vld)begin shi_ge <= data_load[19:16]; end end assign add_shi_ge = (end_fen_shi || set_shi_ge); assign end_shi_ge = add_shi_ge && shi_ge==x-1; assign set_shi_ge = set_flag && set_sel==4 && key_en[2]; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin shi_shi <= 0; end else if(add_shi_shi)begin if(end_shi_shi) shi_shi <= 0; else shi_shi <= shi_shi + 1; end else if(data_load_vld)begin shi_shi <= data_load[23:20]; end end assign add_shi_shi = (end_shi_ge || set_shi_shi); assign end_shi_shi = add_shi_shi && shi_shi==y-1; assign set_shi_shi = set_flag && set_sel==5 && key_en[2]; always @(*)begin if(shi_shi<2) x = 10; else x = 4; end always @(*)begin if(set_flag && set_sel==5 && shi_ge>=4) y = 2; else y = 3; end assign data_out = {shi_shi,shi_ge,fen_shi,fen_ge,miao_shi,miao_ge};endmodule
1.4 數碼管顯示模塊設計
1.4.1接口信號
1.4.2設計思路
數碼管顯示在前面的案例文章已經有講述,這裡不再進行介紹,想了解的可以看一下往期文章:
1.4.3參考代碼
always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_2ms <= 0; end else if(add_cnt_2ms)begin if(end_cnt_2ms) cnt_2ms <= 0; else cnt_2ms <= cnt_2ms + 1; endendassign add_cnt_2ms = 1; assign end_cnt_2ms = add_cnt_2ms && cnt_2ms==TIME_2MS-1 ; always @(posedge clk or negedge rst_n)begin if(!rst_n)begin cnt_sel <= 0; end else if(add_cnt_sel)begin if(end_cnt_sel) cnt_sel <= 0; else cnt_sel <= cnt_sel + 1; endendassign add_cnt_sel = end_cnt_2ms; assign end_cnt_sel = add_cnt_sel && cnt_sel== SEG_NUM-1; always @(posedge clk or negedge rst_n)begin if(rst_n==1&39;b1}}; end else begin seg_sel <= ~(1&39;b0)begin din_ff0 <= 0; end else begin for(ii=0;ii<SEG_NUM;ii=ii+1)begin if(din_vld[ii]==1&39;b0)begin segment<=NUM_0; end else begin case(seg_tmp) 4&39;d1:segment <= NUM_1; 4&39;d3:segment <= NUM_3; 4&39;d5:segment <= NUM_5; 4&39;d7:segment <= NUM_7; 4&39;d9:segment <= NUM_9; default:begin segment <= NUM_ERR; end endcase endendendmodul
由於篇幅限制,請繼續看(二)。