1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十五:FIFO儲存模組(同步)

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十五:FIFO儲存模組(同步)

實驗十五:FIFO儲存模組(同步)

筆者雖然在實驗十四曾解釋儲存模組,而且也演示奇怪的傢伙,但是實驗十四隻是一場遊戲而已。至於實驗十五,筆者會稍微嚴肅一點,手動建立有規格的儲存模組,即同步FIFO。那些看過《時序篇》的同學一定對同步FIFO不會覺得陌生吧?因為筆者曾在《時序篇》建立基於移位暫存器的同步FIFO。不過那種同步FIFO只是用來學習的玩具而已。因此,這回筆者可要認真了!

事實告訴筆者,同步FIFO的利用率遠勝其它儲存模組,幾乎所有介面模組都會出現它的身影。早期的時候,筆者都會利用官方準備的同步FIFO(官方外掛模組),大夥都知道官方外掛模組都非常傲嬌,心意(內容)不僅不容易看透,而且訊號也不容易捉摸,最重要是無法隨心所欲擺佈它們。與其跪下向它求救,筆者還不如建立自己專屬的同步FIFO。

故名思議,“同步”表示相同頻率的時鐘源,“FIFO”表示先進先出的意思。FIFO的用意一般都是緩衝資料,另模組獨立,讓模組迴避呼叫的束縛。同步FIFO是RAM的亞種,它基於RAM,再加上先進先出的機制,學習同步FIFO就是學習如何建立先進先出的機制。

clip_image002

圖15.1 同步FIFO建模圖(常規)。

常規上,同步FIFO的建模圖如圖15.1所示,左邊有寫入請求 ReqW,寫入資料 DataW,還有寫滿標示Full。換之,右邊則有讀出請求ReqR,讀出資料DataR,還有讀空標示 Empty。寫入方面,ReqW必須拉高DataW才能寫入,一旦FIFO寫滿,那麼Full就會拉高。至於讀出方面,ReqR 必須拉高,資料才能經由DataR讀出,一旦FIFO讀空,Empty就會拉高。不過圖15.1可以稍微更動一下,另它更加接近低階建模II的形象。

clip_image004

圖15.2 同步FIFO建模圖(低階建模II)。

如圖15.2所示,Req× 改為溝通訊號 En,其中En[1] 表示寫入使能, En[0]表示讀出使能。Data× 改為資料訊號Data,iData為寫入資料,oData為讀出資料。Full與Empty 則改為狀態訊號 Tag[1] 與 Tag[0] 。

1. module fifo_savemod

2. ( 

3. input CLOCK, RESET,

4. input [1:0]iEn,

5. input [3:0]iData,

6. ouptut [3:0]oData

7. output [1:0
]oTag 8. ); 9. ...... 10. assign oTag[1] = ...; // Full 11. assign oTag[0] = ...; // Empty 12. 13. endmodule

程式碼15.1

同步FIFO大致的外皮如程式碼15.1所示,第3~7行是相關的出入端宣告,第10~11行則是相關的輸出驅動宣告。理解這些以後,接下來我們要學習先進先出這個機制。

clip_image006

圖15.3 讀空狀態。

假設筆者建立位寬為4,深度為4的ram,然後又建立位寬為3的寫指標WP與讀指正RP。同學一定會好奇道,既然ram只有4個深度,那麼指標只要2位寬(22 = 4)即不是可以訪問所有深度呢?話雖如此,為了利用指正表示寫滿與讀空狀態,指標必須多出一位 ... 因此,指標的最高位常常也被稱為方向位。

如圖15.3所示,一開始的時候,寫指標與讀指標同樣指向地址0,而且ram裡邊也是空空如也,為此讀空狀態“叮咚”亮著紅燈。為此,我們可以暫時這樣表示讀空的邏輯關係:

Empty = (WP == RP);

clip_image008

圖15.4 寫入中①。

