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

时间:2022-02-15 21:02:43

 

经过漫长的战斗以后,我们终于来到最后。对于普通人而言,页读写就是一名战士的墓碑(最终战役) ... 然而,怕死的笔者想透过这个实验告诉读者,旅程的终点就是旅程的起点。一直以来,笔者都在烦恼“SDRAM是否应该成为储存类?”SDRAM作为一介储存资源(储存器),它的好处就是大容量空间,坏处则就是麻烦的控制规则,还有中规中矩的沟通速率。

相比之下,片上内存无论是控制的难度,还是沟通的速率,它都远远领先SDRAM。俗语常说,愈是强力的资源愈是珍贵 ... 对此,片上内容的容量可谓是稀罕的程度。实验二十二的要求非常单纯:

”请问如何建立基于SDRAM储存资源的FIFO存储模块呢?“,笔者问道。

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十二: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行是写满状态与读空状态的声明。

sdram_funcmod.v

该功能模块与实验十八的内容一模一样。

sdram_demo.v

该组合模块的连线部署完全参照图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

连线内容请自己看着办吧。

sdram_demo.v

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

图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读写,集于一身是有可能的。不管怎么样,实验二十二所要表达的信息已经非常清晰,即储存类的伸缩性与可塑性。