話不多說先上圖
前言
自從學習FPGA以來,唯一做過的完整系統就是基於basys2得多功能數字表。記得當時做的時候也沒少頭疼,最后用時間磨出來了一個不是很完整的小系統,當時還是產生了滿滿的成就感。現在回頭看來,先不說功能實現的如何,首先代碼書寫滿是不規范,其中犯得最多的一個問題就是把verilog當C來寫。所以,我決定趁着寒假自由支配的時間比較多,決定重寫多功能數字時鍾,算是對我大二第一學期以來對verilog的學習做一個總結。
首先,重寫后的工程不僅在功能上做了一些優化,而且占用片內的資源也相對來說少了一些。話不多說先上圖。
原來工程的資源占用情況
重寫后資源占用情況(少了一點點,呵呵)
一、摘要
多功能數字表,有數字鍾、秒表(有暫停清零功能)、鬧鍾(可設置)、外設蜂鳴器(當鬧鍾達到設定時間的時候蜂鳴器響應)。第一次寫的代碼主要是代碼書寫不規范,可讀性和可移植性不強,一個always塊里經常給多個寄存器賦值,或者多個if...else語句嵌套,這次重寫在占用資源上得到優化,功能更加完善。
二、設計思想
主要有七個模塊構成:數字鍾計數、秒表計數、按鍵驅動、數碼管顯示、蜂鳴器、鬧鍾設置、中央數據處理中心。下面是rtl原理圖。在設計鬧鍾、時鍾、以及秒表共享數碼管顯示模塊的時候,由於每個數碼管是單獨顯示的所以鬧鍾、時鍾、秒表設置模塊的一共12個輸出我還沒有想到更好的辦法處理,就專門寫了一個cpu模塊將各個功能模塊對應使用撥碼開關什么時候數碼管應該顯示的信號。感覺這種寫法cpu模塊的連線有點多。所以以后有機會再回頭看看怎么優化一下。
在寫時鍾計數器時為了使時鍾信號的准確,我采用數據流型信號,來作為時間的進位出發標志,代碼如下,這種寫法,即可以節省寄存器資源占用,也可以在將信號作為邊沿檢測使用時避免時鍾偏移(skew)。

1 parameter TIME = 26'd49999999; 2 //parameter TIME = 26'd49;//仿真專用 3 4 reg [25:0] cnt;//分頻一秒時鍾信號計數器 5 reg [5:0] cnt_s;//秒計數器 6 wire flag_second;//秒59 7 wire flag_minute_one;//秒59,分個9 8 wire flag_minute_ten;//秒59,分個9,分十5 9 wire flag_hour_one1;//秒59,分個9,分十5,時個9 10 wire flag_hour_one2;//秒59,分個9,分十5,時個3 11 wire flag_hour_ten;//秒59,分個9,分十5,時個3,時十2 12 13 //分頻數一秒信號 14 always @(posedge mclk or negedge rst_n) 15 begin 16 if(!rst_n) 17 cnt <= 26'b0; 18 else if(cnt == TIME) 19 cnt <= 26'b0; 20 else 21 cnt <= cnt + 1'b1; 22 end 23 24 //秒計數 25 always @(posedge mclk or negedge rst_n) 26 begin 27 if(!rst_n) 28 cnt_s <= 6'b0; 29 else if(cnt_s == 59 && cnt == TIME) 30 cnt_s <= 6'b0; 31 else if(cnt == TIME) 32 cnt_s <= cnt_s + 1'b1; 33 else 34 cnt_s <= cnt_s; 35 end 36 assign flag_second = (cnt_s == 59 && cnt == TIME)? 1'b1:1'b0; 37 38 //分鍾個位計數 39 always @(posedge mclk or negedge rst_n) 40 begin 41 if(!rst_n) 42 minute_one <= 4'b0; 43 else if(minute_one == 9 && flag_second) 44 minute_one <= 4'b0; 45 else if(flag_second || key_en[0] && !clock) 46 minute_one <= minute_one + 1'b1; 47 else 48 minute_one <= minute_one; 49 end 50 assign flag_minute_one = (minute_one == 9 && flag_second)?1'b1:1'b0; 51 52 //分鍾十位計數 53 always @(posedge mclk or negedge rst_n) 54 begin 55 if(!rst_n) 56 minute_ten <= 3'b0; 57 else if((minute_ten == 5 && flag_minute_one) || minute_ten == 6) 58 minute_ten <= 3'b0; 59 else if(flag_minute_one || key_en[1] && !clock) 60 minute_ten <= minute_ten + 1'b1; 61 else 62 minute_ten <= minute_ten; 63 end 64 assign flag_minute_ten = (minute_ten == 5 && flag_minute_one)?1'b1:1'b0; 65 66 //小時的個位計數 67 always @(posedge mclk or negedge rst_n) 68 begin 69 if(!rst_n) 70 hour_one <= 4'b0; 71 else if(hour_one == 9 && flag_minute_ten || flag_hour_ten) 72 hour_one <= 4'b0; 73 else if(flag_minute_ten || key_en[2] && !clock) 74 hour_one <= hour_one + 1'b1; 75 else 76 hour_one <= hour_one; 77 end 78 assign flag_hour_one1 = (hour_one == 9 && flag_minute_ten)?1'b1:1'b0; 79 assign flag_hour_one2 = (hour_one == 3 && flag_minute_ten)?1'b1:1'b0; 80 81 //小時的十位計數 82 always @(posedge mclk or negedge rst_n) 83 begin 84 if(!rst_n) 85 hour_ten <= 3'b0; 86 else if(hour_ten == 2 && flag_hour_one2) 87 hour_ten <= 3'b0; 88 else if(flag_hour_one1 || key_en[3] && !clock) 89 hour_ten <= hour_ten + 1'b1; 90 else 91 hour_ten <= hour_ten; 92 end 93 assign flag_hour_ten = (hour_ten == 2 && flag_hour_one2)?1'b1:1'b0;
按鍵消抖模塊,我采用的是軟件消抖,當按鍵按下后有一段不穩定時期,所以在檢測到按鍵按下后計數器開始計數,當計數器記到一定數值,按鍵才會響應,當按鍵松開,計數器停止計數,所以在可以將那一段不穩定時期避免開。代碼如下,可以根據所需要的按鍵個數自定義位寬!