當火車開動以後,首先資料 4’hA 寫入地址0,然後寫指標從原來的 3’b0_00 遞增為 3’b0_01,並且指向地址1。此刻,ram再也不是空空入也,所示讀空狀態消除紅燈,結果如圖15.4 所示。

clip_image010

圖15.5 寫入中②。

緊接著,資料 4’hB 寫入地址1,然後寫指標從原來的 3’b0_01 遞增為 3’b0_10,並且指向地址2,結果如圖15.5所示。

clip_image012

圖15.6寫入中③

然後,資料 4’hC 寫入地址2,然後寫指標從原來的 3’b0_10 遞增為 3’b0_11,並且指向地址3,結果如圖15.6所示。

clip_image014

圖15.7 寫滿狀態。

接著,資料 4’hD 寫入地址3,然後寫指標從原來的 3’b0_11 遞增為 3’b1_00,並且重新指向地址0。此刻寫指標的最高位為1,這表示寫指標已經繞彎ram一圈又回來原點,反之讀指標從剛才開始一動也不動,結果最高為0 ... 所以我們可以說寫指標與讀指標目前處於不同的方向。在此ram已經寫滿,所以寫滿狀態便“叮咚”亮紅燈,結果如圖15.6所示。寫滿狀態的邏輯關係則可以這樣表示:

FULL = (WP[2] ^ RP[2] && WP[1:0] == RP[1:0]);

clip_image016

圖15.8 讀出中①。

從現在開始,另一頭火車才開始走動 ... 首先資料4’hA從地址0讀出來,讀指標也從原本的 3’b0_00 遞增為 3’b0_01,並且指向地址1。此刻ram再也不是吃飽飽的狀態,所以寫滿狀態被消除紅燈,結果如圖15.8所示。

clip_image018

圖15.9 讀出中②。

接下來,資料4’hB 從地址1哪裡讀出,讀指標也從原本的 3’b0_01 遞增為 3’b0_10,並且指向地址2,結果如圖15.9所示。

clip_image020

圖15.10 讀出中③。

隨之,資料4’hC 從地址2哪裡讀出,讀指標也從原本的 3’b0_10 遞增為 3’b0_11,並且指向地址3,結果如圖15.10所示。

clip_image022

圖15.11讀空狀態。

最後,資料4’hD 從地址3哪裡讀出,讀指標也從原本的 3’b0_11 遞增為 3’b1_00,並且重新指向地址0。當讀指標繞彎一圈又回到原點的時候,讀者的最高位也成為值1,換句話說 ... 此刻的讀指標與寫指標也處於同樣的位置。同一個時候,ram也是空空如也,所以讀空狀態便“叮咚”亮起紅燈,結果如圖15.11所示。為此,讀空狀態的邏輯關係可以這樣表示:

Empty = (WP == RP);

總結而言,當我們設定N位位寬的時候,讀寫指標的位寬便是 N + 1。此外,讀空狀態為寫指標等價讀指標。反之,寫滿狀態是兩個指標方向一致(異或狀態),然後地址一致。理解先進先出的機制以後,接下來我們便可以填充一下FIFO儲存模組的內容。

1. module fifo_savemod

2. (

3. input CLOCK, RESET, 

4. input [1:0]iEn,

5. input [3:0]iData,

6. output [3:0]oData,

7. output [1:0]oTag

8. );

9. reg [3:0] RAM [3:0]; 

10. reg [3:0]D1;

11. reg [2:0]C1,C2; // N+1

12.

13. always @ ( posedge CLOCK or negedge RESET )

14. if( !RESET )

15. begin

16. C1 <= 3'd0;

17. end

18. else if( iEn[1] ) 

19. begin

20. RAM[ C1[1:0] ] <= iData; 

21. C1 <= C1 + 1'b1; 

22. end

23.

24. always @ ( posedge CLOCK or negedge RESET )

25. if( !RESET )

26. begin

27. D1 <= 4'd0;

28. C2 <= 3'd0;

29. end

30. else if( iEn[0] )

31. begin 

32. D1 <= RAM[ C2[1:0] ]; 

