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

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗十七:IIC儲存模組

1. int main()

2. {

3. int A;

4. A = 165. }

程式碼17.1

話題為進入之前,首先讓我們來聊聊一些題外話。那些學過軟核NIOS的朋友可曾記得,軟核NIOS可利用片上記憶體作為儲存資源,而且它也能利用SDRAM作為儲存資源,然而問題是在這裡 ... 如程式碼17.1所示,筆者先建立變數A,然後變數A賦值16。如果站在高階語言上的角度去思考,無論是建立變數A還是為變數A賦值,我們沒有必要去理解變數A利用什麼儲存資源,然後賦值變數A又是利用怎樣的儲存功能去實現。

我們只要負責輕鬆的表層工作,然而那些辛苦的底層工作統統交由編譯器處理。讀者也許會認為那是高階語言的溫柔,不過這股溫柔卻不適合描述語言,還不如說那是一種抹殺性的傷害。這種感覺好比父母親過度溺愛自己的孩子,嬌生慣養的孩子最終只會失去可能性而已。

筆者曾在前面說過,儲存模組基本上可以分為“儲存資源”還有“儲存方式”。預設下,描述語言可用的儲存資源有暫存器還有片上記憶體,然而兩者都是內在資源。換之,實驗十六卻利用外在的儲存資源,IIC儲存器。

clip_image002

圖17.1 IIC儲存模組與RAM儲存模組。

如圖17.1所示,那是實驗十六的IIC儲存模組,然而圖17.1也表示IIC儲存模組的呼叫方法也不僅近似 RAM儲存模組,而且只有一方呼叫它而已。這種感覺好比順序語言的主函式呼叫某個儲存函式一樣,結果如程式碼17.2所示:

1. int ram_func( int Addr, int WrData ) { ... }

2.

3
. int main() 4. { 5. ram_func( 020 ); 6. ... 7. }

程式碼17.2

如程式碼17.2所示,第1行宣告函式 ram_func,然後主函式在第5行將其呼叫,並且傳遞地址引數0,資料引數 20。高階語言是一位順序又寂寞的傢伙,函式永遠只能被一方呼叫而已 ... 換之,描述語言是一位並行又多愁的傢伙,模組有可能同時被兩方以上呼叫,情況宛如兩位男生同時追求一位少女,對此少女會煩惱選擇誰。哎~這是奢侈的少女憂愁。

clip_image004

圖17.2 雙口RAM儲存模組。

如圖17.2所示,RAM儲存模組同時被兩方呼叫,周邊操作為它寫入資料,核心操作則為它讀出資料。圖17.2也是俗稱的雙口RAM。對此,我們也可以說雙口RAM儲存模是RAM儲存模組的亞種。

clip_image006

圖17.3 基於雙口RAM儲存模組的FIFO儲存模組。

此外,雙口RAM儲存模組只要稍微更換一下馬甲,然後加入一些先進先出的機制,隨之基於雙口RAM儲存模組的FIFO儲存模組便完成,結果如圖17.3所示。對此,我們可以說FIFO儲存模組是雙口RAM儲存模組的亞種。

那麼問題來了:

“請問,實驗十六的IIC儲存模組是否也能成為雙口IIC儲存模組?”,筆者問道。

“再請問,它還可以成為有FIFO機制的儲存模組呢?”,筆者再問道。

沒錯,上述問題就是實驗十七的主要目的。如果這些要求可以成真,我們便可以斷定描述語言不僅不遜色與順序語言,描述語言也充滿許多可能性,而且儲存類作為一個模組類有著舉足輕重的地位。廢話少說,我們還是開始實驗吧,因為筆者已經壓抑不了蛋蛋的衝動!

首先我們要明白,片上記憶體是效率又優秀的儲存資源,基本上只要1個時鐘就可完成讀寫操作,而且讀寫也可以同時進行,兩方也可以同時呼叫,不過就是不能隨意擴充。反之,IIC儲存器雖然可以隨意擴充,但是又笨又麻煩的它,讀寫操作不僅用時很長,而且不能也同時進行,對此造就兩方不能同時呼叫的問題。為此,我們必須先解決這個問題。

