這篇是對上一篇我博文的補充,http://www.cnblogs.com/slhuang10714/archive/2012/07/05/Ben.html。本來是沒打算寫這些的,但是后面逐漸發現點問題,所以決定再寫一個下篇來補充說明一下。
圖一
細心的網友會發現上篇末尾的打印是有點問題的,因為我的數據產生器產生的是1-200,1-200,1-200,1-200,1-200,1-200,共六組1200個8bit數據,全部寫進Wrfifo,寫進去之后發寫請求,同時給Moni_Addr[23:0]為0,也就是0地址開始寫。既然第一個數據是0102,為什么打印0000呢,這里只有一個可能,就是提前了一個節拍吧SDRAM_Read_Ack(isRead_Ack)拉高了,導致Rdfifo裝進去0000。返回去查看SDRAM_Read_Ack的拉高軌跡,發現在周期的543-1055處於拉高狀態。如下圖起始:
圖二
結束位置:
圖三
這個現象很詭異,SDRAM_Read_Ack(isRead_Ack)的確是拉高了512個周期,但是呢,第一個數據對應的卻是0000.我們得重新認識這個dcfifo,它很可能在里面預存了一個0000,所以我們0102就把它0000先排了出來。反過頭來看Wrfifo的情況,下圖:
圖四
果然是在SDRAM_Write_Ack(isWrite_Ack)拉高的第一個周期對應的是0000!這dcfifo不夠老實哈,有辦法對付它這特性,我們拉高513個周期,提前拉高一個周期把0000拉出來,但是呢我們此時還沒不發出_WRITE,這樣就讓SDRAM錯過0000.
always@(posedge CLK_100Mor negedge RSTn)
……//忽略
else if( SDRAM_WRITE )
case( i )
0: // Send Active Command with Bank and Row address
begin Command <= _ACT; Address <= Moni_Addr[23:9]; DQM <= 2'b11; i <= i + 1'b1; end
1: // Send 1 nop Clk for tRCD-20ns 。Add isWrite_Ack
begin Command <= _NOP; DQM <= 2'b11; isWrite_Ack <= 1'b1; i <= i + 1'b1;end
//在i=1時添加一個時鍾的isWrite_Ack為高。提前拉高是為了把0000先排出去
……………………………..
3:
if(C1 == 510) begin C1 <= 10'd0; i <= i + 1'b1;j <= j + 1'b1;end
else begin Command <= _NOP; DQM <= 2'b00; isWrite_Ack <= 1'b1; C1 <= C1 + 1'b1; end
串口打印修改后的數據:
圖五
不過雖然寫入的問題解決了,但是讀的問題還在,有沒有發現最后打印了兩次1516,最后的一個數據應該是1718才對,因為1-200個8bit數循環,512個16bit的數打印,最后肯定是十進制的2324,即十六進制的1718。為什么1718沒打印出來,卻把1516打印了兩遍?和下圖所示一樣:
圖六
是不是1718沒有傳給SDRAM呢,看寫SDRAM時的信號:
圖七
最后一個數據1718已經給了SDRAM數據總線。那到底為什么沒讀出來呢?
走到這里的時候,我犯了個錯誤,懷疑邏輯了,懷疑是不是Wrfifo的最后一個1718是不是沒有出來,需要再加一個數據把它擠出來?哈哈,你看,明明邏輯分析儀已經顯示1718已經出來給了Wrfifo_To_SDRAM,我還懷疑它,那就試試吧,我又添加了一個時鍾的SDRAM_Write_Ack(isWrite_Ack)高電平。修改如下:
………………….
3: //這里C1改成到511,再添加一個時鍾,用來”期望”把1718擠出來,虛幻~
if(C1 == 511) begin C1 <= 10'd0; i <= i + 1'b1;j <= j + 1'b1;end
else begin Command <= _NOP; DQM <= 2'b00; isWrite_Ack <= 1'b1; C1 <= C1 + 1'b1; end
好了,來看打印結果:
圖八
第一個數據竟讓是191A!而且后面連接着0304,0102去哪了?呵呵,來看SignalTapII里面的信號吧:
圖九
SignalTapII分析出了一切,我寫的是第一個頁0-511個地址,但是卻給了513個數據,到一頁的結尾時,我們沒有准時發送突發中斷命令,導致第513個數據從頭開始寫了,於是191A寫進了0地址!
雖然不相信邏輯做的無用功測試,但是這個打印卻能深刻說明,為什么需要中斷突發命令來中斷頁突發操作!
好了,無用功讓我們能體會到點東西,但是我們我們的問題還未解決。不過我已經有點眉目了,兩次發出1516說明最后的一個讀取沒有更新。呵呵,來看看先前的讀操作吧:
。。。
5: // Read Data
if(C1 == 511) begin C1 <= 10'd0; i <= i + 1'b1; end
else begin C1 <= C1 + 1'b1; rData <= SDRAM_DATA; isRead_Ack <= 1'b1;
DQM <= 2'b00; Command <= _NOP;
end
/******************************************/
6:// Send BurstTerm
begin Command <= _BURSTTERM; DQM <= 2'b00; isRead_Ack <= 1'b0; i <= i + 1'b1; end
。。。
上面的代碼讓SDRAM_Write_Ack(isWrite_Ack)拉高512周期,沒錯啊,但是為什么兩次1516,天,我拉高isRead_Ack <= 1'b1;510次操作,511次沒有操作它,所以呢,它保持了512個高電平。問題來了,那rData <= SDRAM_DATA;呢,是不是C1==511的時候,也會”保持”,當然會保持,不過是rData這個寄存器保持着上次的1516,而不是保持着又操作了一次rData <= SDRAM_DATA。這真是個很蛋疼的錯誤,可以說是一個低級的語法理解誤區。說明一下:SDRAM_To_Rdfifo <= rData;所以呢因為第511次我們沒有去操作rData <= SDRAM_DATA;所以導致rData保持了上一次的1516,而沒有更新成1718。不信看看下圖:
圖十
果真是要非常小心的去理解表達技巧上效果啊!
好,讓我們改過來吧:
。。。//紅色就是添加的那句
5: // Read Data
if(C1 == 511) begin C1 <= 10'd0; rData <= SDRAM_DATA;i <= i + 1'b1; end
else begin C1 <= C1 + 1'b1; rData <= SDRAM_DATA; isRead_Ack <= 1'b1;
DQM <= 2'b00; Command <= _NOP;
end
/******************************************/
6:// Send BurstTerm
begin Command <= _BURSTTERM; DQM <= 2'b00; isRead_Ack <= 1'b0; i <= i + 1'b1; end
。。。
編譯燒寫,看串口:
圖十一
哈哈,perfect!經過此番細細挖掘一番,我們獲得了兩個進步:第一,認識到了頁突發的讀和寫操作結束后的確是非常需要突發中斷命令(BURST TERM)的,不然它真的會循環從頭開始讀或者寫,所以我們要准時發送_ BURSTTERM拉住這頭驢。第二,我真的覺得學一種語言要建立自己的一套表述風格,並且深刻理清它,不然它會把你的邏輯搞混亂。
/*****************************************************************************/
我還有一些疑問:
1.上面的操作都是單次操作,那么連續操作會不會有什么問題呢?
2.我們已經知道頁突發模式是不支持Auto-precharge,那么它要不要手動precharge?或者突發中斷命令BURSTTERM已經包括了完成釋放資源的操作?
第一個問題,其實就是回答連續操作和單次操作有什么不同。我是遇到了點問題,就是Auto-Refresh的時間控制,這個真的非常重要,在上一篇中,我們已經知道了在100M的情況下,781個CLK就必須刷新電容了。而我們一次性的讀或寫操作已經耗費了500多個CLK。這個概念就是我們驚醒一次讀或者寫就馬上得進行Auto-Refresh操作,這個不是開完笑的,不信你試試,超時之后數據完全丟失!哈哈,我第一次試連續操作,七改八改的,沒有滿足這個時間就造成第二次讀取完全錯誤的數據。有的網友開始問了,假設我530個CLK完成一個讀或者寫操作,那還剩下281個CLK豈不是干等,浪費了,哎呀,如果你一定要用這200多個時鍾,那就弄個組合吧,burst1/2/4/8 + full-page burst,打個比方就是寫burst8+頁操作讀,很多論文都這樣寫的哦,哈哈,鄙視太多的論文,根本說不清楚。好了,別擔心,因為我們一次讀或寫操作之后就馬上進行了Auto-Refresh,然后又可以進行下一次讀或寫操作了,這個效率算是很高的!
我的多次讀寫是這樣測試的,先把Wrfifo和Rdfifo都擴大,至少擴大到可以裝1024個16bit吧,這樣就能先對0地址寫512個數據,然后再給512這個虛擬地址寫后面512個16bit數據。寫完之后,就可以同理連續讀兩次了,照樣,串口打印吧。
這第二個問題一直存在我心中,只不過在上一篇里面沒做過測試,不敢下妄言。現在在連續讀寫完成的情況下,我做了個實驗,先對0地址寫一組數據,再重復對0地址,然后先去讀512這個首地址的數據,再去讀0地址的數據。
圖十二
第一個黑框寫第一組數據到0地址,第二個黑框寫第二組數據到0地址,第三個黑框去讀512地址的數據,第四黑框就是去讀0開始地址的整頁數據。如果第二個黑框操作失敗,那么第四個黑框操作后,打印的會是01020304…..。看下面不是0102開始,而是1B1C,這已經是第二組數據了。
好了,看打印結果吧:
圖十三
呵呵,證明了,連續突發的頁操作是不需要precharge操作的,或者可以認為BURSTTERM終止命令已經釋放了控制的資源,不會讓下一次操作失效。
結束語:本來是沒想寫這么多的,累人,不過有些東西還是說透徹點好,說透徹了證明自己才懂了。這些圖弄的粗糙,大家湊合着看吧。