33. C2 <= C2 + 1'b1; 

34. end

35.

36. assign oData = D1;

37. assign oTag[1] = ( C1[2]^C2[2] & C1[1:0] == C2[1:0] ); // Full

38. assign oTag[0] = ( C1 == C2 ); // Empty

39.

40. endmodule

程式碼15.2

筆者在第9~11行建立相關的暫存器,C1取代WP,C2取代RP。第13~22行是寫操作,內容非常單純,即 iEn[1] 拉高便將 iData 寫入 C1[1:0] 指定的地方,然後C1遞增。

第24~34行是讀操作,內容也是一樣單純,iEn[0] 拉高便將 C2[1:0] 指定的資料暫存至 D1,隨後C2遞增,最後由D驅動oData。第37~38行是寫滿狀態與讀空狀態的邏輯關係。

clip_image024

圖15.12 呼叫FIFO儲存模組。

建立同步FIFO基本上沒有什麼難度,但是呼叫FIFO倒是一件難題。如圖15.12所示,筆者建立一支核心操作嘗試呼叫 FIFO儲存模組,至於核心操作的內容如程式碼15.3所示:

1. case( i ) // Core

2. 0:

3. if( iTag[1]! ) begin oEn[1] <= 1’b1; oData <= 4’hA; i <= i + 1’b1; end

4. 1:

5. if( iTag[1]! ) begin oEn[1] <= 1’b1; oData <= 4’hB; i <= i + 1’b1; end

6. 2:

7. if( iTag[1]! ) begin oEn[1] <= 1’b1; oData <= 4’hC; i <= i + 1’b1; end

8. 3:

9. if( iTag[1]! ) begin oEn[1] <= 1’b1; oData <= 4’hD; i <= i + 1’b1; end

10. 4:

11. begin oEn[1] <= 1’b0; i <= i + 1’b1; end

12. 5:

13. if( iTag[0]! ) begin oEn[0] <= 1’b1; i <= i + 1’b1; end

14. 6:

15. if( iTag[0]! ) begin oEn[0] <= 1’b1; i <= i + 1’b1; end

16. 7:

17. if( iTag[0]! ) begin oEn[0] <= 1’b1; i <= i + 1’b1; end

18. 8:

19. if( iTag[0]! ) begin oEn[0] <= 1’b1; i <= i + 1’b1; end

20. 9:

21. begin oEn[0] <= 1’b0; i <= i + 1’b1; end

22. endcase

程式碼15.3

如程式碼15.3所示,步驟0~3用來一邊檢測 Tag[1] 是否為高,一邊向儲存模組寫入資料 4’hA~4‘hD,步驟4則用來拉低 oEn[1] 並且歇息一下。步驟5~8用來一邊檢測 Tag[0] 是否為高,一邊從儲存模組哪裡讀出資料,步驟9則用來拉低 oEn[0]並且偷懶一下。

clip_image026

圖15.13 讀寫FIFO儲存模組的理想時序圖。

圖15.13是程式碼15.3所生產的理想時序圖,同時也是核心操作作為視角的時序,至於C1~C2是FIFO儲存模組作為視角的時序。各個視角的時序過程如下:

核心操作視角:

l T0,isTag[1]為低(即時值),拉高oEn[1](未來值),並且傳送資料 4’hA(未來值)。

l T1,isTag[1]為低(即時值),拉高oEn[1](未來值),並且傳送資料 4’hB(未來值)。

l T2,isTag[1]為低(即時值),拉高oEn[1](未來值),並且傳送資料 4’hC(未來值)。

l T3,isTag[1]為低(即時值),拉高oEn[1](未來值),並且傳送資料 4’hD(未來值)。

l T4,isTag[1]為高(即時值),拉低oEn[1](未來值)。

l T5,isTag[0]為低(即時值),拉高oEn[1](未來值)。

l T6,isTag[0]為低(即時值),拉高oEn[1](未來值),資料4’hA讀出(過去值)。

