实验二十一:SDRAM模块④ — 页读写 β
未进入主题之前,让我们先来谈谈一些重要的体外话。《整合篇》之际,笔者曾经比拟Verilog如何模仿for循环,我们知道for循环是顺序语言的产物,如果Verilog要实现属于自己的for循环,那么它要考虑的东西除了步骤以外,还有非常关键的时钟。
for( i=0; i<4; i++ ) 操作A;
i = 0;
while ( i<4 ) { 操作A;i++;}
i = 0;
do { 操作A; i++; } while(i = 3)
代码21.1
代码2.11有三段经典的循环操作,即for循环,while循环,还有do ... while循环。如果三个循环互相交流的话,谁又是谁的朋友呢?为了解决这个问题,我们必须先建立交友标准,即先执行后判断,还是先判断后执行? 如此一来,我们可以百分百确信 for循环与while循环才是好朋友,因为两者都是先判断后执行的类型。反之do ... while循环则是先执行后判断的类型。
为了验证上述的接着,我们试着解剖一下种循环的执行过程 ...
for循环:
清零i
i为0,判断i是否小于4,如是执行操作A第0次,递增i为1;
i为1,判断i是否小于4,如是执行操作A第1次,递增i为2;
i为2,判断i是否小于4,如是执行操作A第2次,递增i为3;
i为3,判断i是否小于4,如是执行操作A第3次,递增i为4;
i为4,判断i是否小于4,不是结束循环。
while循环:
清零i;
i为0,判断i是否小于4,如是执行操作A第0次,递增i为1;
i为1,判断i是否小于4,如是执行操作A第1次,递增i为2;
i为2,判断i是否小于4,如是执行操作A第2次,递增i为3;
i为3,判断i是否小于4,如是执行操作A第3次,递增i为4;
i为4,判断i是否小于4,不是结束循环。
do ... while循环:
清零i;
i为0,执行操作A第0次,i递增为1,判断i是否小于4,如是继续;
i为1,执行操作A第1次,i递增为2,判断i是否小于4,如是继续;
i为2,执行操作A第2次,i递增为3,判断i是否小于4,如是继续;
i为3,执行操作A第3次,i递增为4,判断i是否小于4,不是则结束循环;
读者可能会很奇怪,执行与判断的次序究竟有什么好困惑?作为一只小气鬼,笔者会非常执著小细节。Verilog是并行性质的语言,执行与判断有可能同时执行,也有可能并非同时执行 ... 根据直觉,笔者相信Verilog的循环操作更加适合先执行后判断,而不是先判断后执行。
1. 0:
2. begin
3. if( C1 == 0 ) isEn <= 1’b1;
4. else if( C1 == 4-2 ) isEn <= 1’b0;
5.
6. if( C1 == 4 -1 ) begin C1 <= 4’d0; i <= i + 1’b1; end
7. else C1 <= C1 + 1’b1;
8. end
代码21.1
如代码21.1所示,第6~7行表示步骤0保持4个时钟,其中 C1为0拉高isEn,C1为4-2的拉低 isEn;代码21.1告诉我们,执行与判断是同时进行,然而从解读代码的顺序来看,笔者更加倾向“先执行后判断”这种结构性
图21.1 代码21.1的时序图。
图21.1是代码21.1所描述的时序图,其中isEn拉高是否完全根据C1的计数。T0之前也是复位的状态,C1为0。T0之际,C1为0(过去值)拉高isEn,C1为2(过去值)拉低isEn。
1. 0:
2. begin isEn <= 1’b1; i <= i + 1’b1; end
3. 1:
4. begin isEn <= 1’b0; i <= i + 1’b1; end
5. 2:
6. if( C1 == 4 -1 ) begin C1 <= 4’d0; i <= i + 1’b1; end
7. else begin C1 <= C1 + 1’b1; i <= 4’d0; end
代码21.2
同样的结构性甚至可以衍生至不同的操作,如代码21.2所示。步骤0拉高isEn,步骤1拉低isEn,步骤2判断。如果步骤0~2来回重复,isEn一共拉高4次,或者说isEn产生四个高脉冲。
图21.2 代码21.2的理想时序图。
图21.2是代码21.2所产生的时序图。只要稍微比较图21.1与图21.2,我们会发现两者之间都有相似的“结构性”,因为两者都是倾向“先执行后判断”。
1. 0:
2. if( Done ) begin isCall[0] <= 1’b0; i <= i + 1’b1; end
3. else begin isCall[0] <= 1’b1; end
4. 1:
5. if( Done ) begin isCall[1] <= 1’b0; i <= i + 1’b1; end
6. else begin isCall[1] <= 1’b1; end
7. 2:
8. if( C1 == 4 -1 ) begin C1 <= 4’d0; i <= i + 1’b1; end
9. else begin C1 <= C1 + 1’b1; i <= 4’d0; end
代码21.3
再举例而言,如代码21.3所示,步骤0执行功能0,步骤1执行功能1,步骤2用来判断循环次数。步骤0~2之间会来回重复,直至功能0与1都执行四次,这种感觉好比代码21.4。
1. int main()
2. {
3. for( int i = 0; i < 4; i ++ )
4. {
5. Function0();
6. Function1();
7. }
8. return -1;
9. }
代码21.4
不过代码21.1~21.3都有相似的结构性,即都是先执行后判断。笔者作为热爱结构的男人,笔者会尝尽一切挖掘结构的可能性。此外,这种先执行后判断的模仿对象,笔者称为伪循环(Fake Iteration)。说完题外话,接下来让我们进入本实验的主题。
页读写之所以分为α与β,那是因为过多的数据吞吐量导致读写变成非常麻烦。天真的朋友可能会认为,如果Burst Length 为4,那么数据位宽就是 4 * 16bit。再如果 Burst Length 为 512,那么数据位宽就是 512 * 16 bit。理论上,这样说是没错,不过那样做会撑爆 FPGA的嘴巴。为此,读写入口必须加入缓冲机制才行。此刻,实验十五所学过的知识(同步FIFO)就派上用场了。
图21.3 SDRAM基础模块的建模图。
加入FIFO储存模块的SDRAM基础模块,大致上如图21.3所示。为了简化连线,笔者稍微加大FIFO的深度,对此FIFO有没有反馈状态都没有问题。还没有进入建模之前,让笔者先来解释一下,页读写与FIFO之间,究竟有什么细节需要注意。
页写操作(请求FIFO):
图21.4 页写操作的理想时序图。
图21.4是也写操作的理想时序图,相较实验二十的页写时序,实验二十一的页写时序夹杂了FIFO储存模块,其中isEn[0]是SDRAM功能模块向FIFO的读请求。时序的大致过程如下:
l T1,发送ACT命令,BANK地址与行地址;
l T1半周期,SDRAM读取;
l T2,满足TRCD;
l T3,拉高isEn[0];
l T4,发送WR命令,BANK地址与列地址,FIFO发送第0数据,;
l T4半周期,SDRAM读取
l T5,FIFO发送第1~511数据,C1为510拉低isEn[0],C1为511发送BSTP命令。
图22.4基本上已经表达非常清楚,为使FIFO可以同步发送数据,因为SDRAM功能模块必须提前一个时钟拉高isEn[0],即T3拉高isEn[0]。此外,为了不使FIFO吐出过多的数据,C1为510的时候便拉低isEn[1]。C1为511的时候则发送BSTP命令。对此,Verilog则可以这样描述,结果如代码21.5所示:
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:
9. begin isEn[0] <= 1'b1; i <= i + 1'b1; end
10.
11. 4: // Send Write command with row address,
12. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= { 4'b0000, iAddr[8:0] }; i <= i + 1'b1; end
13.
14. 5: // continue write until end and send BSTP
15. begin
16. if( C1 == 512 -2 ) begin isEn[0] <= 1'b0; end
17. if( C1 == 512 -1 ) begin rCMD <= _BSTP; C1 <= 14'd0; i <= i + 1'b1; end
18. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
19. end
代码21.5
如代码21.5所示,步骤1发送Active命令,步骤2满足TRCD,步骤3提前拉高读请求,即isEn[0]。步骤4发送 Write命令,还有写入第0数据。步骤5写入第1~511数据,C1为510的时候拉低isEn[0],C1为511的时候发送 Burst Stop 命令。
页读操作(请求FIFO):
图21.5 页读操作的理想时序图。
图21.5还是笔者自定义的页写的时序图,相较实验二十,其中多了FIFO的写请求 isEn[1],FIFO的iData,还有C1计数。一旦 CAS Latency 得到满足,SDRAM便会开始读出数据。半个周期之后(T5)FPGA读取,并且拉高isEn[1],然后将数据转交FIFO。
T6之际,512个数据读写完毕,拉低isEn[1],然后发送BSTP命令。
时序的大致过程如下:
l T1,发送ACT命令,BANK地址与行地址;
l T1半周期,SDRAM读取;
l T2,满足TRCD;
l T3,发送RD命令,BANK地址与列地址;
l T3半周期,SDRAM读取命令。
l T4,满足 CAS Latency。
l T5,拉高isEn[1],读取第0~511数据,并且向FIFO的iData写入。
l T6,拉低isEn[1],发送BSTP命令。
l T6半周期,SDRAM读取。
对此,Verilog可以这样描述,结果如代码21.6所示:
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
9. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0000, 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
17. D1 <= S_DQ; isEn[1] <= 1'b1;
18. if( C1 == 512 -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
19. else begin C1 <= C1 + 1'b1; end
20. end
21.
22. 6:
23. begin isEn[1] <= 1'b0; rCMD <= _BSTP; i <= i + 1'b1; end
代码21.6
如代码21.6所示,步骤4满足CL以后,步骤5便拉高isEn[1],并且读取512个数据。步骤6将isEn[1]拉低,并且发送命令 BSTP。理解完毕以后,我们可以开始建模了。
fifo_savemod.v
图21.6 FIFO储存模块的建模图。
图21.6是FIFO储存模块的建模图,为了简化设计,笔者不小心吃掉FIFO的输出标签,取而代之就是增加深度。虽然实验只有两只FIFO储存模块,方向也不一样,不过母体都是一样的东西。具体内容我们还是来看代码吧:
1. module fifo_savemod
2. (
3. input CLOCK, RESET,
4. input [1:0]iEn,
5. input [15:0]iData,
6. output [15:0]oData,
7. output [1:0]oTag
8. );
以上内容为相关的出入端声明。
9. initial begin
10. for( C1 = 0; C1 < 1024; C1 = C1 + 1'b1 )
11. begin RAM[ C1 ] <= 16'd0; end
12. end
13.
14. reg [15:0] RAM [1023:0];
以上内容为声明位宽为16,深度为1024的RAM,,然后将其初始化。
15. reg [10:0] C1 = 11'd0,C2 = 11'd0; // N+1
16.
17. always @ ( posedge CLOCK or negedge RESET )
18. if( !RESET )
19. begin
20. C1 <= 11'd0;
21. end
22. else if( iEn[1] )
23. begin
24. RAM[ C1[9:0] ] <= iData;
25. C1 <= C1 + 1'b1;
26. end
27.
28. always @ ( posedge CLOCK or negedge RESET )
29. if( !RESET )
30. begin
31. C2 <= 11'd0;
32. end
33. else if( iEn[0] )
34. begin
35. //D1 <= RAM[ C2[9:0] ];
36. C2 <= C2 + 1'b1;
37. end
38.
以上内容为核心内容。第15行声明写指针C1,还有读指针C2,位宽为 RAM的深度+1。第17~26行是FIFO的写操作,第28~37行是FIFO的读操作,笔者将第35行注释掉。
39. assign oData = RAM[ C2[9:0]];
40. assign oTag[1] = ( C1[10]^C2[10] & C1[9:0] == C2[9:0] ); // Full Left
41. assign oTag[0] = ( C1 == C2 ); // Empty Right
42.
43. endmodule
取而代之,笔者将RAM直接驱动 oData。好奇的朋友一定觉得疑惑?其实笔者是为了偷时钟,如果用D1驱动oData,FIFO的读数据(未来值)就会慢了半拍。所以,FIFO与SDRAM功能模块之间会同步失败。反之,RAM直接驱动输出,好比组合逻辑直接驱动输出,读取数据都是即时值。第40~41行是写满状态还有读空状态的输出驱动声明。
sdram_funcmod.v
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,
8. output [12:0]S_A,
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11.
12. output [1:0]oEn, //[1]Write [0]Read
13. input [23:0]iAddr, // [23:22]BA,[21:9]Row,[8:0]Column
14. input [15:0]iData,
15. output [15:0]oData,
16.
17. input [3:0]iCall,
18. output oDone
19. );
以上内容为相关的出入端声明。
20. parameter T100US = 14'd13300;
21. // tRP 20ns, tRRC 63ns, tRCD 20ns, tMRD 2CLK, tWR/tDPL 2CLK, CAS Latency 3CLK
22. parameter TRP = 14'd3, TRRC = 14'd9, TMRD = 14'd2, TRCD = 14'd3, TWR = 14'd2, CL = 14'd3;
23. parameter _INIT = 5'b01111, _NOP = 5'b10111, _ACT = 5'b10011, _RD = 5'b10101, _WR = 5'b10100,
24. _BSTP = 5'b10110, _PR = 5'b10010, _AR = 5'b10001, _LMR = 5'b10000;
25.
以上内容为相关的常量声明。
26. reg [4:0]i;
27. reg [13:0]C1;
28. reg [15:0]D1;
29. reg [4:0]rCMD;
30. reg [1:0]rBA;
31. reg [12:0]rA;
32. reg [1:0]rDQM;
33. reg [1:0]isEn;
34. reg isOut;
35. reg isDone;
36.
37. always @ ( posedge CLOCK or negedge RESET )
38. if( !RESET )
39. begin
40. i <= 4'd0;
41. C1 <= 14'd0;
42. D1 <= 16'd0;
43. rCMD <= _NOP;
44. rBA <= 2'b11;
45. rA <= 13'h1fff;
46. rDQM <= 2'b00;
47. isEn <= 2'b00;
48. isOut <= 1'b1;
49. isDone <= 1'b0;
50. end
以上内容为相关的寄存器声明与复位操作。
51. else if( iCall[3] )
52. case( i )
53.
54. 0: // Set IO to output Tag
55. begin isOut <= 1'b1; i <= i + 1'b1; end
56.
57. 1: // Send Active Command with Bank and Row address
58. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
59.
60. 2: // wait TRCD 20ns
61. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
62. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
63.
64. /*********************************************/
65.
66. 3:
67. begin isEn[0] <= 1'b1; i <= i + 1'b1; end
68.
69. 4: // Send Write command with row address
70. begin rCMD <= _WR; rBA <= iAddr[23:22]; rA <= { 4'b0000, iAddr[8:0] }; i <= i + 1'b1; end
71.
72. 5: // continue write until end and send BSTP
73. begin
74. if( C1 == 512 -2 ) begin isEn[0] <= 1'b0; end
75. if( C1 == 512 -1 ) begin rCMD <= _BSTP; C1 <= 14'd0; i <= i + 1'b1; end
76. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
77. end
78.
79. /**********************************************/
80.
81. 6: // Generate done signal
82. begin rCMD <= _NOP; isDone <= 1'b1; i <= i + 1'b1; end
83.
84. 7:
85. begin isDone <= 1'b0; i <= 4'd0; end
86.
87. endcase
以上内容为部分核心操作。以上内容是写操作,步骤3提前请求FIFO,步骤4写入第0数据,步骤5写入第1~511数据,然后发送BSTP命令。步骤6发送 NOP命令之余,也用来产生完成信号。
88. else if( iCall[2] )
89. case( i )
90.
91. 0:
92. begin isOut <= 1'b0; D <= 16'd0; i <= i + 1'b1; end
93.
94. 1: // Send Active command with Bank and Row address
95. begin rCMD <= _ACT; rBA <= iAddr[23:22]; rA <= iAddr[21:9]; i <= i + 1'b1; end
96.
97. 2: // wait TRCD 20ns
98. if( C1 == TRCD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
99. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
100.
101. /********************/
102.
103. 3: // Send Read command and column address
104. begin rCMD <= _RD; rBA <= iAddr[23:22]; rA <= { 4'b0000, iAddr[8:0]}; i <= i + 1'b1; end
105.
106. 4: // wait CL 3 clock
107. if( C1 == CL -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
108. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
109.
110. /********************/
111.
112. 5: // Read Data
113. begin
114. D1 <= S_DQ; isEn[1] <= 1'b1;
115. if( C1 == 512 -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
116. else begin C1 <= C1 + 1'b1; end
117. end
118.
119. /********************/
120.
121. 6:
122. begin isEn[1] <= 1'b0; rCMD <= _BSTP; i <= i + 1'b1; end
123.
124. /******************/
125.
126. 7: // Generate done signal
127. begin rCMD <= _NOP; isDone <= 1'b1; i <= i + 1'b1; end
128.
129. 8:
130. begin isDone <= 1'b0; i <= 4'd0; end
131.
132. endcase
以上内容为部分核心操作。以上内容是读操作。步骤4满足CL以后,步骤5读取并且发送512个数据给FIFO。步骤6,拉低isEn[1]然后发送BSTP命令。步骤7发送NOP命令,然后产生完成信号。
133. else if( iCall[1] )
134. case( i )
135.
136. 0: // Send Precharge Command
137. begin rCMD <= _PR; i <= i + 1'b1; end
138.
139. 1: // wait TRP 20ns
140. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
141. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
142.
143. 2: // Send Auto Refresh Command
144. begin rCMD <= _AR; i <= i + 1'b1; end
145.
146. 3: // wait TRRC 63ns
147. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
148. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
149.
150. 4: // Send Auto Refresh Command
151. begin rCMD <= _AR; i <= i + 1'b1; end
152.
153. 5: // 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. /********************/
158.
159. 6: // Generate done signal
160. begin isDone <= 1'b1; i <= i + 1'b1; end
161.
162. 7:
163. begin isDone <= 1'b0; i <= 4'd0; end
164.
165. endcase
以上内容为部分核心操作。以上内容是刷新操作。
166. else if( iCall[0] )
167. case( i )
168.
169. 0: // delay 100us
170. if( C1 == T100US -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
171. else begin C1 <= C1 + 1'b1; end
172.
173. /********************/
174.
175. 1: // Send Precharge Command
176. begin rCMD <= _PR; { rBA, rA } <= 15'h3fff; i <= i + 1'b1; end
177.
178. 2: // wait TRP 20ns
179. if( C1 == TRP -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
180. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
181.
182. 3: // Send Auto Refresh Command
183. begin rCMD <= _AR; i <= i + 1'b1; end
184.
185. 4: // wait TRRC 63ns
186. if( C1 == TRRC -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
187. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
188.
189. 5: // Send Auto Refresh Command
190. begin rCMD <= _AR; i <= i + 1'b1; end
191.
192. 6: // 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. /********************/
197.
198. 7: // Send LMR Cmd. Burst Read & Write, 3'b010 mean CAS latecy = 3, Sequential, 1 burst length
199. begin rCMD <= _LMR; rBA <= 2'b11; rA <= { 3'd0, 1'b0, 2'd0, 3'b011, 1'b0, 3'b111 }; i <= i + 1'b1; end
200.
201. 8: // Send 2 nop CLK for tMRD
202. if( C1 == TMRD -1 ) begin C1 <= 14'd0; i <= i + 1'b1; end
203. else begin rCMD <= _NOP; C1 <= C1 + 1'b1; end
204.
205. /********************/
206.
207. 9: // Generate done signal
208. begin isDone <= 1'b1; i <= i + 1'b1; end
209.
210. 10:
211. begin isDone <= 1'b0; i <= 4'd0; end
212.
213. endcase
214.
以上内容为部分核心操作。以上内容是初始化,注意步骤7的设置内容,Burst Length 设置为 3’b111。
215. assign { S_CKE, S_NCS, S_NRAS, S_NCAS, S_NWE } = rCMD;
216. assign { S_BA, S_A } = { rBA, rA };
217. assign S_DQM = rDQM;
218. assign S_DQ = isOut ? iData : 16'hzzzz;
219. assign oEn = isEn;
220. assign oDone = isDone;
221. assign oData = D1;
222.
223. endmodule
以上内容为相关的输出驱动。注意,第218行表示FIFO直接驱动 S_DQ的输出。
sdram_ctrlmod.v
该控制模块不曾修改,所以笔者就不用重复粘贴了。
sdram_basemod.v
图21.7 SDRAM基础模块的建模图。
图21.7是SDRAM基础模块的建模图,其中控制模块只是负责上级调用,还有SDRAM功能模块的调用而已。SDRAM功能模块的读出操作经由FIFO储存模块缓冲,然后再由上级读写数据。Addr地址信号也是上级调用。注意,由于SDRAM基础模块的连线部署稍微复杂一点,为此图21.7稍微不遵守格式。
1. module sdram_basemod
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,
8. output [12:0]S_A,
9. output [1:0]S_DQM,
10. inout [15:0]S_DQ,
11.
12. input [1:0]iEn,
13. input [23:0]iAddr,
14. input [15:0]iData,
15. output [15:0]oData,
16. output [1:0]oTag,
17.
18. input [1:0]iCall,
19. output [1:0]oDone
20. );
21. wire [3:0]CallU1; // [3]Write, [2]Read, [1]A.Ref, [0]Initial
22.
23. sdram_ctrlmod U1
24. (
25. .CLOCK( CLOCK ),
26. .RESET( RESET ),
27. .iCall( iCall ), // < top ,[1]Write [0]Read
28. .oDone( oDone ), // > top ,[1]Write [0]Read
29. .oCall( CallU1 ), // > U4
30. .iDone( DoneU4 ) // < U4
31.
32. );
33.
34. wire [15:0]DataU2;
35.
36. fifo_savemod U2
37. (
38. .CLOCK( CLOCK ),
39. .RESET( RESET ),
40. .iEn( {iEn[1],EnU4[0]} ), // < top
41. .iData( iData ), // < top
42. .oData( DataU2 ), // > top
43. .oTag() //
44. );
45.
46. fifo_savemod U3
47. (
48. .CLOCK( CLOCK ),
49. .RESET( RESET ),
50. .iEn( {EnU4[1],iEn[0]} ), // < U4 & top
51. .iData( DataU4 ), // < U4
52. .oData( oData ), // > top
53. .oTag() //
54. );
55.
56. wire DoneU4;
57. wire [1:0]EnU4;
58. wire [15:0]DataU4;
59.
60. sdram_funcmod U4
61. (
62. .CLOCK( CLOCK ),
63. .RESET( RESET ),
64. .S_CKE( S_CKE ), // > top
65. .S_NCS( S_NCS ), // > top
66. .S_NRAS( S_NRAS ), // > top
67. .S_NCAS( S_NCAS ), // > top
68. .S_NWE( S_NWE ), // > top
69. .S_BA( S_BA ), // > top
70. .S_A( S_A ), // > top
71. .S_DQM( S_DQM ), // > top
72. .S_DQ( S_DQ ), // <> top
73. .oEn( EnU4 ), // > U2 && U3
74. .iAddr( iAddr ), // < top
75. .iData( DataU2 ), // < U2
76. .oData( DataU4 ), // > top
77. .iCall( CallU1 ), // < U1
78. .oDone( DoneU4 ) // > U1
79. );
80.
81. endmodule
该组合模块的连线部署完全遵照图21.7。读者自己看着办吧。
sdram_demo.v
图21.8 实验二十一的建模图。
图21.8是实验二十一的建模图,内容上的改变也只有 En 多出来而已,不过核心操作的内容却有很大的改变。具体内容让我们来看代码吧。
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模块的实例化。
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.
22. wire [1:0]DoneU2;
23. wire [15:0]DataU2;
24. wire [1:0]TagU2;
25.
26. sdram_basemod U2
27. (
28. .CLOCK( CLOCK1 ),
29. .RESET( RESET ),
30. .S_CKE( S_CKE ),
31. .S_NCS( S_NCS ),
32. .S_NRAS( S_NRAS ),
33. .S_NCAS( S_NCAS ),
34. .S_NWE( S_NWE ),
35. .S_A( S_A ),
36. .S_BA( S_BA ),
37. .S_DQM( S_DQM ),
38. .S_DQ( S_DQ ),
39. .iEn( isEn ),
40. .iAddr( {D1,9’d0} ),
41. .iData( D2 ),
42. .oData( DataU2 ),
43. .oTag( TagU2 ),
44. .iCall( isCall ),
45. .oDone( DoneU2 )
46. );
47.
以上内容为SDRAM基础模块的实例化。
48. parameter B115K2 = 11'd1157, TXFUNC = 6'd16;
49.
50. reg [5:0]i,Go;
51. reg [10:0]C1,C2;
52. reg [14:0]D1;
53. reg [15:0]D2,D3;
54. reg [10:0]T;
55. reg [1:0]isCall,isEn;
56. reg rTXD;
57.
58. always @ ( posedge CLOCK1 or negedge RESET )
59. if( !RESET )
60. begin
61. i <= 6'd0;
62. Go <= 6'd0;
63. C1 <= 11'd0;
64. C2 <= 11'd0;
65. D1 <= 15'd0;
66. D2 <= 16'hA000;
67. D3 <= 16'd0;
68. T <= 11'd0;
69. isCall <= 2'b00;
70. isEn <= 2'b00;
71. rTXD <= 1'b1;
72. end
73. else
以上内容为相关的寄存器声明与复位操作。第48行是波特率为115200与伪函数入口的实例化。注意,由于本实验是页读写,所以 iAddr[8:0] 基本作废,所以D1也只有15位宽而已。然后 { D1,9’d0 } 联合驱动 iAddr。
74. case( i )
75.
76. 0:
77. begin isEn[1] <= 1'b1; i <= i + 1'b1; end
78.
79. 1:
80. begin isEn[1] <= 1'b0; i <= i + 1'b1; end
81.
82. 2:
83. if( C2 == 511 ) begin C2 <= 11'd0; i <= i + 1'b1; end
84. else begin D2[11:0] <= (D2[11:0] + 1'b1); C2 <= C2 + 1'b1; i <= 6'd0; end
85.
86. 3:
87. if( DoneU2[1] ) begin isCall[1] <= 1'b0; i <= i + 1'b1; end
88. else begin isCall[1] <= 1'b1; D1 <= 15'd0; end
89.
90. 4:
91. if( DoneU2[0] ) begin isCall[0] <= 1'b0; i <= i + 1'b1; end
92. else begin isCall[0] <= 1'b1; D1 <= 15'd0; end
93.
94. 5:
95. begin isEn[0] <= 1'b1; i <= i + 1'b1; end
96.
97. 6:
98. begin D3 <= DataU2; isEn[0] <= 1'b0; i <= i + 1'b1; end
99.
100. 7:
101. begin T <= { 2'b11, D3[15:8], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
102.
103. 8:
104. begin T <= { 2'b11, D3[7:0], 1'b0 }; i <= TXFUNC; Go <= i + 1'b1; end
105.
106. 9:
107. if( C2 == 24'd511 ) begin C2 <= 11'd0; i <= i + 1'b1; end
108. else begin C2 <= C2 + 1'b1; i <= 6'd5; end
109.
110. 10:
111. i <= i;
112.
113. /******************************/
114.
以上内容为部分核心操作。步骤0~2是用来写满FIFO。步骤3将FIFO的内容写入SDRAM。步骤4又将内容读至FIFO。步骤5~9从FIFO读出内容,并且发送出去,直至512个数据读完为止。步骤10发呆。
115. 16,17,18,19,20,21,22,23,24,25,26:
116. if( C1 == B115K2 -1 ) begin C1 <= 11'd0; i <= i + 1'b1; end
117. else begin rTXD <= T[i - 16]; C1 <= C1 + 1'b1; end
118.
119. 27:
120. i <= Go;
121.
122. endcase
123.
124. assign S_CLK = CLOCK2;
125. assign TXD = rTXD;
126.
127. endmodule
以上内容为部分核心操作。步骤16~27是发送一帧数据的伪函数。第124~125行是相关输出驱动声明。综合完毕并且下载程序,如果串口调试软件出现数据 A000~A1FF 表示实验成功。
细节一:完整的个体模块
本实验的SDRAM基础模块已经准备就绪。