clip_image008

圖17.4 寫操作與讀操作。

實驗十六告訴我們,IIC儲存模組有兩位 Call 訊號,其中 Call[1] 表示寫操作,Call[0]表示讀操作。不過不管是寫操作還是讀操作,IIC儲存模組都必須呼叫IIC儲存器,而且讀寫操作一次也只能進行其中一項。如圖17.4,假設左邊的周邊操作負責寫操作,右邊的核心操作負責讀操作 ... 如果兩者同時拉高 Call 訊號就會發生多義性的問題,對此筆者該如何協調呢?

clip_image010

圖17.5 輪流協調。

為了公平起見,筆者採取輪流的方式來協調多義性的問題。圖17.5所是輪流協調的概念圖,一般Call兼職“提問與使能“,即Call一拉高操作便執行。如今Call訊號作為第一層提問,isDo則作為第二層使能 ... 換句話說,不管 Call 拉不拉高,只要isDo不拉高,操作也不會執行。如圖17.5所示,isDo位寬有3表示模組有3種操作,或者說3個操作共享一個模組資源。至於右邊是稱為使能指標的箭頭,它的作用是給予使能權。

clip_image012

圖17.6 輪流協調例子①。

如圖17.6所示,假設 Call[2] 拉高以示提問,但是指標並沒有指向它,所以它沒有使能權也不能執行操作。這種情況好比舉手的學生沒被老師點名,這位學生就不能隨意開口。當然,使能指標也不是靜止不動,只要遇見有人舉手提問,它便會按照順序檢測各個物件。

clip_image014

圖17.7 輪流協調例子②。

如圖17.7所示,當指標來到Call[2]的面前並且給予使能權,isDo[2]立即拉高使能操作,直至操作完成之前,該操作都享有模組的使用權。(灰度指標為過去,黑色指標為現在)

clip_image016

圖17.8 輪流協調例子③。

如圖17.8所示,操作執行完畢之際,模組便會反饋完成訊號以示結束操作,isDo[2] 還有Call[2] 都會經由完成訊號拉低內容。此外,指標也會立即指向下一個物件。

clip_image018

圖17.9 輪流協調例子④。

如圖17.9所示,假設 Call[2] 還有 Call[1] 同時提問,由於指標沒有指向它們,所以Call[2] 與 Call[1] 都沒有使能權。此刻,指標開始一動。

clip_image020

圖17.10 輪流協調例子⑤。

首先,Call[1] 會得到使能權,isDo[1]因此拉高並且開始執行操作,直至操作結束之前,isDo[1]都獨佔模組,結果如圖17.10所示。

clip_image022

圖17.11 輪流協調例子⑥。

如圖17.11所示,當操作執行完畢,模組便會反饋完成訊號,隨之isDo[1] 還有 Call[1]都會拉低內容,而且指標也會指向下一個物件。對此,isDo[2] 得到使能權,並且開始執行操作 ... 直至操作結束之前,它都獨佔模組。

clip_image024

圖17.12 輪流協調例子⑦。

操作結束之際,模組便會反饋完成訊號,isDo[2] 還有 Call[2] 隨之也會拉低內容,然後指標指向另一個物件,結果如圖17.12所示。輪流協調的概念基本上就是這樣而已,即單純也非常邏輯。接下來,讓我們來看看Verilog 如何描述輪流協調,結果如程式碼17.3所示:

1. module iic_savemod

2. (

3. input [1:0]iCall,

4. output [1:0]oDone,

5. );

6. reg [1:0]C7;

7. reg [1:0]isDo;

8.

9. always @ ( posedge CLOCK or negedge RESET )

10. if( !RESET ) 

11. begin 

12. C7 <= 2’b10;

13. isDo <= 2’b00;

14. end

15. else 

16. begin

17. if( iCall[1] & C7[1] ) isDo[1] <= 1’b1;

18. else if( iCall[0] & C7[0] ) isDo[0] <= 1’b1;