l T7,isTag[0]為低(即時值),拉高oEn[1](未來值),資料4’hB讀出(過去值)。

l T8,isTag[0]為低(即時值),拉高oEn[1](未來值),資料4’hC讀出(過去值)。

l T9,isTag[0]為高(即時值),拉低oEn[1](未來值),資料4’hD讀出(過去值)。

l T10,isTag[0]為高(即時值)。

FIFO儲存模組視角:

l T0,oEn[1]為低(過去值)。C1等價C2為讀空狀態,iTag[0]拉高(即時值)。

l T1,oEn[1]為高(過去值),讀取資料4’hA(過去值),遞增C1。C1不等價C2,iTag[0]拉低(即時值)。

l T2,oEn[1]為高(過去值),讀取資料4’hB(過去值),遞增C1。

l T3,oEn[1]為高(過去值),讀取資料4’hC(過去值),遞增C1。

l T4,oEn[1]為高(過去值),讀取資料4’hA(過去值),遞增C1。C1等價C2為寫滿狀態,iTag[1]拉高(即時值)。

l T5,oEn[1]為低(過去值)。

l T6,oEn[0]為高(過去值),讀出資料4’hA(未來值),遞增C2。C1不等價C2,isTag[1]拉低(即時值)。

l T7,oEn[0]為高(過去值),讀出資料4’hB(未來值),遞增C2。

l T8,oEn[0]為高(過去值),讀出資料4’hC(未來值),遞增C2。

l T9,oEn[0]為高(過去值),讀出資料4’hD(未來值),遞增C2。C1等價C2為讀空狀態,isTag[0]拉高(即時值)。

l T10,oEn[0]為低(過去值)。

讀者是不是一邊瀏覽一邊捏蛋蛋呢?什麼過去值,又什麼未來值,又又什麼即時值的 ... 沒錯,同步FIFO的設計原理雖然簡單,但是時序解讀卻讓人淚流滿面。因為同步FIFO夾雜兩種時序表現——時間點事件還有即時事件。如圖15.13 所示,除了 iTag 訊號是觸發即時事件以外,所有訊號都是觸發時間點事件。讀過《時序篇》或者《工具篇II》的朋友一定知曉,即時值不僅比過去值優先,而且即時值也會無視時鐘。

好奇的同學可能困惑道:“為什麼iTag不能設計成為時間點事件呢?”。筆者曾在《時序篇》建立基於移位暫存器的FIFO,其中iTag就是設計成為時間點事件,結果FIFO的寫滿狀態或者讀空狀態都來不及反饋,因此發生呼叫上的混亂。

clip_image028

圖15.14 讀寫FIFO儲存模組的即時事件。

為了理解重點,首先讓我們來焦距寫資料的部分。如圖15.14所示,關鍵的地方就是發生在T4——這隻時鐘沿。T4之際,FIFO儲存模組讀取oEn[1]的過去值,C1也因此遞增,即時事件就在這個瞬間發生了。寫滿狀態成立,iTag[1]也隨之拉高即時值。從時序上來看,C1的更新(C1為4’b100)是發生在T4之後,不過那也無關緊要,因為即時值是更新在小小的時間沿之間,也是即時層 ... 然而,即時層是無法顯示在時序之上。

clip_image030

圖15.15 遲到的寫滿狀態。

假設,iTag[1]不是經由即時事件觸發而是事件點事件,那麼iTag就會反饋遲到的寫滿狀態。如圖15.15所示,T4之際 oEn[1] 為高,C1也因此遞增為 3’b100。T5之際,C1與C2的過去值均為 3’b100 與 3’b000,然後拉高 iTag[1]。由於時間點事件的關係,所以iTag[1]遲一拍被拉高 ... 讀者千萬別小看這樣慢來一拍,它是搞亂呼叫的罪魁禍首。

如果核心操作在T5繼續寫操作的話,此刻iTag[1]的過去值為0,它會認為FIFO未滿,然後不管三七二十一執行寫操作,結果FIFO發生錯亂隨之機能崩潰。從某種程度來看,即時事件的偷時鐘能力,是建立同步FIFO的關鍵。

