1. 程式人生 > >【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十一:SDRAM模組④ — 頁讀寫 β

【黑金原創教程】【FPGA那些事兒-驅動篇I 】實驗二十一:SDRAM模組④ — 頁讀寫 β

實驗二十一: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告訴我們,執行與判斷是同時進行,然而從解讀程式碼的順序來看,筆者更加傾向“先執行後判斷”這種結構性

clip_image002

圖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產生四個高脈衝。

clip_image004

圖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)就派上用場了。

clip_image006

圖21.3 SDRAM基礎模組的建模圖。

加入FIFO儲存模組的SDRAM基礎模組,大致上如圖21.3所示。為了簡化連線,筆者稍微加大FIFO的深度,對此FIFO有沒有反饋狀態都沒有問題。還沒有進入建模之前,讓筆者先來解釋一下,頁讀寫與FIFO之間,究竟有什麼細節需要注意。

頁寫操作(請求FIFO):

clip_image008

圖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):

clip_image010

圖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

clip_image012

圖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

clip_image014

圖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

clip_image016

圖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基礎模組已經準備就緒。

相關推薦

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組④ — β

實驗二十一:SDRAM模組④ — 頁讀寫 β 未進入主題之前,讓我們先來談談一些重要的體外話。《整合篇》之際,筆者曾經比擬Verilog如何模仿for迴圈,我們知道for迴圈是順序語言的產物,如果Verilog要實現屬於自己的for迴圈,那麼它要考慮的東西除了步驟以外,還有非常關鍵的時鐘。 for(

黑金原創教程FPGA那些事兒-驅動I 實驗流水燈模組

實驗一:流水燈模組 對於發展商而言,動土儀式無疑是最重要的任務。為此,流水燈實驗作為低階建模II的動土儀式再適合不過了。廢話少說,我們還是開始實驗吧。 圖1.1 實驗一建模圖。 如圖1.1 所示,實驗一有名為 led_funcmod的功能模組。如果無視環境訊號(時鐘訊號還有復位訊號),該功能模組只有

黑金原創教程FPGA那些事兒-驅動I 連載導讀

前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組② — 點選與長點選

實驗三:按鍵模組② — 點選與長點選 實驗二我們學過按鍵功能模組的基礎內容,其中我們知道按鍵功能模組有如下操作: l 電平變化檢測; l 過濾抖動; l 產生有效按鍵。 實驗三我們也會z執行同樣的事情,不過卻是產生不一樣的有效按鍵: l 按下有效(點選); l 長按下有效(長點選)。 圖3

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組

實驗二:按鍵模組① - 消抖 按鍵消抖實驗可謂是經典中的經典,按鍵消抖實驗雖曾在《建模篇》出現過,而且還惹來一堆麻煩。事實上,筆者這是在刁難各位同學,好讓對方的慣性思維短路一下,但是慘遭口水攻擊 ... 面對它,筆者宛如被甩的男人,對它又愛又恨。不管怎麼樣,如今 I’ll be back,筆者再也不會重複一

黑金原創教程FPGA那些事兒-驅動I 實驗數碼管模組

實驗六:數碼管模組 有關數碼管的驅動,想必讀者已經學爛了 ... 不過,作為學習的新儀式,再爛的東西也要溫故知新,不然學習就會不健全。黑金開發板上的數碼管資源,由始至終都沒有改變過,筆者因此由身懷念。為了點亮多位數碼管從而顯示數字,一般都會採用動態掃描,然而有關動態掃描的資訊請怒筆者不再重複。在此,同樣也是

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組③ — 單擊與雙擊

實驗四:按鍵模組③ — 單擊與雙擊 實驗三我們建立了“點選”還有“長點選”等有效按鍵的多功能按鍵模組。在此,實驗四同樣也是建立多功能按鍵模組,不過卻有不同的有效按鍵。實驗四的按鍵功能模組有以下兩項有效按鍵: l 單擊(按下有效); l 雙擊(連續按下兩下有效)。 圖4.1 單擊有效按鍵,時序示意圖

黑金原創教程FPGA那些事兒-驅動I 實驗按鍵模組④ — 點選,長點選,雙擊

實驗五:按鍵模組④ — 點選,長點選,雙擊 實驗二至實驗四,我們一共完成如下有效按鍵: l 點選(按下有效) l 點選(釋放有效) l 長擊(長按下有效) l 雙擊(連續按下有效) 然而,不管哪個實驗都是隻有兩項“功能”的按鍵模組而已,如今我們要建立三項“功能”的按鍵模組,亦即點選(按下有效),長

黑金原創教程FPGA那些事兒-驅動I 原創教程連載導讀連載完成,共九章

前言: 無數晝夜的來回輪替以後,這本《驅動篇I》終於編輯完畢了,筆者真的感動到連鼻涕也流下來。所謂驅動就是認識硬體,還有前期建模。雖然《驅動篇I》的硬體都是我們熟悉的老友記,例如UART,VGA等,但是《驅動篇I》貴就貴在建模技巧的昇華,亦即低階建模II。 話說低階建模II,讀過《建模篇》的朋友多少也會面

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組④ — 普通滑鼠

實驗十:PS/2模組④ — 普通滑鼠 學習PS/2鍵盤以後,接下來就要學習 PS/2 滑鼠。PS/2滑鼠相較PS/2鍵盤,驅動難度稍微高了一點點,因為FPGA(從機)不僅僅是從PS/2滑鼠哪裡讀取資料,FPGA還要往滑鼠裡寫資料 ... 反之,FPGA只要對PS/2鍵盤讀取資料即可。然而,最傷腦筋的地方就在

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組① — 單字

實驗十八:SDRAM模組① — 單字讀寫 筆者與SDRAM有段不短的孽緣,它作為冤魂日夜不斷糾纏筆者。筆者嘗試過許多方法將其退散,不過屢試屢敗的筆者,最終心情像橘子一樣橙。《整合篇》之際,筆者曾經大戰幾回兒,不過內容都是點到即止。最近它破蠱而出,日夜不停:“好~痛苦!好~痛苦!”地呻吟著,嚇得筆者不敢半夜如

黑金原創教程FPGA那些事兒-驅動I 實驗TFT模組

實驗二十七:TFT模組 - 顯示 所謂TFT(Thin Film Transistor)就是眾多LCD當中,其中一種支援顏色的LCD,相較古老的點陣LCD(12864笑),它可謂高階了。黑金的TFT LCD除了320×240大小以外,內建SSD1289控制器,同時也是獨立模組。事實上,無論是驅動點陣LCD還

黑金原創教程FPGA那些事兒-驅動I 實驗十三串列埠模組② — 接收

實驗十三:串列埠模組② — 接收 我們在實驗十二實現了串列埠傳送,然而這章實驗則要實現串列埠接收 ... 在此,筆者也會使用其它思路實現串列埠接收。 圖13.1 模組之間的資料傳輸。 假設我們不考慮波特率,而且一幀資料之間的傳輸也只是發生在FPGA之間,即兩隻模組之間互轉,並且兩塊模組都使用相同的時

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組① — 鍵盤

實驗七:PS/2模組① — 鍵盤 實驗七依然也是熟爛的PS/2鍵盤。相較《建模篇》的PS/2鍵盤實驗,實驗七實除了實現基本的驅動以外,我們還要深入解PS/2時序,還有PS/2鍵盤的行為。不過,為了節省珍貴的頁數,怒筆者不再重複有關PS/2的基礎內容,那些不曉得的讀者請複習《建模篇》或者自行谷歌一下。 市場

黑金原創教程FPGA那些事兒-驅動I 實驗PS/2模組③ — 鍵盤與多組合鍵

實驗九:PS/2模組③ — 鍵盤與多組合鍵 筆者曾經說過,通碼除了單位元組以外,也有雙位元組通碼,而且雙位元組通碼都是 8’hE0開頭,別名又是 E0按鍵。常見的的E0按鍵有,<↑>,<↓>,<←>,<→>,<HOME>,<PRTSC>

黑金原創教程FPGA那些事兒-驅動I 實驗SDHC模組

實驗二十五:SDHC模組 筆者曾經說過,SD卡發展至今已經衍生許多版本,實驗二十四就是針對版本SDV1.×的SD卡。實驗二十四也說過,CMD24還有CMD17會故意偏移地址29,讓原本範圍指向從原本的232 變成 223,原因是SD卡讀寫一次都有512個位元組。為此我們可以這樣計算: SDV1.x = 2

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組③ — α

實驗二十:SDRAM模組③ — 頁讀寫 α 完成單字讀寫與多字讀寫以後,接下來我們要實驗頁讀寫。醜話當前,實驗二十的頁讀寫只是實驗性質的東西,其中不存在任何實用價值,筆者希望讀者可以把它當成頁讀寫的熱身運動。 表示20.1 Mode Register的內容。 Mode Register

黑金原創教程FPGA那些事兒-驅動I 實驗TFT模組

實驗二十八:TFT模組 - 觸屏 讀者在上一個實驗所玩弄過的 TFT LCD模組,除了顯示大小為 320 × 240,顏色為16位RGB的影象資訊以外,它還支援觸屏。所謂觸屏就是滑鼠還有鍵盤以外的輸入手段,例如現在流行平板還有智慧手機,觸屏輸入對我們來說,已經成為日常的一部分。描述語言一門偏向硬體的語言

黑金原創教程FPGA那些事兒-驅動I 實驗SDRAM模組⑤ — FIFO

經過漫長的戰鬥以後,我們終於來到最後。對於普通人而言,頁讀寫就是一名戰士的墓碑(最終戰役) ... 然而,怕死的筆者想透過這個實驗告訴讀者,旅程的終點就是旅程的起點。一直以來,筆者都在煩惱“SDRAM是否應該成為儲存類?”SDRAM作為一介儲存資源(儲存器),它的好處就是大容量空間,壞處則就是麻煩的控制規

黑金原創教程FPGA那些事兒-驅動I 實驗儲存模組

實驗十四比起動手筆者更加註重原理,因為實驗十四要討論的東西,不是其它而是低階建模II之一的模組類,即儲存模組。接觸順序語言之際,“儲存”不禁讓人聯想到變數或者陣列,結果它們好比資料的暫存空間。 1. int main() 2. { 3. int VarA; 4.