19.

20. if( isDo[1] & isDone[1] ) isDo[1] <= 1’b0;

21. else if( isDo[0] & isDone[0] ) isDo[0] <= 1’b0;

22.

23. if( isDone ) C7 <= { isDo[0], isDo[1] };

24. else if( iCall ) C7 <= { C7[0], C7[1] };

25. end

26.

程式碼17.3

第3~4行是相關的出入端宣告, 其中 Call 還有 Done 均為兩位。第6~7行是輪流協調作用的暫存器isDo與C7,C7為使能指標。第10~14行則是這些暫存器的初始化,注意C7預設下指向 Call[1]。第16~25行則是輪流協調的主要操作,第17~18行是提問與使能,其中 iCall[N] & C7[N] 表示提問並且被指標指向,isDo[N] 表示給予使能權。

第20~21行是消除提問和使能,其中 isDo[N] & isDone[N] 表示相關的完成訊號對應相關的操作,然後 isDo[N] 表示消除使能。第24行的表示有提問,指標就立即移動。第23行表示結束操作,指標便指向下一個物件。

27. reg [4:0]i;

28. reg [1:0]isDone;

29.

30. always @ ( posedge CLOCK or negedge RESET )

31. if( !RESET )

32. begin

33. ...

34. i <= 5’d0;

35. isDone <= 2’b00;

36. end

37. else if( isDo[1] )

38. case( i )

39. ...

40. 5: begin isDone[1] <= 1’b1; i <= i + 1’b1; end

41. 6: begin isDone[1] <= 1’b0; i <= 5’d0; end

42. endcase

43. else if( isDo[0] )

44. case( i )

45. ...

46. 7: begin isDone[0] <= 1’b1; i <= i + 1’b1; end

47. 8: begin isDone[0] <= 1’b0; i <= 5’d0; end

48. endcase

49.

50. endmodule

程式碼17.3

第27~28行只核心操作相關的暫存器,第31~36行則是這些暫存器的復位操作。第37行表示 isDo[1] 拉高才執行操作1。第40~41行表示操作1反饋完成訊號。第43行表示 isDo[0] 拉高才指向操作0。第46~47行表示操作0反饋完成訊號。如此一來,問答訊號便有輪流協調,接下來就是為IIC儲存模組加入FIFO機制。

clip_image026

圖17.13 有FIFO機制的IIC儲存模組。

如圖17.13所示,那是擁有FIFO機制的IIC儲存模組,它那畸形的儲存功能,可謂是IIC儲存模組的亞種。其中 Call/Done[1] 表示寫入呼叫,Tag[1] 表示寫滿狀態,反之既然。由於目前的IIC儲存模組是FIFO的關係,所以寫入地址還有讀出地址都是在裡邊建立。為此,Verilog可以這樣描述,結果如程式碼17.4所示:

1. module iic_savemod

2. (

3. input [1:0]iCall,

4. output [1:0]oDone,

5. input [7:0]iData,

6. output [7:0]oData,

7. output [1:0]oTag

8. );

9. always @ ( posedge CLOCK or negedge RESET ) // 輪流協調的周邊操作

10. ...

11.

12. reg [8:0]C2,C3; // C2 Write Pointer, C3 Read Pointer;

13.

14. always @ ( posedge CLOCK or negedge RESET )

15. if( !RESET )

16. begin

17. C2 <= 9’d0;

18. C3 <= 9’d0;

19. end

20. else if( isDo[1] )

21. case( i )

22. ...

23. 2: // Wirte Word Addr

24. begin D1 <= C2[7:0]; i <= FF_Write1; Go <= i + 1'b1; end

25. ...

26. 5:

27. begin C2 <= C2 + 1'b1; isDone[1] <= 1'b1; i <= i + 1'b1; end

28. ...

29. endcase

30. else if( isDo[0] )

31. case( i )

32. ...

33. 2: // Wirte Word Addr

34. begin D1 <= C3[7:0]; i <= FF_Write2; Go <= i + 1'b1; end

