實驗六:數碼管模塊
有關數碼管的驅動,想必讀者已經學爛了 ... 不過,作為學習的新儀式,再爛的東西也要溫故知新,不然學習就會不健全。黑金開發板上的數碼管資源,由始至終都沒有改變過,筆者因此由身懷念。為了點亮多位數碼管從而顯示數字,一般都會采用動態掃描,然而有關動態掃描的信息請怒筆者不再重復。在此,同樣也是動態掃描,但我們卻用不同的思路去理解。
圖6.1 6位數碼管。
如圖6.1所示,哪里有一排6位數碼管,其中包好8位DIG信號還有6位SEL信號。DIG為digit,即俗稱的數碼管碼,如果數碼管預要顯示“A”,那么DIG必須輸入“A”的數碼管碼。SEL為select,即俗稱的位選,從左至右即SEL[0]~SEL[5],如果想要使能第一位最左邊的數碼管,SEL[0]必須設置有效位。不管DIG還是SEL,黑金開發板都是拉低有效,亦即0值表示有效位。
圖6.2 信號DIG與數碼管碼。
DIG信號位寬為8,亦即一個數碼管資源內藏8個LED,8位DIG信號分別表示各個LED,結果如圖6.2所示。除了DIG[7]較為少用以外,DIG[0]~[6] 一般都用來顯示十六進制的數字 0~F。筆者在此強調,黑金開發板所采用的數碼管是拉低有效,亦即點亮LED筆者設置為0。為此,十六進制的數字0~F可以用Verilog這樣表示,如代碼6.1所示:
1. parameter _0 = 8'b1100_0000, _1 = 8'b1111_1001, _2 = 8'b1010_0100,
2. _3 = 8'b1011_0000, _4 = 8'b1001_1001, _5 = 8'b1001_0010,
3. _6 = 8'b1000_0010, _7 = 8'b1111_1000, _8 = 8'b1000_0000,
4. _9 = 8'b1001_0000, _A = 8'b1000_1000, _B = 8'b1000_0011,
5. _C = 8'b1100_0110, _D = 8'b1010_0001, _E = 8'b1000_0110,
6. _F = 8'b1000_1110;
代碼6.1
如代碼6.1所示,筆者用常量聲明16個16進制的數字。
圖6.3 16進制數字的數碼管碼(拉低有效)。
為了方便讀者,筆者也順便繪制一張直觀的示意圖 ... 如圖6.3所示,哪里有16個16進制數字以及相關的數碼管碼。理解DIG信號與數碼管碼的關系以后,接下來筆者會解釋SEL信號與數碼管的關系。
圖6.4 信號SEL與數碼管碼。
如圖6.4所示,我們可以看見SEL信號與數碼管的關系,每當SEL為值不同,相關的數碼管就會顯示數字,例如SEL為值6’b111_110,最左邊的數碼管就會顯示數字;SEL為值6’b011_111,最右邊的數碼管就會顯示數字。如何實現自左向右輪流顯示數字,就是將 6’b111_110 其中的“0值”按間隔向左位移即可,這也是動態掃描最基本的理論。
圖6.5 流水燈的理想時序圖(腦補)。
例如實驗一的流水燈實驗,流水操作負責輪流點亮4位1組的LED資源。假設流水間隔是一個時鍾,如圖6.5所示,LED信號分別在T0~T3之間輸出4’b0001,4’b0010,4’b0100,4’b10000,上述行為重復N次以后便產生流水效果。Verilog 則可以這樣描述,結果如代碼6.2所示:
1. case( i )
2.
3. 0:
4. begin LED <= 4’b0001; i <= i + 1’b1; end
5. 1:
6. begin LED <= 4’b0010; i <= i + 1’b1; end
7. 2:
8. begin LED <= 4’b0100; i <= i + 1’b1; end
9. 3:
10. begin LED <= 4’b1000; i <= 4’d0; end
11.
12. endcase
代碼6.2
如代碼6.2所示, 相較實驗一的內容,步驟0~3也是實現流水效果,不過步驟0~3卻沒有考慮每個步驟所保持的時間,亦即流水間隔僅有一個時鍾而已。
圖6.6 數碼管顯示數字的例子。
相較實驗一的流水燈實驗,動態掃描就是功能稍微復雜一點的流水等而已,SEL信號類似LED信號,不過不是點亮LED而是負責位選工作,換之DIG信號則是顯示內容。
如圖6.6所示,假設筆者想要顯示上述的結果,即自左向右輪流顯示數字0~5,其中DIG信號負責數字0~5等信息,至於數字的顯示次序則是SIG信號負責。
圖6.7 自左向右顯示數字0~5的理想時序圖(腦補)。
假設流水間隔亦然是1個時鍾,為了自左向右輪流顯示數字“012345”,每個時鍾的SEL(位選)信息,必須對應有效的DIG內容(數碼管碼),結果如圖6.6所示。時序發生過程如下:
T0的時候,DIG發送未來值8’b1100_0000,SEL發送未來值6’b111_110;
T1的時候,DIG發送未來值8’b1111_1001,SEL發送未來值6’b111_101;
T2的時候,DIG發送未來值8’b1010_0100,SEL發送未來值6’b111_011;
T3的時候,DIG發送未來值8’b1011_0000,SEL發送未來值6’b110_111;
T4的時候,DIG發送未來值8’b1001_1001,SEL發送未來值6’b101_111;
T5的時候,DIG發送未來值8’b1001_0010,SEL發送未來值6’b011_111;
Verilog則可以這樣表示,如代碼6.3所示:
1. case( i )
2.
3. 0:
4. begin DIG <= 8’b1100_0000; SEL <= 6’b111_110; i <= i + 1’b1; end
5. 1:
6. begin DIG <= 8’b1111_1001; SEL <= 6’b111_101; i <= i + 1’b1; end
7. 2:
8. begin DIG <= 8’b1010_0100; SEL <= 6’b111_011; i <= i + 1’b1; end
9. 3:
10. begin DIG <= 8’b1011_0000; SEL <= 6’b110_111; i <= i + 1’b1; end
11. 4:
12. begin DIG <= 8’b1001_1001; SEL <= 6’b101_111; i <= i + 1’b1; end
13. 5:
14. begin DIG <= 8’b1001_0010; SEL <= 6’b011_111; i <= 4’d0; end
15.
16. endcase
代碼6.3
如代碼6.3所示,步驟0~5分別對應6位數碼管的顯示次序。如步驟0為DIG被賦予8’b1100_0000,即數字0的數碼管碼,期間SEL也被賦予6’b111_110,即點亮第一位數碼管(左邊第一個);步驟1為DIG被賦予8’b1111_1001,即數字1的數碼管碼,期間SEL也被賦予6’b111_101,即點亮第二位數碼管(左邊第二個);至於步驟2~5以此類推,完后便返回步驟0,重復一樣的操作。
流水間隔亦即動態掃描頻率,常規是10ms,不過喪心病狂的筆者卻設置為100us。100us經過50Mhz的時鍾量化以后是 5000,Verilog則可以這樣表示:
parameter T100US = 13'd5000;
如果步驟6.3的流水間隔不是一個時鍾而是100us,那么代碼6.3可以這樣修改,修改結果如代碼6.4所示:
1. case( i )
2.
3. 0:
4. if( C1 == T100US -1 ) begin C1 <= 13’d0; i <= i + 1’b1; end
5. else DIG <= 8’b1100_0000; SEL <= 6’b111_110; end
6. ...
7. 5:
8. if( C1 == T100US -1 ) begin C1 <= 13’d0; i <= i + 1’b1; end
9. else DIG <= 8’b1001_0010; SEL <= 6’b011_111; end
10.
11. endcase
代碼6.4
理解這些內容以后,我們便可以開始建模了 ...
圖6.8 實驗六的建模圖。
如圖6.8所示,那是實驗六的建模圖,其中smg_basemod 是組合模塊,它包含數碼管功能模塊,還有數碼管加碼模塊。接下來,讓我們來分析一下內部情況,數碼管功能模塊它有一組24位的iData,然后又有10位oData。隨后oData[9:6]會經由加碼模塊(即時模塊)成為8位的數碼管信息,並且驅動DIG頂層信號。反之oData[5:0]則會直接驅動SEL頂層信號。
smg_funcmod.v
圖6.9 數碼管功能模塊。
接下來,讓我們獨自分析個體模塊 ... 首先是數碼管功能模塊,人如其名它是負責所有數碼管驅動工作的功能模塊,24位的iData分別針對6位數碼管的顯示內容(數字),位分配如表6.1所示:
表 6.1 輸入數據位分配
| 位分配 |
[23..20] |
[19..16] |
[15..12] |
[11..8] |
[7..4] |
[3..0] |
| 數碼管分配 |
第一位 |
第二位 |
第三位 |
第四位 |
第五位 |
第六位 |
至於oData的作用如上所示,oData[9:6]必須經由數碼管加碼模塊,oData[5:0]則直接驅動SEL頂層信號。詳細內容就讓我們直接窺視代碼吧:
1. module smg_funcmod
2. (
3. input CLOCK, RESET,
4. input [23:0]iData,
5. output [9:0]oData
6. );
7. parameter T100US = 13'd5000;
以上內容為相關出入端聲明。第8行則是100us的常量聲明(流水間隔/停留時間)。
8.
9. reg [3:0]i;
10. reg [12:0]C1;
11. reg [3:0]D1;
12. reg [5:0]D2;
13.
14. always @ ( posedge CLOCK or negedge RESET )
15. if( !RESET )
16. begin
17. i <= 4'd0;
18. C1 <= 13'd0;
19. D1 <= 4'd0;
20. D2 <= 6'b111_110;
21. end
以上內容為相關的寄存器聲明以及復位操作。D1暫存iData的部分數據,D2則暫存位選數據。第17~21則是這些寄存器的復位操作。
22. else
23. case( i )
24.
25. 0:
26. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
27. else begin C1 <= C1 + 1'b1; D1 <= iData[23:20]; D2 <= 6'b111_110; end
28.
29. 1:
30. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
31. else begin C1 <= C1 + 1'b1; D1 <= iData[19:16]; D2 <= 6'b111_101; end
32.
33. 2:
34. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
35. else begin C1 <= C1 + 1'b1; D1 <= iData[15:12]; D2 <= 6'b111_011; end
36.
37. 3:
38. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
39. else begin C1 <= C1 + 1'b1; D1 <= iData[11:8]; D2 <= 6'b110_111; end
40.
41. 4:
42. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= i + 1'b1; end
43. else begin C1 <= C1 + 1'b1; D1 <= iData[7:4]; D2 <= 6'b101_111; end
44.
45. 5:
46. if( C1 == T100US -1 ) begin C1 <= 13'd0; i <= 4'd0; end
47. else begin C1 <= C1 + 1'b1; D1 <= iData[3:0]; D2 <= 6'b011_111; end
48.
49. endcase
50.
51. assign oData = { D1,D2 };
52.
53. endmodule
以上內容為核心操作以及輸出驅動聲明,步驟0~5用來輪流點亮數碼管,大概的思路之前已經解釋過 ... 舉例來說,步驟0為D1賦予iData[23:20]的內容,D2賦予 6’b111_110的內容,簡單說就是將iData[23:20]的數字顯示在第一位數碼管,至於步驟1~5也是以此類推,完后操作會返回步驟0。第51行則是輸出驅動聲明。
smg_encode_immdmod.v
圖6.10 數碼管加碼(即時)模塊。
如圖6.10所示,數碼管加碼模塊是一只即時模塊,它有一組4位的iData與一組8位的oData。該模塊接收iData的內容,然后轉換為數碼管信息,最后再經由oData輸出。具體內容,還是來瀏覽代碼吧:
1. module smg_encode_immdmod
2. (
3. input [3:0]iData,
4. output [7:0]oData
5. );
6. parameter _0 = 8'b1100_0000, _1 = 8'b1111_1001, _2 = 8'b1010_0100,
7. _3 = 8'b1011_0000, _4 = 8'b1001_1001, _5 = 8'b1001_0010,
8. _6 = 8'b1000_0010, _7 = 8'b1111_1000, _8 = 8'b1000_0000,
9. _9 = 8'b1001_0000, _A = 8'b1000_1000, _B = 8'b1000_0011,
10. _C = 8'b1100_0110, _D = 8'b1010_0001, _E = 8'b1000_0110,
11. _F = 8'b1000_1110;
12.
13. reg [7:0]D = 8'b1111_1111;
14.
15. always @ ( * )
16. if( iData == 4'd0 ) D = _0;
17. else if( iData == 4'd1 ) D = _1;
18. else if( iData == 4'd2 ) D = _2;
19. else if( iData == 4'd3 ) D = _3;
20. else if( iData == 4'd4 ) D = _4;
21. else if( iData == 4'd5 ) D = _5;
22. else if( iData == 4'd6 ) D = _6;
23. else if( iData == 4'd7 ) D = _7;
24. else if( iData == 4'd8 ) D = _8;
25. else if( iData == 4'd9 ) D = _9;
26. else if( iData == 4'hA ) D = _A;
27. else if( iData == 4'hB ) D = _B;
28. else if( iData == 4'hC ) D = _C;
29. else if( iData == 4'hD ) D = _D;
30. else if( iData == 4'hE ) D = _E;
31. else if( iData == 4'hF ) D = _F;
32. else D = 8'dx;
33.
34. assign oData = D;
35.
36. endmodule
第3~4行是出入端聲明。第6~11行是數碼管碼0~F的常量聲明。第13行是相關的寄存器聲明。第15~32行則是加碼操作。第34行是輸出驅動聲明。
smg_basemod.v
至於組合模塊smg_basemod筆者就不重復貼圖了,讀者請自行看回圖6.8。詳細的內容讓我們來瀏覽代碼吧:
1. module smg_basemod
2. (
3. input CLOCK, RESET,
4. output [7:0]DIG,
5. output [5:0]SEL
6. );
7. wire [9:0]DataU1;
8.
9. smg_funcmod U1
10. (
11. .CLOCK( CLOCK ),
12. .RESET( RESET ),
13. .iData( 24'hABCDEF ), // < top
14. .oData( DataU1 ) // > U2
15. );
16.
17. assign SEL = DataU1[5:0];
18.
19. smg_encode_immdmod U2
20. (
21. .iData( DataU1[9:6] ), // < U1
22. .oData( DIG ) // > top
23. );
24.
25. endmodule
該代碼由於演示的作用,並沒有將U1的iData直接引出,而是直接在其輸入設置常量24’hABCDEF(第13行)。至於相關的連線部署就復習圖6.8吧。編譯完后下載程序,我們便會發現數字“ABCDEF”分別自左向右顯示在6位數碼管。
細節一:完整的個體模塊
圖6.11 數碼管基礎模塊的建模圖。
如圖6.11所示,那是完整的數碼管基礎模塊,除了將smg_funcmod的iData向外引出,余下都一樣。
smg_basemod.v
1. module smg_basemod
2. (
3. input CLOCK, RESET,
4. input [23:0]iData,
5. output [7:0]DIG,
6. output [5:0]SEL
7. );
8. wire [3:0]DataU1;
9.
10. smg_funcmod U1
11. (
12. .CLOCK( CLOCK ),
13. .RESET( RESET ),
14. .iData( iData ), // < top
15. .oData( DataU1 ), // > U2
16. );
17.
18. assign SEL = DataU1[5:0];
19.
20. smg_encode_immdmod U2
21. (
22. .iData( DataU1[9:6] ), // < U1
23. .oData( DIG ) // > top
24. );
25.
26. endmodule