fifo_savemod.v

clip_image032

圖15.16 fifo儲存模組的建模圖。

圖15.16基本上與圖15.2沒什麼兩樣,不過FIFO儲存模組的位寬還有深度發生改變而已。此外,圖15.16的訊號佈局雖然有點違規低,不過這點小細節讀者就不要太計較了。建模技巧畢竟不是暴力規範,用不著死守,反之隨機應變才是本意。

1. module fifo_savemod

2. (

3. input CLOCK, RESET, 

4. input [1:0]iEn,

5. input [7:0]iData,

6. output [7:0]oData,

7. output [1:0]oTag

8. );

以上內容是相關的出入端宣告。

9. reg [7:0] RAM [15:0]; 

10. reg [7:0]D1;

11. reg [4:0]C1,C2; // N+1

12.

以上內容是相關的記憶體與暫存器宣告。第9行,RAM宣告為8位寬還有24=16個深度。為此,第11行的寫指標C1與讀指標C2宣告為5個位寬。

13. always @ ( posedge CLOCK or negedge RESET )

14. if( !RESET )

15. begin

16. C1 <= 5'd0;

17. end

18. else if( iEn[1] ) 

19. begin

20. RAM[ C1[3:0] ] <= iData; 

21. C1 <= C1 + 1'b1; 

22. end

23.

以上內容是fifo的寫操作,第18行的 iEn[1] 每拉高一個時鐘, 第20行的iData 便寫入C1[3:0]指定的位置,隨後第21行寫指標也遞增。

24. always @ ( posedge CLOCK or negedge RESET )

25. if( !RESET )

26. begin

27. D1 <= 8'd0;

28. C2 <= 5'd0;

29. end

30. else if( iEn[0] )

31. begin 

32. D1 <= RAM[ C2[3:0] ]; 

33. C2 <= C2 + 1'b1; 

34. end

35.

以上內容是fifo的讀操作,第30行的 iEn[0] 每拉高一個時鐘, 第32行的D1便賦予C2[3:0]指定的資料,隨後第33行的讀指標也遞增。

36. assign oData = D1;

37. assign oTag[1] = ( C1[4]^C2[4] & C1[3:0] == C2[3:0] ); // Full

38. assign oTag[0] = ( C1 == C2 ); // Empty

39.

40. endmodule

以上內容是相關輸出驅動宣告,其中第37行是寫滿狀態,第38行是讀空狀態。在此,讀者需要注意一下 ... 第36行相較第37~38行 ,前者由暫存器D1驅動,即oData訊號為時間點事件。反之,後者由組合邏輯驅動,即 oTag[1:0] 訊號為即時事件。為此,該儲存模組的內部狀態是以即時的方式反饋出去。

tx_rx_demo.v

clip_image034

圖15.17 實驗十五的建模圖。

實驗十五是實驗十三的延續 ... 實驗十三之際,RX功能模組接收並且失敗傳送一連串的資料,因為傳送方不僅來不及,而且接收成功的資料也沒有地方緩衝。如今實驗十五多了一隻FIFO儲存模組作為緩衝空間。注意,圖15.17雖然是實驗十五的建模圖,可是卻與實際的連線部署有一點出入,不過大意上都是差不多的。

RX功能模組接收一連串的資料,然後經由周邊操作協調,事後再將資料緩衝至FIFO儲存模組。至於核心操作會不停從FIFO儲存模組哪裡讀取資料,然後再呼叫TX功能模組將資料傳送出去。

1. module tx_rx_demo

2. (

3. input CLOCK, RESET, 

4. input RXD,

5. output TXD

6. ); 

以上內容是相關的出入端宣告。

7. wire DoneU1;

8. wire [7:0]DataU1;

9.

10. rx_funcmod U1

11. (

12. .CLOCK( CLOCK ),

13. .RESET( RESET ),

14. .RXD( RXD ), // < top

15. .iCall( isRX ), // < sub

16. .oDone( DoneU1 ), // > U2

17. .oData( DataU1 ) // > U2

18. );