35. ...

36. 7:

37. begin C3 <= C3 + 1'b1; isDone[0] <= 1'b1; i <= i + 1'b1; end

38. ...

39. endcase

40.

41. ...

42. assign oTag[1] = ( (C2[8]^C3[8]) && (C2[7:0] == C3[7:0]) );

43. assign oTag[0] = ( C2 == C3 );

44.

45. endmodule

程式碼17.4

如程式碼17.4所示,第3~7行是相關的出入端宣告。第12行建立相關的暫存器,C2為寫指標,C3為讀指標,位寬為 N + 1。第23~24行表示C2[7:0]為寫資料地址。第26~27行表示C2遞增。第33~34行表示C3[7:0]為讀資料地址。第36~37行表示C3遞增。第42行表示寫滿狀態,第43行則表示讀空狀態。完後,我們便可以開始建模了。

clip_image028

圖17.14 實驗十七的建模圖。

圖17.14是實驗十七的建模圖,周邊操作為 IIC 儲存模組寫入資料,核心操作則從哪裡讀取資料,並且將讀出的資料驅動數碼管基礎模組。

iic_savemod.v

clip_image030

圖17.15 IIC儲存模組。

圖17.15是IIC儲存模組的建模圖,左方是寫入操作,右邊是讀出操作,上方則是連結至頂層訊號 SCL 與 SDA。

1. module iic_savemod

2. (

3. input CLOCK, RESET,

4. output SCL,

5. inout SDA,

6. input [1:0]iCall,

7. output [1:0]oDone,

8. input [7:0]iData,

9. output [7:0]oData,

10. output [1:0]oTag

11. );

12. parameter FCLK = 10'd125, FHALF = 10'd62, FQUARTER = 10'd31; //(1/400E+3)/(1/50E+6)

13. parameter THIGH = 10'd30, TLOW = 10'd65, TR = 10'd15, TF = 10'd15;

14. parameter THD_STA = 10'd30, TSU_STA = 10'd30, TSU_STO = 10'd30;

15. parameter FF_Write1 = 5'd7;

16. parameter FF_Write2 = 5'd9, FF_Read = 5'd19;

17.

18. /***************/

19.

20. reg [1:0]C7;

21. reg [1:0]isDo;

22.

23. always @ ( posedge CLOCK or negedge RESET )

24. if( !RESET )

25. begin

26. C7 <= 2'b10;

27. isDo <= 2'b00;

28. end

29. else

30. begin

31.

32. if( iCall[1] & C7[1] ) isDo[1] <= 1'b1;

33. else if( iCall[0] & C7[0] ) isDo[0] <= 1'b1;

34.

35. if( isDo[1] & isDone[1] ) isDo[1] <= 1'b0;

36. else if( isDo[0] & isDone[0] ) isDo[0] <= 1'b0;

37.

38. if( isDone ) C7 <= {isDo[0],isDo[1]};

39. else if( iCall ) C7 <= { C7[0], C7[1] };

40.

41. end

42.

43.

44. /***************/

45.

46. reg [4:0]i;

47. reg [4:0]Go;

48. reg [9:0]C1;

49. reg [7:0]D1;

50. reg [1:0]isDone;

51. reg [8:0]C2,C3; // C2 Write Pointer, C3 Read Pointer

52. reg rSCL,rSDA;

53. reg isAck,isQ;

54.

55. always @ ( posedge CLOCK or negedge RESET )

56. if( !RESET )

57. begin

58. { i,Go } <= { 5'd0,5'd0 };

59. C1 <= 10'd0;

60. D1 <= 8'd0;

61. isDone <= 2'd0;

62. { C2, C3 } <= 18'd0;

63. { rSCL,rSDA,isAck,isQ } <= 4'b1111;

64. end

65. else if( isDo[1] )

66. case( i )

67.

68. 0: // Call

69. begin

70. isQ = 1;

71. rSCL <= 1'b1;

72.

73. if( C1 == 0 ) rSDA <= 1'b1; 

74. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

75.

