网上有很多的SDR SDRAM控制器的代码,但都是基于burst1/2/4/8模式下的,这种模式下传输高速的相机数据还是有点拮据的,所以花了几天把这些模式改造成了页突发模式。我的这个控制器模型是这样的:
图一
这里的有两个缓冲Wrfifo和Rdfifo,它们都是dcfifo(混合宽度异步时钟FIFO)。上面的图我画的很丑,但是有些细节要注意,Wrfifo进来是8bit出去是16bit。不管是手机那种摄像头还是工业相机camera_clk都不会很高,在30hz的640x480输出情况下,8进16出,还是比SDRAM要慢,不会把Wrfifo塞满。工业应用上,往往很强调实时性,所以相机的帧率会达到上百帧,这样保证处理的数据尽量是最新的一帧。
好了,进入正题了,我们在已有的SDRAM burst1/2/4/8的控制器代码下怎么修改成full-page burst 模式,需要注意点什么细节。
先来了解一下器件,我用的是美光的一款SDRAM,MT48LC64M4A2,资源如下图椭圆框标示:
图二
4Meg x 16 x 4banks ,行地址[12:0] ,列地址[8:0]。
模式寄存器配置图:
图三
从上面得到一个信息:full-page模式下必须把register[2:0]配置成111。
突发定义表格:
图四
从上图,我们可以得到两个信息:
- full page burst不支持Interleaved,只支持顺序sequential模式,这可以在Mode register设置。
- 从注释的第一条和第五条可知道,在16位数据线模式下,页模式可访问的长度是512个16bit数据,也就是一行的长度column[8:0]。
要把burst1/2/4/8改成full page,我们知道最大的不同就是要改读写时序,先来看一下页突发模式的写时序图:
图五
这张图说起来啰嗦,我用verilog表述一下:
reg[23:0] Moni_Addr;//[23:22] bank;[21:9] row;[8:0] clmn
reg [14:0]Address;//地址线
reg[15:0] rData;//寄存器获取SDRAM数据总线数据
always@(posedge CLK_100Mor negedge RSTn)
……//忽略
else if( SDRAM_READ )
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
begin Command <= _NOP; DQM <= 2'b11; i <= i + 1'b1; end
2: // Send Read command and column address, bank address ,pull down DQM clock
begin Command <= _RD; Address <= { Moni_Addr[23:22], 4'b0000, Moni_Addr[8:0]};
DQM <= 2'b00; i <= i + 1'b1; end
3:
begin Command <= _NOP; DQM <= 2'b00; i <= i + 1'b1; end
4: // Send 2 nop Clk for CAS Latency
begin Command <= _NOP; DQM <= 2'b00; i <= i + 1'b1; end
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
7:
begin Command <= _NOP; DQM <= 2'b11; i <= i + 1'b1; end
8: // return i to 0
begin i <= 4'd0; end
上面的表述,有几点下面说明一下:
1.full-page模式不支持Auto-precharge.这点手册里面原文表述如下:A precharge of the bank/row that is addressed with the READ or WRITE command is automatically performed upon completion of the READ or WRITE burst, except
in the continuous page burst mode where auto precharge does not apply.
所以在i=2时刻,发送的地址里对A10置1是无效的。
2.full-page模式需要突发中断命令来终止它,为什么?我们来看手册原文表述:A continuous page burst continues until terminated;at the end of the page, it wraps to column 0 and continues.
它的意思是说,一个page burst操作必须在 burstterm下才停止,不然它会重新在地址(column 0)开始获取数据。通俗点说它就会循环获取数据!
3.在i=5的操作很有看点,也是重点: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
我循环操作511次去获取数据,即rData <= SDRAM_DATA。循环操作511次去拉高isRead_Ack,即isRead_Ack <= 1'b1;。这个isRead_Ack就是给Rdfifo的rdreq信号,就是为了拉高写进阀门511次,把512个数据都写进Rdfifo去。为什么拉高511次操作写进了512个数据??呵呵,因为verilog语言是有后续效果的。Fifo的特点我这里也不多说了,用用就知道了。
再来看看写时序图吧:
图六
这张写图感觉和读时序图差不多啊,呵呵,也用verilog表述一下:
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
begin Command <= _NOP; DQM <= 2'b11; i <= i + 1'b1;end
2: // Send Write command with row address, bank addr,pull down DQM 1 clk
begin Command <= _WR; Address <= { Moni_Addr[23:22], 4'b0000, Moni_Addr[8:0] };
DQM <= 2'b00; isWrite_Ack <= 1'b1; i <= i + 1'b1;end
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
4: // Send BurstTerm
begin Command <= _BURSTTERM; DQM <= 2'b00; isWrite_Ack <= 1'b0; i <= i + 1'b1; end
5:
begin Command <= _NOP; DQM <= 2'b11; i <= i + 1'b1; end
6: // return i to 0
begin i <= 4'd0; end
这里也说明一下:
1.和发送SDRAM_READ命令一样,发送SDRAM_WRITE命令时和之后的空命令NOP,DQM都是需要拉低处理的,而在之前的发送ACT命令是DON’T CARE(无所谓)。
2.isWrite_Ack是给Wrififo的wrreq信号,就是控制阀门打开,要把数据放出去,和读的isRead_Ack性质一样。
3.在i=2的时候,发送_WR的时候也拉高了一个时钟的isWrite_Ack,再加上i=3时产生效果的511个高效果isWrite_Ack,这样就一共拉高512个时钟节拍,就是把512个16bit的数据给放出去给SDRAM。
4.页突发写结束时,照样也要发送_BURSTTERM来终止写操作!
好了,总结一下,把burst1/2/4/8修改成full-page burst,我们需要做的工作是:
- 修改Mode register的burst length和保证BT为sequential。
- 修改控制器里面的读和写时序,很重要这点,而且老实说要很细心,因为有些代码的计时控制,比如我们需要满足tRCD,CAS latency等等这些时间,一般是用计时器来控制的,这些计时器在状态机里面循环,希望不要绕晕你。我上面对读和写只是做了一个顺序操作的流程,让你知道一个读写操作下来需要的小操作,知道这些之后就好添加修改了。
- 我这里是有Wrfifo和Rdfifo两个dcfifo,数据在刚开始是放在fifo里面的,流程是先网Wrfifo里面写进512个以上的16bit数据,因为是8进16出,所以其实我们要网Wrfifo的写端至少写1024个8bit的数据!然后对控制器发送写请求,写完之后,再发出读请求,然后512个数据哗啦啦的被装进了Rdfifo。
说了上面三点还有最需要注意的一点,定时刷新,很重要!手册里面原文:
Regardless of device width,the 256Mb SDRAM requires 8192 AUTO REFRESH cycles every 64ms (commercial and
industrial) or 16ms (automotive). Providing a distributed AUTO REFRESH command every 7.813μs (commercial and industrial) or 1.953μs (automotive) will meet the refresh requirement and ensure that each row is refreshed.
这款芯片,必须在64ms内完成8192次刷新。也就是说每隔7.813us就必须发一个Auto-Refresh命令。在100M的时钟下7.813us就是781.3个CLK,my god!?这么迫切,这是什么概念,从上面的读写时序表述中知道:我们仅仅数据耗费了512个CLK!!!准确来说,上面读操作耗费520个时钟,写操作耗费516个时钟,还不算上真正操作时传递信号的消费!这说明了什么,这说明我们在一个读或写操作之后必须进行刷新操作,不然时间来不及,等不了两个读或写操作。
所以第四点,我们必须懂得控制好自动刷新电容的操作!
好了,修改了这么多,但是修改之后怎么测试呢,外围的我们可以加个串口模块来打印SDRAM读取的数据,内部信号的测试用SignalTapII,Altera自带的逻辑分析仪,这个工具很好用。来一张测试的截图:
图七
这张图CMD命令,16是_BUSTTERM,17是_NOP,11是_AUTOREFLESH.这里有个很明显的错误,黑线框画出来了,11,17,16,17,说明自动刷新电容命令之后是突发终止命令,这是很奇怪的,违背了我们的本意,刷新电容之后必然是等待读写命令,不可能出现终止命令。这只是个例子,来说明怎么观察信号来修改代码。
下面看一下,正确的信号观察:
椭圆区1,是往Wrfifo写600个16bit数据,Wrfifo_Req是写请求信号,Wrfifo_Data_In是数据
椭圆区2,isWrite拉高向控制器发出写SDRAM请求,SDRAM_Write_Ack是控制器回馈的应答信号,对应给Wrfifo的rdreq,拉高了512个周期,打开闸门放512个16bit数据。
椭圆区3,向控制器发出读请求,isRead拉高,控制器SDRAM_Read_Ack反馈应答给Rdfifo的wrreq,拉高512个周期,放入512个数据给Rdfifo。
图八
这个SingnalTapII文件最好采样深度设2K,把全部信号都能观察到。外部直观点的,最好在Rdfifo的读出端连个串口模块,直接观察打印…
图九
结束语:在刚开始做视觉项目的时候就想弄个页突发模式的控制器,虽然Altera的Sdram_Control_4Port还能凑合着用,但是我表示很难适应它这代码的风格,很难改。后面在网上得了个burst8模式的控制器,这几天试着改成了页模式,把这些细节记录下来。
图一
细心的网友会发现上篇末尾的打印是有点问题的,因为我的数据产生器产生的是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终止命令已经释放了控制的资源,不会让下一次操作失效。
结束语:本来是没想写这么多的,累人,不过有些东西还是说透彻点好,说透彻了证明自己才懂了。这些图弄的粗糙,大家凑合着看吧。