经过漫长的战斗以后,我们终于来到最后。对于普通人而言,页读写就是一名战士的墓碑(最终战役) ... 然而,怕死的笔者想透过这个实验告诉读者,旅程的终点就是旅程的起点。一直以来,笔者都在烦恼“SDRAM是否应该成为储存类?”SDRAM作为一介储存资源(储存器),它的好处就是大容量空间,坏处则就是麻烦的控制规则,还有中规中矩的沟通速率。
相比之下,片上内存无论是控制的难度,还是沟通的速率,它都远远领先SDRAM。俗语常说,愈是强力的资源愈是珍贵 ... 对此,片上内容的容量可谓是稀罕的程度。实验二十二的要求非常单纯:
”请问如何建立基于SDRAM储存资源的FIFO存储模块呢?“,笔者问道。
图22.1 SDRAM基础模块。
图22.1是基于实验十八修改而成的SDRAM基础模块,修改对象除了SDRAM控制模块以外,SDRAM功能模块保持实验十八的状态,即单字读写。SDRAM控制模块,除了多出Tag以外,Addr的驱动也由该模块负责。具体的内容,让我们来看代码吧:
1. module sdram_ctrlmod
2. (
3. input CLOCK,
4. input RESET,
5. input [1:0]iCall, // [1]Write, [0]Read
6. output [1:0]oDone,
7. output [3:0]oCall,
8. input iDone,
9. output [23:0]oAddr,
10. output [1:0]oTag
11. );
12. parameter WRITE = 4'd1, READ = 4'd4, REFRESH = 4'd7, INITIAL = 4'd8;
13. parameter TREF = 11'd1040;
14.
以上内容为相关的出入端声明以及常量。其中多了24位宽的oAddr与2位宽的oTag。
15. reg [1:0]C7;
16. reg [1:0]isDo;
17.
18. always @ ( posedge CLOCK or negedge RESET ) // sub
19. if( !RESET )
20. begin
21. C7 <= 2'b10;
22. isDo <= 2'b00;
23. end
24. else
25. begin
26.
27. if( iCall[1] & C7[1] ) isDo[1] <= 1'b1;
28. else if( iCall[0] & C7[0] ) isDo[0] <= 1'b1;
29.
30. if( isDo[1] & isDone[1] ) isDo[1] <= 1'b0;
31. else if( isDo[0] & isDone[0] ) isDo[0] <= 1'b0;
32.
33. if( isDone ) C7 <= {isDo[0],isDo[1]};
34. else if( iCall ) C7 <= { C7[0], C7[1] };
35.
36. end
37.
以上内容为轮流协调的周边操作。具体内容与实验十七一样。
38. reg [3:0]i;
39. reg [10:0]C1;
40. reg [3:0]isCall; //[3]Write [2]Read [1]A.Refresh [0]Initial
41. reg [1:0]isDone;
42. reg [23:0]D1;
43. reg [24:0]C2,C3; // N + 1
44.
45. always @ ( posedge CLOCK or negedge RESET ) // core
46. if( !RESET )
47. begin
48. i <= INITIAL; // Initial SDRam at first
49. C1 <= 11'd0;
50. isCall <= 4'b0000;
51. isDone <= 2'b00;
52. D1 <= 24'd0;
53. C2 <= 25'd0;
54. C3 <= 25'd0;
55. end
以上内容为相关的寄存器声明与复位操作。其中C2是写指针,C3是读指针,位宽为oAddr + 1。D用来驱动oAddr。
56. else
57. case( i )
58.
59. 0: // IDLE
60. if( C1 >= TREF ) begin C1 <= 11'd0; i <= REFRESH; end
61. else if( isDo[1] ) begin C1 <= C1 + 1'b1; i <= WRITE; end
62. else if( isDo[0] ) begin C1 <= C1 + 1'b1; i <= READ; end
63. else begin C1 <= C1 + 1'b1; end
64.
65. /***********************/
66.
以上内容为部分核心内容。步骤0是待机状态,其中61~62行改为 isDo[1] 与 isDo[2]。
67. 1: //Write
68. if( iDone ) begin isCall[3] <= 1'b0; C1 <= C1 + 1'b1; i <= i + 1'b1; end
69. else begin isCall[3] <= 1'b1; D1 <= C2[23:0]; C1 <= C1 + 1'b1; end
70.
71. 2:
72. begin C2 <= C2 + 1'b1; isDone[1] <= 1'b1; C1 <= C1 + 1'b1; i <= i + 1'b1; end
73.
74. 3:
75. begin isDone[1] <= 1'b0; C1 <= C1 + 1'b1; i <= 4'd0; end
76.
77. /***********************/
78.
以上内容为部分核心内容。步骤1~3是写操作,步骤1将C2[23:0]的内容赋值D。步骤2~3产生完成信号之余也递增C2.
79. 4: // Read
80. if( iDone ) begin isCall[2] <= 1'b0; C1 <= C1 + 1'b1; i <= i + 1'b1; end
81. else begin isCall[2] <= 1'b1; D1 <= C3[23:0]; C1 <= C1 + 1'b1; end
82.
83. 5:
84. begin C3 <= C3 + 1'b1; isDone[0] <= 1'b1; C1 <= C1 + 1'b1; i <= i + 1'b1; end
85.
86. 6:
87. begin isDone[0] <= 1'b0; C1 <= C1 + 1'b1; i <= 4'd0; end
88.
89. /***********************/
90.
以上内容为部分核心内容。步骤4~6是写操作,步骤4将C3[23:0]的内容赋值D。步骤5~7产生完成信号之余也递增C3.
91. 7: // Auto Refresh
92. if( iDone ) begin isCall[1] <= 1'b0; i <= 4'd0; end
93. else begin isCall[1] <= 1'b1; end
94.
95. /***********************/
96.
97. 8: // Initial
98. if( iDone ) begin isCall[0] <= 1'b0; i <= 4'd0; end
99. else begin isCall[0] <= 1'b1; end
100.
101. endcase
102.
以上内容为部分核心内容。步骤7~8保持不变。
103. assign oDone = isDone;
104. assign oCall = isCall;
105. assign oAddr = D1;
106. assign oTag[1] = ( C2[24]^C3[24] & C2[23:0] == C3[23:0] );
107. assign oTag[0] = ( C2 == C3 );
108.
109. endmodule
以上内容为相关的输出驱动。D1驱动oAddr,第106~107行是写满状态与读空状态的声明。
该功能模块与实验十八的内容一模一样。
该组合模块的连线部署完全参照图22.1。
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]iCall,
13. output [1:0]oDone,
14. output [1:0]oTag,
15. input [15:0]iData,
16. output [15:0]oData
17. );
18. wire [3:0]CallU1; // [3]Refresh, [2]Read, [1]Write, [0]Initial
19. wire [23:0]AddrU2;
20.
21. sdram_ctrlmod U1
22. (
23. .CLOCK( CLOCK ),
24. .RESET( RESET ),
25. .iCall( iCall ), // < top ,[1]Write [0]Read
26. .oDone( oDone ), // > top ,[1]Write [0]Read
27. .oAddr( AddrU2 ), // > U2
28. .oTag( oTag ), // > top
29. .oCall( CallU1 ), // > U2
30. .iDone( DoneU2 ) // < U2
31. );
32.
33. wire DoneU2;
34.
35. sdram_funcmod U2
36. (
37. .CLOCK( CLOCK ),
38. .RESET( RESET ),
39. .S_CKE( S_CKE ), // > top
40. .S_NCS( S_NCS ), // > top
41. .S_NRAS( S_NRAS ), // > top
42. .S_NCAS( S_NCAS ), // > top
43. .S_NWE( S_NWE ), // > top
44. .S_BA( S_BA ), // > top
45. .S_A( S_A ), // > top
46. .S_DQM( S_DQM ), // > top
47. .S_DQ( S_DQ ), // <> top
48. .iCall( CallU1 ), // < U1
49. .oDone( DoneU2 ), // > U1
50. .iAddr( AddrU2 ), // < U1
51. .iData( iData ), // < top
52. .oData( oData ) // > top
53. );
54.
55. endmodule
连线内容请自己看着办吧。
图22.3 实验二十二的建模图。
图22.3是实验二十二的建模图,左边的周边操作负责写入数据,右边的核心操作负责读取数据并且经由TXD发送出去。具体内容我们还是来看代码吧。
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 [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. .iCall( {isWR,isRD} ),
40. .oDone( DoneU2 ),
41. .iData( D2 ),
42. .oData( DataU2 ),
43. .oTag( TagU2 )
44. );
以上内容为SDRAM基础模块的实例化,注意第39行的iCall是由 isWR与isRD联合驱动。此外,第43行也多了oTag。
46. reg [5:0]i;
47. reg [15:0]D2;
48. reg isWR;
49.
50. always @ ( posedge CLOCK1 or negedge RESET )
51. if( !RESET )
52. begin
53. i <= 6'd0;
54. D2 <= 16'hA000;
55. isWR <= 1'b0;
56. end
57. else
58. case( i )
59.
60. 0:
61. if( !TagU2[1] ) i <= i + 1'b1;
62.
63. 1:
64. if( DoneU2[1] ) begin isWR <= 1'b0; i <= i + 1'b1; end
65. else begin isWR <= 1'b1; end
66.
67. 2:
68. if( D2 == 16'hA1FF ) i <= i + 1'b1;
69. else begin D2[11:0] <= D2[11:0] + 1'b1; i <= 6'd0; end
70.
71. 3:
72. i <= i;
73.
74. endcase
75.
以上内容为写作用的周边操作。步骤0检测是否写满,步骤1写入数据,步骤2判断是否写满512次,不是的话就递增内容。步骤3是写完发呆。
76. reg [5:0]j,Go;
77. reg [10:0]C1;
78. reg [15:0]D3;
79. reg [10:0]T;
80. reg isRD;
81. reg rTXD;
82.
83. parameter B115K2 = 11'd1157, TXFUNC = 6'd16;
84.
85. always @ ( posedge CLOCK1 or negedge RESET )
86. if( !RESET )
87. begin
88. j <= 6'd0;
89. Go <= 6'd0;
90. C1 <= 11'd0;
91. D3 <= 16'd0;
92. T <= 11'd0;
93. isRD <= 1'b0;
94. rTXD <= 1'b1;
95. end
96. else
97. case( j )
98.
99. 0:
100. if( !TagU2[0] ) j <= j + 1'b1;
101.
102. 1:
103. if( DoneU2[0] ) begin D3 <= DataU2; isRD <= 1'b0; j <= j + 1'b1; end
104. else begin isRD <= 1'b1; end
105.
106. 2:
107. begin T <= { 2'b11, D3[15:8], 1'b0 }; j <= TXFUNC; Go <= j + 1'b1; end
108.
109. 3:
110. begin T <= { 2'b11, D3[7:0], 1'b0 }; j <= TXFUNC; Go <= j + 1'b1; end
111.
112. 4:
113. if( D3 == 16'hA1FF ) j <= j + 1'b1;
114. else j <= 6'd0;
115.
116. 5:
117. j <= j;
118.
119. /******************************/
120.
以上内容为部分核心操作。步骤0检测是否读空,步骤1读出数据,步骤2~3将数据发送数据,步骤4判断是否执行512次?如果不是的话就返回步骤0,是的话就递增i进入发呆的步骤5。
121. 16,17,18,19,20,21,22,23,24,25,26:
122. if( C1 == B115K2 -1 ) begin C1 <= 11'd0; j <= j + 1'b1; end
123. else begin rTXD <= T[j - 16]; C1 <= C1 + 1'b1; end
124.
125. 27:
126. j <= Go;
127.
128. endcase
129.
130. assign S_CLK = CLOCK2;
131. assign TXD = rTXD;
132.
133. endmodule
以上内容为核心操作以及输出驱动·。综合完毕便下载程序,如果串口调试软件出现 A000~A1FF,表示实验成功。
细节一:完整的个体模块
本实验的SDRAM基础模块已经准备就绪。
细节二:小谈储存类
FIFO机制的SDRAM很可能实用性不强。相对低级建模II而言,SDRAM已经脱离死板的印象,任何畸形的储存方式,都有可能实现在任何储存资源之上。举例而言,片上内存储存资源可以实现FIFO功能,IIC储存资源也可以实现FIFO功能,SDRAM储存资源也可以实现FIFO功能。
如果按照这条思路逆向思考的话,如果SDRAM储存资源可以实现功能C,那么IIC储存资源还有片上内存储存资源同样也可以实现功能C。如此一来,建模的自由度会大大增高,喜爱建模的孩子也会开心至极。如果读者是变态,那么一块小小的SDRAM储存资源实现多功能读写如:单字读写,多字读写,页读写,甚至FIFO读写,集于一身是有可能的。不管怎么样,实验二十二所要表达的信息已经非常清晰,即储存类的伸缩性与可塑性。