76. if( C1 == (FCLK) -1) begin C1 <= 10'd0; i <= i + 1'b1; end

77. else C1 <= C1 + 1'b1;

78. end

79.

80. 1: // Write Device Addr

81. begin D1 <= {4'b1010, 3'b000, 1'b0}; i <= 5'd7; Go <= i + 1'b1; end

82.

83. 2: // Wirte Word Addr

84. begin D1 <= C2[7:0]; i <= FF_Write1; Go <= i + 1'b1; end

85.

86. 3: // Write Data

87. begin D1 <= iData; i <= FF_Write1; Go <= i + 1'b1; end

88.

89. /*************************/

90.

91. 4: // Stop

92. begin

93. isQ = 1'b1;

94.

95. if( C1 == 0 ) rSCL <= 1'b0;

96. else if( C1 == FQUARTER ) rSCL <= 1'b1; 

97.

98. if( C1 == 0 ) rSDA <= 1'b0;

99. else if( C1 == (FQUARTER + TR + TSU_STO ) ) rSDA <= 1'b1;

100.

101. if( C1 == (FQUARTER + FCLK) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

102. else C1 <= C1 + 1'b1; 

103. end

104.

105. 5:

106. begin C2 <= C2 + 1'b1; isDone[1] <= 1'b1; i <= i + 1'b1; end

107.

108. 6: 

109. begin isDone[1] <= 1'b0; i <= 5'd0; end

110.

111. /*******************************/ //function

112.

113. 7,8,9,10,11,12,13,14:

114. begin

115. isQ = 1'b1;

116. rSDA <= D1[14-i];

117.

118. if( C1 == 0 ) rSCL <= 1'b0;

119. else if( C1 == (TF + TLOW) ) rSCL <= 1'b1; 

120.

121. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

122. else C1 <= C1 + 1'b1;

123. end

124.

125. 15: // waiting for acknowledge

126. begin

127. isQ = 1'b0;

128. if( C1 == FHALF ) isAck <= SDA;

129.

130. if( C1 == 0 ) rSCL <= 1'b0;

131. else if( C1 == FHALF ) rSCL <= 1'b1;

132.

133. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

134. else C1 <= C1 + 1'b1; 

135. end

136.

137. 16:

138. if( isAck != 0 ) i <= 5'd0;

139. else i <= Go; 

140.

141. /*******************************/ // end function

142.

143. endcase

144.

145. else if( isDo[0] ) 

146. case( i )

147.

148. 0: // Call

149. begin

150. isQ = 1; 

151. rSCL <= 1'b1;

152.

153. if( C1 == 0 ) rSDA <= 1'b1; 

154. else if( C1 == (TR + THIGH) ) rSDA <= 1'b0;

155.

156. if( C1 == FCLK -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

157. else C1 <= C1 + 1'b1;

158. end

159.

160. 1: // Write Device Addr

161. begin D1 <= {4'b1010, 3'b000, 1'b0}; i <= 5'd9; Go <= i + 1'b1; end

162.

163. 2: // Wirte Word Addr

164. begin D1 <= C3[7:0]; i <= FF_Write2; Go <= i + 1'b1; end

165.

166. 3: // Start again

167. begin

168. isQ = 1'b1;

169.

170. if( C1 == 0 ) rSCL <= 1'b0;

171. else if( C1 == FQUARTER ) rSCL <= 1'b1;

172. else if( C1 == (FQUARTER + TR + TSU_STA + THD_STA + TF) ) rSCL <= 1'b0;

173.

174. if( C1 == 0 ) rSDA <= 1'b0; 

175. else if( C1 == FQUARTER ) rSDA <= 1'b1;

176. else if( C1 == ( FQUARTER + TR + THIGH) ) rSDA <= 1'b0;

177.

178. if( C1 == (FQUARTER + FCLK + FQUARTER) -1 ) begin C1 <= 10'd0; i <= i + 1'b1; end

179. else C1 <= C1 + 1'b1;

180. end

181.

182. 4: // Write Device Addr ( Read )

183. begin D1 <= {