【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十一:SDRAM模块④ — 页读写 β

来源:互联网 时间:1970-01-01

实验二十一: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基础模块已经准备就绪。


相关阅读:
Top