19.

以上內容是RX功能模組的例項化。第15行表示 isRX 充當使能。

20. reg isRX;

21.

22. always @ ( posedge CLOCK or negedge RESET ) // sub

23. if( !RESET ) isRX <= 1'b0;

24. else if( DoneU1 ) isRX <= 1'b0; 

25. else isRX <= 1'b1; 

26.

以上內容是周邊操作,它主要重複呼叫RX功能模組。

27. wire [1:0]TagU2;

28. wire [7:0]DataU2;

29.

30. fifo_savemod U2

31. (

32. .CLOCK( CLOCK ),

33. .RESET( RESET ),

34. .iEn ( { DoneU1 , isRead } ), // < U1 & Core

35. .iData ( DataU1 ), // < U1

36. .oData ( DataU2 ), // > U3

37. .oTag ( TagU2 ) // > core

38. );

39.

以上內容是FIFO儲存模組的例項化。第34行表示,DoneU1充當寫入使能,isRead充當讀出使能。

40. wire DoneU3;

41.

42. tx_funcmod U3

43. (

44. .CLOCK( CLOCK ), 

45. .RESET( RESET ),

46. .TXD( TXD ), // > top

47. .iCall( isTX ), // < core

48. .oDone( DoneU3 ), // > core

49. .iData( DataU2 ) // < U2

50. );

51.

以上內容是TX功能模組的例項化。第47行表示 isTX充當使能。第49行表示,該模組的iData直接經由DataU2驅動。

52. reg [3:0]i;

53. reg isRead;

54. reg isTX;

55.

56. always @ ( posedge CLOCK or negedge RESET ) // core

57. if( !RESET )

58. begin 

59. i <= 4'd0;

60. isRead <= 1'b0;

61. isTX<= 1'b0;

62. end

以上內容是核心操作的相關暫存器宣告與復位操作。

63. else

64. case( i )

65.

66. 0:

67. if( !TagU2[0] ) begin isRead <= 1'b1; i <= i + 1'b1; end

68.

69. 1:

70. begin isRead <= 1'b0; i <= i + 1'b1; end

71.

72. 2:

73. if( DoneU3 ) begin isTX <= 1'b0; i <= 4'd0; end

74. else isTX <= 1'b1;

75.

76. endcase

77.

78. endmodule

以上內容是操作操作。步驟0用來判斷FIFO是否讀空,否則就拉高isRead。步驟1則拉低isRead,然後FIFO就會讀出資料。步驟2則使能TX功能模組,並且將方才讀出的資料傳送出去。

編譯完畢並下載程式。此刻,串列埠便可以支援一連串的資料傳送與接收,為了避免部分資料憑空消失的怪事,資料流的容量必須配合FIFO的緩衝容量(深度)。此外,實驗十五還有許多優化的空間,然而這些都是交由讀者的功課。(注意,某些串列埠除錯助手必須把檢驗位設定為標誌位才能顯示字元)

細節一:完整的個體模組

該實驗的 fifo_savemod.v 已經是完整的個體。

相關推薦

黑金原創教程FPGA那些事兒-驅動I 實驗FIFO儲存模組同步

實驗十五:FIFO儲存模組(同步) 筆者雖然在實驗十四曾解釋儲存模組,而且也演示奇怪的傢伙,但是實驗十四隻是一場遊戲而已。至於實驗十五,筆者會稍微嚴肅一點,手動建立有規格的儲存模組,即同步FIFO。那些看過《時序篇》的同學一定對同步FIFO不會覺得陌生吧?因為筆者曾在《時序篇》建立基於移位暫存器的同步FIF

黑金原創教程FPGA那些事兒-驅動I 實驗流水燈模組