1 parameter DURATION = 600; 2 3 reg[10:0] cnt; 4 5 always @(posedge mclk or negedge rst_n) 6 begin 7 if(!rst_n) 8 cnt <= 11'b0; 9 else if(key[0] == 1)begin 10 if(cnt == DURATION) 11 cnt <= cnt; 12 else 13 cnt <= cnt + 1'b1; 14 end 15 else 16 cnt <= 11'b0; 17 end 18 assign key_en[0] = (cnt == DURATION -1)?1'b1:1'b0;
蜂鳴器模塊,當時鍾計數到的時間與預先設置的時間相同時,蜂鳴器響應,發出救護車的聲音。蜂鳴器驅動救護之音方法與呼吸燈類似,使用pwm信號脈寬調制,當pwm信號越來越高占空比高的時候,蜂鳴器聲音越來越大,反之蜂鳴器聲音越來越小。本例采用的無源蜂鳴器(有源蜂鳴器只需要通電便可直接響應),代碼才考如下:

1 module beep( 2 input mclk, 3 input rst_n, 4 input ring, 5 //時鍾 6 input [2:0] a1, 7 input [3:0] a2, 8 input [2:0] a3, 9 input [3:0] a4, 10 //鬧鍾 , 11 input [2:0] b1, 12 input [3:0] b2, 13 input [2:0] b3, 14 input [3:0] b4, 15 output reg beep 16 ); 17 18 parameter H_s = 25'd249999, 19 L_s = 25'd31249; 20 21 reg [24:0] cnt_T; 22 reg [15:0] cnt; 23 reg flag;//鬧鍾標志 24 25 always @(posedge mclk or negedge rst_n) 26 begin 27 if(!rst_n) 28 flag <= 1'b0; 29 else if(a1 == b1 && a2 == b2 && a3 == b3 && a4 == b4) 30 flag <= 1'b1; 31 else 32 flag <= 1'b0; 33 end 34 35 always @(posedge mclk or negedge rst_n) 36 begin 37 if(!rst_n) 38 cnt_T <= 25'b0; 39 else 40 cnt_T <= cnt_T + 1'b1; 41 end 42 43 always @(posedge mclk or negedge rst_n) 44 begin 45 if(!rst_n) 46 begin 47 cnt <= L_s; 48 beep <= 1'b0; 49 end 50 else if(cnt == 0 && flag && ring) 51 begin 52 cnt <= cnt_T[24]?H_s:L_s; 53 beep <= ~beep; 54 end 55 else 56 cnt <= cnt - 1'b1; 57 end 58 59 endmodule
最后附上數碼管顯示模塊,該模塊沒有復雜的地方會寫譯碼器就應該能看懂

1 module seven_seg_display( 2 input mclk, 3 input rst_n, 4 input [2:0] hour_ten, 5 input [3:0] hour_one, 6 input [2:0] minute_ten, 7 input [3:0] minute_one, 8 output reg [7:0] out, 9 output reg [3:0] an//所有的數碼管的使能端 10 ); 11 12 wire [3:0] aen;//數碼管使能信號 13 reg [1:0] s;//數碼管顯示選擇 14 reg [18:0] cnt;//數碼管掃描時鍾計數 15 16 parameter CLK190 = 18'd263157; 17 18 19 always @(posedge mclk or negedge rst_n) 20 begin 21 if(!rst_n) 22 cnt <= 18'b0; 23 else if(cnt == CLK190 - 1'b1) 24 cnt <= 18'b0; 25 else 26 cnt <= cnt + 1'b1; 27 end 28 29 //燈的狀態沒5.2毫秒刷新一次 30 always @(posedge mclk or negedge rst_n) 31 begin 32 if(!rst_n) 33 s <= 2'b0; 34 else if(cnt == CLK190 - 1'b1) 35 s <= s + 1'b1; 36 end 37 38 assign aen = 4'b1111; 39 always @(*) 40 begin 41 an <= 4'b1111; 42 if(aen[s] == 1) 43 an[s] <= 1'b0; 44 end 45 46 reg [7:0] out0; 47 //小時的十位顯示 48 always @(posedge mclk or negedge rst_n) 49 begin 50 if(!rst_n) 51 out0 <= 8'b0000_0011; 52 else 53 begin 54 case(hour_ten) 55 0: out0 <= 8'b0000_0011; 56 1: out0 <= 8'b1001_1111; 57 2: out0 <= 8'b0010_0101; 58 default: out0 <= 8'b0000_0011; 59 endcase 60 end 61 end 62 63 reg [7:0] out1; 64 //小時的個位顯示 65 always @(posedge mclk or negedge rst_n) 66 begin 67 if(!rst_n) 68 out1 <= 8'b0000_0010; 69 else 70 begin 71 case(hour_one) 72 0: out1 <= 8'b0000_0010; 73 1: out1 <= 8'b1001_1110; 74 2: out1 <= 8'b0010_0100; 75 3: out1 <= 8'b0000_1100; 76 4: out1 <= 8'b1001_1000; 77 5: out1 <= 8'b0100_1000; 78 6: out1 <= 8'b0100_0000; 79 7: out1 <= 8'b0001_1110; 80 8: out1 <= 8'b0000_0000; 81 9: out1 <= 8'b0000_1000; 82 default: out1 <= 8'b0000_0010; 83 endcase 84 end 85 end 86 87 reg [7:0] out2; 88 //分鍾的十位顯示 89 always @(posedge mclk or negedge rst_n) 90 begin 91 if(!rst_n) 92 out2 <= 8'b0000_0011; 93 else 94 begin 95 case(minute_ten) 96 0: out2 <= 8'b0000_0011; 97 1: out2 <= 8'b1001_1111; 98 2: out2 <= 8'b0010_0101; 99 3: out2 <= 8'b0000_1101; 100 4: out2 <= 8'b1001_1001; 101 5: out2 <= 8'b0100_1001; 102 6: out2 <= 8'b0100_0001; 103 7: out2 <= 8'b0001_1111; 104 8: out2 <= 8'b0000_0001; 105 9: out2 <= 8'b0000_1001; 106 default: out2 <= 8'b0000_0011; 107 endcase 108 end 109 end 110 111 reg [7:0] out3; 112 //分鍾的個位顯示 113 always @(posedge mclk or negedge rst_n) 114 begin 115 if(!rst_n) 116 out3 <= 8'b0000_0011; 117 else 118 begin 119 case(minute_one) 120 0: out3 <= 8'b0000_0011; 121 1: out3 <= 8'b1001_1111; 122 2: out3 <= 8'b0010_0101; 123 3: out3 <= 8'b0000_1101; 124 4: out3 <= 8'b1001_1001; 125 5: out3 <= 8'b0100_1001; 126 6: out3 <= 8'b0100_0001; 127 7: out3 <= 8'b0001_1111; 128 8: out3 <= 8'b0000_0001; 129 9: out3 <= 8'b0000_1001; 130 default: out3 <= 8'b0000_0011; 131 endcase 132 end 133 end 134 135 always @(posedge mclk or negedge rst_n) 136 begin 137 if(!rst_n) 138 out <= 8'b0000_0011; 139 else 140 case(s) 141 0: out <= out3; 142 1: out <= out2; 143 2: out <= out1; 144 3: out <= out0; 145 default: out <= 8'b0000_0011; 146 endcase 147 end 148 149 endmodule
-----------------------------------------我是華麗的分隔線----------------------------------------------------------------
今天無意之中看到了另一種設置多功能數字表的思想,廢話少說,下面做以筆記。
我原來的設計,在秒分頻過后,當記到59秒時,將分的個位加1,當分的個位記到9時將分的十位加1,當分的十個位為59時,將時的個位加1,以此類推。我使用的basys2開發板只有四個LED數碼管,所以只能顯示分時,所以有四個輸出端口minute_one,minute_ten,hour_one,hour_ten。完成顯示。
下面我引出另一種設計思路
將秒計數到59,分進行進位,將分計數到59,小時進行進位,最后小時計數到23。然而一個時鍾的個位和十位是分別顯示的,所以還要做一個接口轉換,把2位十進制數的個位和十位分離開。具體方法就是除以10,商是十位的值,余是個位的值。
如圖所示:
三、實驗總結
這里我就我不再次總結了,把以前的實驗報告復制過來看看,現在看起來以前的總結報告,有些寫的真是不堪入目,
-----------------------------------------我是分隔線---------------------------------------------------------------------
工作總結
第二期我們小隊選擇多功能數字時鍾一題,針對basys2開發板使用Verilog HDL語言編譯,完成該工程。題目要求多功能數字中有時鍾(可校時)、秒表(可清零、暫停)功能,我們在原有題目完成的基礎上添加了鬧鍾功能,在完成任務過程中可謂是一路心酸加苦逼,但最后還是終於圓滿完成!下面我將簡述我們完成工作過程中具體遇到的一些問題。
心得與體會
在設計之初,由於心中對整個工程的思考不夠,沒有一個具體的模塊化概念,還只是單單把每個功能當成一個題目去做,導致給后面的工作埋下了定時炸彈。后來在慢慢的進度到后面的時候,才有了將各個模塊聯系起來的思想,在設計一個模塊的時候就必須將其他想加入的模塊提前考慮好,給下一步工程就要留下能加入引用的空間。而且小組合作就必須每個人的代碼風格要類似,每個人寫自己的模塊,要不然最后模塊整合的時候會有很大的麻煩。
在這次任務中我們沒有體現出小組合作的優勢,很傷心的是我們組最開始的兩個組員因為忙雙雙離我而去,他們由於事情安排不開而且上一期的任務還沒有完成,所以前面一半的工程我都獨自一個人完成。記得那個周六我從早上九點多來到實驗室,除了中間吃飯一直坐在這寫代碼,一直寫到晚上十點多才離開,到第二天也是同樣,只不過下午五點多就回去休息了。正是我這兩天沉下心來做才讓我們的任務有了很大的進展。最后我的兩個組員離開,我感到有些傷心,但工程還得繼續,我已經做好獨立完成的准備,還好最后又有兩個人分到我的小隊,這讓我又看到了希望,我的新隊員給了我很多的靈感,所以我決定在原有的題目基礎上加入鬧鍾的功能,但是我獨立完成的那一部分還有一個致命的問題沒有解決,就是硬件開發的基礎問題——按鍵消抖。
從接觸開始我就感覺按鍵消抖比較抽象,再加上對於放在我們這個數字鍾的題目,按鍵不由的跟時鍾扯到了一起,消抖的代碼沒有問題,但是因為這個題目的特殊,消抖后必須和時鍾扯上關系,所以在這個問題上耗費了我很多的時間,上周從周一到周四四天里一下課我就來實驗室,將代碼改了一遍又一遍,改回來又改回去,到周四的時候我已經體會到絕望的感覺。周五終於解決了這個問題。到這里我們的工程就取得了一個大前進。
我其他兩個組員編寫了秒表計數的模塊,然后在周五下午我就立馬將模塊整合,到此便完成了題目的所有要求。鬧鍾功能必須要又可以設置鬧鍾的功能而按鍵只有四個已經用過了,要是再在原來的基礎上改動有可能別的模塊出問題。周日的工作很順利幾乎沒用多少時間,在原有的代碼基礎上增加個模塊就實現了鬧鍾功能,至此我們的任務順利完成。
------------------------------------------------------------------------------
從開始接觸fpga以來已經有6個月了,我的路還是很漫長,就像bingo曾經說的,我沒資格放棄。現在寒假還有不到二十天結束,不知道我又能學多少,我不認為自己勢必比別人聰明的人,但我相信通過自己的努力,會慢慢變得更好。最后獻上一個圖,每天多努力一點點和少努力一點點的差距就是這么大。
轉載請注明出處:NingHeChuan(寧河川)
個人微信訂閱號:NingHeChuan
如果你想及時收到個人撰寫的博文推送,可以掃描左邊二維碼(或者長按識別二維碼)關注個人微信訂閱號
知乎ID:NingHeChuan
微博ID:NingHeChuan