實驗十九:SDRAM模塊② — 多字讀寫
表示19.1 Mode Register的內容。
Mode Register |
||||||||||||
A12 |
A11 |
A10 |
A9 |
A8 |
A7 |
A6 |
A5 |
A4 |
A3 |
A2 |
A1 |
A0 |
0 |
0 |
OP Code |
0 |
0 |
CAS Latency |
BT |
Burst Length |
|
|
||||||||||||||||||||||||||||||||||||||
|
|
實驗十八我們實現單字讀寫,實驗十九則要實現多字讀寫。表19.1告訴我們,A2~A0控制Burst Length的長度,為了實現長度為4的字讀寫,A2~A0的內容設置為3’b010。
7: // Send LMR Cmd. Burst Read & Write, 3'b011 mean CAS latecy = 3, Sequentia 4 burst length
begin rCMD <= _LMR; rBA <= 2'b11; rA <= { 3'd0, 1'b0, 2'd0, 3'b011, 1'b0, 3'b010 }; i <= i + 1'b1; end
代碼19.2
如代碼基本上,初始化的大致過程與實驗十八沒有什么兩樣,僅有更改 Mode Register 的內容,結果如代碼19.2所示。對此,寫操作還有讀操作因為讀寫字節改變,所以時序也稍微改變了一下。
多字寫操作:
圖19.1 多字寫操作的理想時序圖。
圖19.1是多字寫操作的理想時序圖。T1~T3基本上沒有什么改變,反之接續的T4~T6會依據Burst Length設置的長度而有所改變。由於 Burst Length 設置為 4,結果T3寫第一字數據,T4寫第二字數據,T5寫第三字數據,T6則寫第四字,然而大致的過程如下所示:
l T1,發送ACT命令,BANK地址與行地址;
l T1半周期,SDRAM讀取;
l T2,滿足TRCD;
l T3,發送WR命令,BANK地址與列地址,還有寫第一字數據;
l T3半周期,SDRAM讀取;
l T4,寫第二字數據;
l T4半周期,SDRAM讀取;
l T5,寫第三字數據;
l T5半周期,SDRAM讀取;
l T6,寫第四字數據;
l T6半周期,SDRAM讀取;
l T7,滿足TWR;
l T8,滿足TRP。
Verilog則可以這樣描述,結果如代碼19.2所示:
1. 1: // Send Active Command with Bank and Row address
2. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
3.
4. 2: // wait TRCD 20ns
5. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
6. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
7.
8. 3: // Send Write command with row address, pull up A10 to PR
9. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= {4'b0010,iAddr[8:0]}; D1 <= iData[63:48]; i <= i + 1'b1; end
10.
11. 4:
12. begin rCMD <= _NOP; D1 <= iData[47:32]; i <= i + 1'b1; end
13.
14. 5:
15. begin rCMD <= _NOP; D1 <= iData[31:16]; i <= i + 1'b1; end
16.
17. 6:
18. begin rCMD <= _NOP; D1 <= iData[15:0]; i <= i + 1'b1; end
19.
20. 7: // wait TWR 2 clock
21. if( C1 == TWR -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
22. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
23.
24. 8: // wait TRP 20ns
25. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
26. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
代碼19.2
假設多字讀寫由高至低,那么步驟3寫入iData[63:48],步驟4寫入 iData[47:32],步驟5寫入 iData[31:16],步驟6寫 iData[15:0]。
多字讀操作:
圖19.2 多字讀操作的理想時序圖。
圖19.2是多字讀操作的理想時序圖,大致過程如下:
l T1,發送ACT命令,BANK地址與行地址;
l T1半周期,SDRAM讀取;
l T2,滿足TRCD;
l T3,發送RD命令,BANK地址與列地址;
l T3半周期,SDRAM讀取命令;
l T4,滿足 CAS Latency;
l T5,讀取第一字數據;
l T6,讀取第二字數據;
l T7,讀取第三字數據;
l T8,讀取第四字數據。
多字讀操作相較單字讀操作稍微有一些不同,不同的地方除了讀取數據的字變長以外,還有 TRP滿足在最后兩字之中。至於Verilog則可以這樣描述,結果如代碼19.3所示:
1. 1: // Send Active command with Bank and Row address
2. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
3.
4. 2: // wait TRCD 20ns
5. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
6. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
7.
8. 3: // Send Read command and column address, pull up A10 to PR
9. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0]}; i <= i + 1'b1; end
10.
11. 4: // wait CL 3 clock
12. if( C1 == CL -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
13. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
14.
15. 5: // Read Data
16. begin T[63:48] <= S_DQ; i <= i + 1'b1; end
17.
18. 6: // Read Data
19. begin T[47:32] <= S_DQ; i <= i + 1'b1; end
20.
21. 7: // Read Data
22. begin T[31:16] <= S_DQ; i <= i + 1'b1; end
23.
24. 8: // Read Data
25. begin T[15:0] <= S_DQ; i <= i + 1'b1; end
代碼19.3
代碼19.3也沒有什么好解釋的,基本上完全根據圖19.2描述。理解完畢以后,我們就可以開始建模了。
圖19.3 SDRAM基礎模塊的建模圖。
圖19.3是SDRAM基礎模塊的建模圖,這家伙比較實驗十八,最大的區別就是iData與oData的位寬增大而已。
sdram_funcmod.v
圖19.4 SDRAM功能模塊的建模圖。
圖19.4是SDRAM功能模塊的建模圖,具體內容我們還是來看代碼吧。
1. module sdram_funcmod
2. (
3. input CLOCK,
4. input RESET,
5.
6. output S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE,
7. output [1:0]S_BA, //2
8. output [12:0]S_A, //12, CA0~CA8, RA0~RA12, BA0~BA1, 9+13+2 = 24;
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11.
12. input [3:0]iCall,
13. output oDone,
14. input [23:0]iAddr, // [23:22]BA,[21:9]Row,[8:0]Column
15. input [63:0]iData,
16. output [63:0]oData
17. );
以上內容為相關的出入端聲明,注意第15~16行的位寬增大至64位。
18. parameter T100US = 14'd13300;
19. // tRP 20ns, tRRC 63ns, tRCD 20ns, tMRD 2CLK, tWR/tDPL 2CLK, CAS Latency 3CLK
20. parameter TRP = 14'd3, TRRC = 14'd9, TMRD = 14'd2, TRCD = 14'd3, TWR = 14'd2, CL = 14'd3;
21. parameter _INIT = 5'b01111, _NOP = 5'b10111, _ACT = 5'b10011, _RD = 5'b10101, _WR = 5'b10100,
22. _BSTP = 5'b10110, _PR = 5'b10010, _AR = 5'b10001, _LMR = 5'b10000;
23.
以上內容為相關的常量聲明。
24. reg [4:0]i;
25. reg [13:0]C1;
26. reg [15:0]D1;
27. reg [63:0]T;
28. reg [4:0]rCMD;
29. reg [1:0]rBA;
30. reg [12:0]rA;
31. reg [1:0]rDQM;
32. reg isOut;
33. reg isDone;
34.
35. always @ ( posedge CLOCK or negedge RESET )
36. if( !RESET )
37. begin
38. i <= 4'd0;
39. C1 <= 14'd0;
40. D1 <= 16'd0;
41. T <= 64'd0;
42. rCMD <= _NOP;
43. rBA <= 2'b11;
44. rA <= 13'h1fff;
45. rDQM <= 2'b00;
46. isOut <= 1'b1;
47. isDone <= 1'b0;
48. end
以上內容為相關的寄存器聲明與復位操作,注意寄存器T是用來暫存讀取數據。
49. else if( iCall[3] )
50. case( i )
51.
52. 0: // Set IO to output State
53. begin isOut <= 1'b1; i <= i + 1'b1; end
54.
55. 1: // Send Active Command with Bank and Row address
56. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
57.
58. 2: // wait TRCD 20ns
59. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
60. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
61.
62. /*********************************************/
63.
64. 3: // Send Write command with row address, pull up A10 to PR
65. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0] }; D1 <= iData[63:48]; i <= i + 1'b1; end
66.
67. 4:
68. begin rCMD <= _NOP; D1 <= iData[47:32]; i <= i + 1'b1; end
69.
70. 5:
71. begin rCMD <= _NOP; D1 <= iData[31:16]; i <= i + 1'b1; end
72.
73. 6:
74. begin rCMD <= _NOP; D1 <= iData[15:0]; i <= i + 1'b1; end
75.
76. /**********************************************/
77.
78. 7: // wait TWR 2 clock
79. if( C1 == TWR -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
80. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
81.
82. 8: // wait TRP 20ns
83. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
84. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
85.
86. /*******************/
87.
88. 9: // Generate done signal
89. begin isDone <= 1'b1; i <= i + 1'b1; end
90.
91. 10:
92. begin isDone <= 1'b0; i <= 4'd0; end
93.
94. endcase
以上內容為部分核心操作。注意步驟3~6,D1用來驅動SD_Q,而iData賦值D1,次序由高至低。
95. else if( iCall[2] )
96. case( i )
97.
98. 0:
99. begin isOut <= 1'b0; D1 <= 16'd0; i <= i + 1'b1; end
100.
101. 1: // Send Active command with Bank and Row address
102. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
103.
104. 2: // wait TRCD 20ns
105. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
106. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
107.
108. /********************/
109.
110. 3: // Send Read command and column address, pull up A10 to PR
111. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0010, iAddr[8:0]}; i <= i + 1'b1; end
112.
113. 4: // wait CL 3 clock
114. if( C1 == CL -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
115. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
116.
117. /********************/
118.
119. 5: // Read Data
120. begin T[63:48] <= S_DQ; i <= i + 1'b1; end
121.
122. 6: // Read Data
123. begin T[47:32] <= S_DQ; i <= i + 1'b1; end
124.
125. 7: // Read Data
126. begin T[31:16] <= S_DQ; i <= i + 1'b1; end
127.
128. 8: // Read Data
129. begin T[15:0] <= S_DQ; i <= i + 1'b1; end
130.
131. /********************/
132.
133. 9: // Generate done signal
134. begin rCMD <= _NOP; isDone <= 1'b1; i <= i + 1'b1; end
135.
136. 10:
137. begin isDone <= 1'b0; i <= 4'd0; end
138.
139. endcase
以上內容為部分核心操作。注意步驟5~8,寄存器T用來暫存讀數據,次序由高至低。
140. else if( iCall[1] )
141. case( i )
142.
143. 0: // Send Precharge Command
144. begin rCMD <= _PR; i <= i + 1'b1; end
145.
146. 1: // wait TRP 20ns
147. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
148. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
149.
150. 2: // Send Auto Refresh Command
151. begin rCMD <= _AR; i <= i + 1'b1; end
152.
153. 3: // wait TRRC 63ns
154. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
155. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
156.
157. 4: // Send Auto Refresh Command
158. begin rCMD <= _AR; i <= i + 1'b1; end
159.
160. 5: // wait TRRC 63ns
161. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
162. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
163.
164. /********************/
165.
166. 6: // Generate done signal
167. begin isDone <= 1'b1; i <= i + 1'b1; end
168.
169. 7:
170. begin isDone <= 1'b0; i <= 4'd0; end
171.
172. endcase
以上內容為部分核心操作。刷新操作,基本上沒有什么改變。
173. else if( iCall[0] )
174. case( i )
175.
176. 0: // delay 100us
177. if( C1 == T100US -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
178. else begin C1 <= C1 + 1'b1; end
179.
180. /********************/
181.
182. 1: // Send Precharge Command
183. begin rCMD <= _PR; { rBA, rA } <= 15'h3fff; i <= i + 1'b1; end
184.
185. 2: // wait TRP 20ns
186. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
187. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
188.
189. 3: // Send Auto Refresh Command
190. begin rCMD <= _AR; i <= i + 1'b1; end
191.
192. 4: // wait TRRC 63ns
193. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
194. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
195.
196. 5: // Send Auto Refresh Command
197. begin rCMD <= _AR; i <= i + 1'b1; end
198.
199. 6: // wait TRRC 63ns
200. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
201. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
202.
203. /********************/
204.
205. 7: // Send LMR Cmd. Burst Read & Write, 3'b011 mean CAS latecy = 3, Sequentia 4 burst length
206. begin rCMD <= _LMR; rBA <= 2'b11; rA <= { 3'd0, 1'b0, 2'd0, 3'b011, 1'b0, 3'b010 }; i <= i + 1'b1; end
207.
208. 8: // Send 2 nop CLK for tMRD
209. if( C1 == TMRD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
210. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
211.
212. /********************/
213.
214. 9: // Generate done signal
215. begin isDone <= 1'b1; i <= i + 1'b1; end
216.
217. 10:
218. begin isDone <= 1'b0; i <= 4'd0; end
219.
220. endcase
221.
以上內容為部分核心操作。初始化操作,注意步驟7,Burst Length 設置為 3’b010。
222. assign { S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE } = rCMD;
223. assign { S_BA, S_A } = { rBA, rA };
224. assign S_DQM = rDQM;
225. assign S_DQ = isOut ? D1 : 16'hzzzz;
226. assign oDone = isDone;
227. assign oData = T;
228.
229. endmodule
以上內容為相關的輸出驅動,D1驅動S_DQ,T驅動oData。
sdram_ctrlmod.v
該控制模塊的內容基本上與實驗十八一模一樣,筆者就不重復粘貼了。
sdram_demo.v
圖19.5 實驗十九的建模圖。
圖19.5是實驗十九的建模圖,雖然外觀上改變不大,最多只是Data的位寬改為64位而已 ... 話雖如此,核心操作則有點不同,具體的內容讓我們來看代碼吧。
1. module sdram_demo
2. (
3. input CLOCK,
4. input RESET,
5. output S_CLK,
6. output S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE,
7. output [12:0]S_A,
8. output [1:0]S_BA,
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11. output TXD
12. );
以上內容為相關的出入端聲明。
13. wire CLOCK1,CLOCK2;
14.
15. pll_module U1
16. (
17. .inclk0 ( CLOCK ), // 50Mhz
18. .c0 ( CLOCK1 ), // 133Mhz -210 degree phase
19. .c1 ( CLOCK2 ) // 133Mhz
20. );
21.
以上內容為PLL模塊的實例化。
22. wire [1:0]DoneU2;
23. wire [63:0]DataU2;
24.
25. sdram_basemod U2
26. (
27. .CLOCK( CLOCK1 ),
28. .RESET( RESET ),
29. .S_CKE( S_CKE ),
30. .S_NCS( S_NCS ),
31. .S_NRAS( S_NRAS ),
32. .S_NCAS( S_NCAS ),
33. .S_NWE( S_NWE ),
34. .S_A( S_A ),
35. .S_BA( S_BA ),
36. .S_DQM( S_DQM ),
37. .S_DQ( S_DQ ),
38. .iCall( isCall ),
39. .oDone( DoneU2 ),
40. .iAddr( {D1,2’b00} ),
41. .iData( D2 ),
42. .oData( DataU2 )
43. );
44.
以上內容為sdram基礎模塊的實例化。
45. parameter B115K2 = 11'd1157, TXFUNC = 6'd16;
46.
47. reg [5:0]i,Go;
48. reg [10:0]C1;
49. reg [21:0]D1;
50. reg [63:0]D2,D3;
51. reg [10:0]T;
52. reg [1:0]isCall;
53. reg rTXD;
54.
55. always @ ( posedge CLOCK1 or negedge RESET )
56. if( !RESET )
57. begin
58. i <= 6'd0;
59. Go <= 6'd0;
60. C1 <= 11'd0;
61. D1 <= 22'd0;
62. D2 <= 64'd0;
63. D3 <= 64'd0;
64. T <= 11'd0;
65. isCall <= 2'b00;
66. rTXD <= 1'b1;
67. end
以上內容為相關的寄存器聲明還有復位操作。第45行是波特率為115200的常量聲明還有偽函數入口。
68. else
69. case( i )
70.
71. 0:
72. if( DoneU2[1] ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
73. else begin isCall[1] <= 1'b1; D1 <= 22'd0; D2 <= 64'hAABBCCDDEEFF8899; end
74.
75. 1:
76. if( DoneU2[0] ) begin D3 <= DataU2; isCall[0] <= 1'b0; i <= i + 1'b1; end
77. else begin isCall[0] <= 1'b1; D1 <= 22'd0; end
78.
79. 2:
80. begin T <= { 2'b11, D3[63:56], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
81.
82. 3:
83. begin T <= { 2'b11, D3[55:48], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
84.
85. 4:
86. begin T <= { 2'b11, D3[47:40], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
87.
88. 5:
89. begin T <= { 2'b11, D3[39:32], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
90.
91. 6:
92. begin T <= { 2'b11, D3[31:24], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
93.
94. 7:
95. begin T <= { 2'b11, D3[23:16], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
96.
97. 8:
98. begin T <= { 2'b11, D3[15:8], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
99.
100. 9:
101. begin T <= { 2'b11, D3[7:0], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
102.
103. 10:
104. i <= i;
105.
106. /******************************/
107.
以上內容為部分核心操作。步驟0將64位的數據寫入地址0,步驟1則將數據從地址0讀取。步驟2~9則是輪番將數據送出去。
108. 16,17,18,19,20,21,22,23,24,25,26:
109. if( C1 == B115K2 -1 ) begin C1 <= 11'd0; i <= i + 1'b1; end
110. else begin rTXD <= T[i - 16]; C1 <= C1 + 1'b1; end
111.
112. 27:
113. i <= Go;
114.
115. endcase
116.
117. assign S_CLK = CLOCK2;
118. assign TXD = rTXD;
119.
120. endmodule
以上內容為部分核心操作。步驟16~27是發送一幀數據的偽函數。第117~118行則是相關的輸出驅動。綜合完畢並且下載程序,如果串口調試程序出現數據 AABBCCDDEEFF8899,結果表示實驗成功。
細節一:完整的個體模塊
本實驗的SDRAM基礎模塊已經准備就緒。
細節二:讀寫地址的驅動方式
1. sdram_basemod
2. (
3. ...
4. iAddr( {D1,2’b00} ),
5. ...
6. );
7. reg [21:0]D1;
8. ...
9. always @ ( posedge CLOCK1 )
10. ...
11. case( i )
12. 0:
13. if( ... ) ...
14. else D1 <= 22'd0; ...
代碼19.4
代碼19.4是sdram_demo的部分內容,其中iAddr由22位寬的D1與2’b00聯合驅動,好奇的朋友一定會覺得疑惑“為什么”?其實這是經過深思以后的寫法。實驗十九是多字讀寫操作,其中長度為4,或者說地址的偏移量為4,所以iAddr[1:0] 所指定的范圍基本作廢。為了正式這點問題,代碼19.4需要這樣表達。
1. sdram_basemod
2. (
3. ...
4. iAddr( D1 ),
5. ...
6. );
7. reg [23:0]D1;
8. ...
9. always @ ( posedge CLOCK1 )
10. ...
11. case( i )
12. 0:
13. if( ... ) ...
14. else D1 <= 24'd1; ...
代碼19.5
如代碼19.5所示,假設我們無視這個問題,直接使用24位寬的D1驅動iAddr,然后將數據寫入地址24’d1。根據SDRAM的內部操作,數據會依序寫入地址為1234,而不是 0123 或者 4567 之類 ... 如此一來,我們會不小心破壞地址的偏移量。