實驗一:流水燈模組 對於發展商而言,動土儀式無疑是最重要的任務。為此,流水燈實驗作為低階建模II的動土儀式再適合不過了。廢話少說,我們還是開始實驗吧。 圖1.1 實驗一建模圖。 如圖1.1 所示,實驗一有名為 led_funcmod的功能模組。如果無視環境訊號(時鐘訊號還有復位訊號),該功能模組只有

黑金原創教程FPGA那些事兒-驅動I 連載導讀

前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組② — 點選與長點選

實驗三:按鍵模組② — 點選與長點選 實驗二我們學過按鍵功能模組的基礎內容,其中我們知道按鍵功能模組有如下操作: l 電平變化檢測; l 過濾抖動; l 產生有效按鍵。 實驗三我們也會z執行同樣的事情,不過卻是產生不一樣的有效按鍵: l 按下有效(點選); l 長按下有效(長點選)。 圖3

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組

實驗二:按鍵模組① - 消抖 按鍵消抖實驗可謂是經典中的經典,按鍵消抖實驗雖曾在《建模篇》出現過,而且還惹來一堆麻煩。事實上,筆者這是在刁難各位同學,好讓對方的慣性思維短路一下,但是慘遭口水攻擊 ... 面對它,筆者宛如被甩的男人,對它又愛又恨。不管怎麼樣,如今 I’ll be back,筆者再也不會重複一

黑金原創教程FPGA那些事兒-驅動I 實驗數碼管模組

實驗六:數碼管模組 有關數碼管的驅動,想必讀者已經學爛了 ... 不過,作為學習的新儀式,再爛的東西也要溫故知新,不然學習就會不健全。黑金開發板上的數碼管資源,由始至終都沒有改變過,筆者因此由身懷念。為了點亮多位數碼管從而顯示數字,一般都會採用動態掃描,然而有關動態掃描的資訊請怒筆者不再重複。在此,同樣也是

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組③ — 單擊與雙擊

實驗四:按鍵模組③ — 單擊與雙擊 實驗三我們建立了“點選”還有“長點選”等有效按鍵的多功能按鍵模組。在此,實驗四同樣也是建立多功能按鍵模組,不過卻有不同的有效按鍵。實驗四的按鍵功能模組有以下兩項有效按鍵: l 單擊(按下有效); l 雙擊(連續按下兩下有效)。 圖4.1 單擊有效按鍵,時序示意圖

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組④ — 點選,長點選,雙擊

實驗五:按鍵模組④ — 點選,長點選,雙擊 實驗二至實驗四,我們一共完成如下有效按鍵: l 點選(按下有效) l 點選(釋放有效) l 長擊(長按下有效) l 雙擊(連續按下有效) 然而,不管哪個實驗都是隻有兩項“功能”的按鍵模組而已,如今我們要建立三項“功能”的按鍵模組,亦即點選(按下有效),長

黑金原創教程FPGA那些事兒-驅動I 原創教程連載導讀連載完成,共二九章

前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組④ — 頁讀寫 β

實驗二十一:SDRAM模組④ — 頁讀寫 β 未進入主題之前,讓我們先來談談一些重要的體外話。《整合篇》之際,筆者曾經比擬Verilog如何模仿for迴圈,我們知道for迴圈是順序語言的產物,如果Verilog要實現屬於自己的for迴圈,那麼它要考慮的東西除了步驟以外,還有非常關鍵的時鐘。 for(

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組④ — 普通滑鼠

實驗十:PS/2模組④ — 普通滑鼠 學習PS/2鍵盤以後,接下來就要學習 PS/2 滑鼠。PS/2滑鼠相較PS/2鍵盤,驅動難度稍微高了一點點,因為FPGA(從機)不僅僅是從PS/2滑鼠哪裡讀取資料,FPGA還要往滑鼠裡寫資料 ... 反之,FPGA只要對PS/2鍵盤讀取資料即可。然而,最傷腦筋的地方就在

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組① — 單字讀寫

實驗十八:SDRAM模組① — 單字讀寫 筆者與SDRAM有段不短的孽緣,它作為冤魂日夜不斷糾纏筆者。筆者嘗試過許多方法將其退散,不過屢試屢敗的筆者,最終心情像橘子一樣橙。《整合篇》之際,筆者曾經大戰幾回兒,不過內容都是點到即止。最近它破蠱而出,日夜不停:“好~痛苦!好~痛苦!”地呻吟著,嚇得筆者不敢半夜如

黑金原創教程FPGA那些事兒-驅動I 實驗TFT模組

實驗二十七:TFT模組 - 顯示 所謂TFT(Thin Film Transistor)就是眾多LCD當中,其中一種支援顏色的LCD,相較古老的點陣LCD(12864笑),它可謂高階了。黑金的TFT LCD除了320×240大小以外,內建SSD1289控制器,同時也是獨立模組。事實上,無論是驅動點陣LCD還

黑金原創教程FPGA那些事兒-驅動I 實驗十三串列埠模組② — 接收

實驗十三:串列埠模組② — 接收 我們在實驗十二實現了串列埠傳送,然而這章實驗則要實現串列埠接收 ... 在此,筆者也會使用其它思路實現串列埠接收。 圖13.1 模組之間的資料傳輸。 假設我們不考慮波特率,而且一幀資料之間的傳輸也只是發生在FPGA之間,即兩隻模組之間互轉,並且兩塊模組都使用相同的時

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組① — 鍵盤

實驗七:PS/2模組① — 鍵盤 實驗七依然也是熟爛的PS/2鍵盤。相較《建模篇》的PS/2鍵盤實驗,實驗七實除了實現基本的驅動以外,我們還要深入解PS/2時序,還有PS/2鍵盤的行為。不過,為了節省珍貴的頁數,怒筆者不再重複有關PS/2的基礎內容,那些不曉得的讀者請複習《建模篇》或者自行谷歌一下。 市場

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組③ — 鍵盤與多組合鍵

實驗九:PS/2模組③ — 鍵盤與多組合鍵 筆者曾經說過,通碼除了單位元組以外,也有雙位元組通碼,而且雙位元組通碼都是 8’hE0開頭,別名又是 E0按鍵。常見的的E0按鍵有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC>

黑金原創教程FPGA那些事兒-驅動I 實驗SDHC模組

實驗二十五:SDHC模組 筆者曾經說過,SD卡發展至今已經衍生許多版本,實驗二十四就是針對版本SDV1.×的SD卡。實驗二十四也說過,CMD24還有CMD17會故意偏移地址29,讓原本範圍指向從原本的232 變成 223,原因是SD卡讀寫一次都有512個位元組。為此我們可以這樣計算: SDV1.x = 2

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組③ — 頁讀寫 α

實驗二十:SDRAM模組③ — 頁讀寫 α 完成單字讀寫與多字讀寫以後,接下來我們要實驗頁讀寫。醜話當前,實驗二十的頁讀寫只是實驗性質的東西,其中不存在任何實用價值,筆者希望讀者可以把它當成頁讀寫的熱身運動。 表示20.1 Mode Register的內容。 Mode Register

黑金原創教程FPGA那些事兒-驅動I 實驗TFT模組

實驗二十八:TFT模組 - 觸屏 讀者在上一個實驗所玩弄過的 TFT LCD模組,除了顯示大小為 320 × 240,顏色為16位RGB的影象資訊以外,它還支援觸屏。所謂觸屏就是滑鼠還有鍵盤以外的輸入手段,例如現在流行平板還有智慧手機,觸屏輸入對我們來說,已經成為日常的一部分。描述語言一門偏向硬體的語言

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組⑤ — FIFO讀寫

經過漫長的戰鬥以後,我們終於來到最後。對於普通人而言,頁讀寫就是一名戰士的墓碑(最終戰役) ... 然而,怕死的筆者想透過這個實驗告訴讀者,旅程的終點就是旅程的起點。一直以來,筆者都在煩惱“SDRAM是否應該成為儲存類?”SDRAM作為一介儲存資源(儲存器),它的好處就是大容量空間,壞處則就是麻煩的控制規