【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十八:SDRAM模块① — 单字读写
2015-03-02 20:40
239 查看
实验十八:SDRAM模块①—单字读写
笔者与SDRAM有段不短的孽缘,它作为冤魂日夜不断纠缠笔者。笔者尝试过许多方法将其退散,不过屡试屡败的笔者,最终心情像橘子一样橙。《整合篇》之际,笔者曾经大战几回儿,不过内容都是点到即止。最近它破蛊而出,日夜不停:“好~痛苦!好~痛苦!”地呻吟着,吓得笔者不敢半夜如厕。疯狂之下,誓要歪它不可...可恶的东西,笔者要它血债血还!图18.1数据读取(理想时序左,物理时序右)。
首先,让我们来了解一下,什么才是数据读取的最佳状态?如图18.1所示,红色箭头是上升沿,绿色箭头是锁存沿。左图是理想时序读取数据的最佳状态,即T0发送数据,T1锁存数据。右图则是物理时序读取数据的最佳状态,即T0发送数据,然后数据经由TDATA延迟,然后T1锁存数据。理想状态下,读取数据不用考虑任何物理因数,凡是过去值都会读取成功。
图18.2读取数据(物理时序)。
然而物理状态下,读取数据则必须考虑物理因数,但是物理时序也有所谓的理想状态,即数据被TDATA推挤,然后恰好停留在锁存沿的正中间。该状态之所以称为理想,那是因为建立时间TSETUP与保持时间THOLD都被满足。
如图18.2所示,TSETUP从数据中间向左边覆盖,THOLD从数据中间向右边覆盖,如果两者不完全覆盖数据,那么数据的有效性就能得到保证。简言之,数据是否读取成功,建立时间还有保持时间都必须得到满足。但是我们也知道,Verilog不能描述理想以外的东西,即Verilog无力描述TDATA。话虽如此,我们可以改变时钟位移来达到同样的效果。
图18.3CLOCK1位移-180°(左图),没有位移(中图),CLOCK2位移+180°(右图),以及修正结果。
常见的理想时序,最多适用在FPGA的内部而已。当描述功活动涉及FPGA的外部,那么理想时序必须考虑对外的情况。如图18.3所示,中间的理想时序图可以经由CLOCK1位移-180°,又或者CLOCK2位移+180°来得到同样的效果。虽说180°的位移是理想效果,但是我们还要考虑物理路径所带来的影响。根据Alinix301这只开发板,我们必须追加-30°位移才能达到修正的效果。(注意:追加-30°的修正时序仅仅为适用Alinix301这只板子而已)。理解完毕以后,我们便可进入正题。
驱动SDRAM而言,简单可以分为以下四项操作:
(一)初始化
(二)刷新操作
(三)读操作
(四)写操作
初始化令SDRAM就绪,刷新操作就是不失掉内容(数据),读操作就是从SDRAM哪里读取数据,写操作就是向SDRAM写数据。其中,读写操作又有单字读写,多字读写还有页读写。
首先,让我们来分析一下Alinx开发板上HY57V2562GTR这只SDRAM。根据手册,这只SDRAM有256Mb的容量,4个BANK(即一个BANK为64Mb),频率极限为200Mhz,数据保留周期为8192/64ms。至于引脚定义如表18.1所示:
表18.1SDRAM的引脚定义
分类 | 标示 | 信号 | 说明 |
时钟信号 | CLK | S_CLK | 时钟源 |
地址信号 | BA0~1 | S_BA[1:0] | BANK地址 |
A0~A12 | S_A[12:0] | 读写地址,行列共用,A0~A12为行地址,CA0~CA8为列地址 | |
命令信号 | CKE | S_CKE, | 时钟选,拉高有效 |
CS | S_NCS, | 片选,拉低有效 | |
RAS | S_NRAS, | 命令选,拉低有效 | |
CAS | S_NCAS, | 命令选,拉低有效 | |
WE | S_NWE | 命令选,拉低有效 | |
数据信号 | DQ0~DQ15 | S_DQ[15:0] | 读写数据的IO |
LDQM,UDQM | S_DQM[1:0] | 遮盖数据,一般拉低无视 |
表18.2常用命令。
命令 | CKE | CS | RAS | CAS | WE | 说明 |
NOP | 1 | 0 | 1 | 1 | 1 | 空命令 |
ACT | 1 | 0 | 0 | 1 | 1 | 激活命令,选择Bank地址与行地址 |
WR | 1 | 0 | 1 | 0 | 0 | 写命令,开始写数据 |
RD | 1 | 0 | 1 | 0 | 1 | 读命令,开始读数据 |
BSTP | 1 | 0 | 1 | 1 | 0 | 停止命令,停止读写 |
PR | 1 | 0 | 0 | 1 | 0 | 预充命令,释放选择 |
AR | 1 | 0 | 0 | 0 | 1 | 刷新命令,刷新内容 |
LMR | 1 | 0 | 0 | 0 | 0 | 设置命令,设置SDRAM |
lACT为Active,即激活命令,用来选择某Bank某行。
lWR为Write,即写命令,通知设备开始写数据。
lRD为Read,即读命令,通知设备开始读数据。
lBSTP为BurstStop,即停止命令,禁止设备继续读写。
lPR为Precharge,即预充命令,用来释放某Bank与某行的选择。
lAR为AutoRefresh,即刷新命令,用来刷新或者更新数据内容。
lLMR为LoadModeRegister,即设置命令,用来配置设备参数。
Verilog则可以这样描述这些命令,结果如代码18.1所示:
parameter_INIT=5'b01111,_NOP=5'b10111,_ACT=5'b10011,_RD=5'b10101,_WR=5'b10100,
[code]_BSTP=5'b10110,_PR=5'b10010,_AR=5'b10001,_LMR=5'b10000;
[/code]
代码18.1
DQ0~DQ15为数据信号。BA0~1与A0~A12皆为地址信号,其中A0~A12行列共用,,然而地址信号可以指向的范围,如下计算:
2(2Bank+13Row+9Column)×16bit=224×16bit
=1.6777216e7×16bit//16M×16bit
=2.68435456e8bit
=262144kbit
=256Mbits
初始化:
初始化除了就绪SDRAM以外,我们还要设置SDRAM内部的ModeRegister,设置内容内容如表18.3所示:
表18.3ModeRegister的内容。
ModeRegister | ||||||||||||
A12 | A11 | A10 | A9 | A8 | A7 | A6 | A5 | A4 | A3 | A2 | A1 | A0 |
0 | 0 | OPCode | 0 | 0 | CASLatency | BT | BurstLength |
|
| ||||||||||||||||||||||||||||||||||||||
|
|
图18.4初始化的理想时序图。
图18.4是初始化的理想时序图,其中CLOCK1为-210°的系统时钟,CLOCK2为SDRAM的时钟。rCMD为CKE,CS,RAS,CAS还有WE等命令。rA为A0~A12,rBA为BA0~BA1等地址信号。初始化过程如下所示:
lT0,满足100us;
lT1,发送PR命令,拉高所有rA与rBA。
lT1半周期,SDRAM读取。
lT2,满足TRP;
lT3,发送AR命令。
lT3半周期,SDRAM读取。
lT4,满足TRRC,
lT5,发送AR命令。
lT5半周期,SDRAM读取。
lT6,满足TRRC,
lT7,发送LMR命令与相关Code(设置内容)。
lT7半周期,SDRAM读取。
lT8,满足TMRD。
怎么样?读者是不是觉得很单纯呢?事后,Verilog则可以这样描述,结果如代码18.2所示:
1.case(i)
[code]2.
3.0://delay100us
4.if(C1==T100US-1)beginC1<=14'd0;i<=i+1'b1;end
5.elsebeginC1<=C1+1'b1;end
6.
7.1://SendPrechargeCommand
8.beginrCMD<=_PR;{rBA,rA}<=15'h3fff;i<=i+1'b1;end
9.
10.2://waitTRP20ns
11.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
12.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
13.
14.3://SendAutoRefreshCommand
15.beginrCMD<=_AR;i<=i+1'b1;end
16.
17.4://waitTRRC63ns
18.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
19.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
20.
21.5://SendAutoRefreshCommand
22.beginrCMD<=_AR;i<=i+1'b1;end
23.
24.6://waitTRRC63ns
25.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
26.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
27.
28.7://SendLMRCmd.BurstRead&Write,3'b011meanCASlatecy=3,Sequential,1burstlength
29.beginrCMD<=_LMR;rBA<=2'b11;rA<={3'd0,1'b0,2'd0,3'b011,1'b0,3'b000};i<=i+1'b1;end
30.
31.8://Send2nopCLKfortMRD
32.if(C1==TMRD-1)beginC1<=14'd0;i<=i+1'b1;end
33.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
34.
35.9://Generatedonesignal
36.beginisDone<=1'b1;i<=i+1'b1;end
37.
38.10:
39.beginisDone<=1'b0;i<=4'd0;end
40.
41.endcase
[/code]
代码18.2
代码18.2完全按照图18.4去驱动,读者只要将i看为T就万事大吉,其中步骤7发送LMR命令还有设置Code内容。至于步骤8~9则用来产生完成信号。
刷新操作:
图18.5刷新操作的理想时序图。
所谓定期刷新就是被宫掉的初始化,如图18.5所示,时序过程如下:
lT0,发送PR命令(拉高所有rA与rBA视喜好而定);
lT0半周期,SDRAM读取。
lT1,满足TRP;
lT2,发送AR命令。
lT2半周期,SDRAM读取。
lT3,满足TRRC,
lT4,发送AR命令。
lT4半周期,SDRAM读取。
lT5,满足TRRC,
Verilog则可以这样表示,结果如表18.3所示:
1.case(i)
[code]2.
3.0://SendPrechargeCommand
4.beginrCMD<=_PR;i<=i+1'b1;end
5.
6.1://waitTRP20ns
7.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
8.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
9.
10.2://SendAutoRefreshCommand
11.beginrCMD<=_AR;i<=i+1'b1;end
12.
13.3://waitTRRC63ns
14.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
15.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
16.
17.4://SendAutoRefreshCommand
18.beginrCMD<=_AR;i<=i+1'b1;end
19.
20.5://waitTRRC63ns
21.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
22.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
23.
24.6://Generatedonesignal
25.beginisDone<=1'b1;i<=i+1'b1;end
26.
27.7:
28.beginisDone<=1'b0;i<=4'd0;end
29.
30.endcase
[/code]
代码18.3
除了步骤6~7用来产生完成信号以外,代码18.3都是据图18.5描述。SDRAM储存的内容是非常脆弱的,如果我们不定期刷新内容,该内容有可能会蒸发掉。根据HY57V2562GTR这只SDRAM,它的内容储存周期为8192/64ms,然而定期刷新的计算如下:
64ms/8192=7.8125us
换言之,每隔7.8125微妙就要刷新一次所有内容。
写操作:
图18.6写操作的理想时序图。
图18.6是写操作的理想时序图,过程如下:
lT1,发送ACT命令,BANK地址与行地址;
lT1半周期,SDRAM读取;
lT2,满足TRCD;
lT3,发送WR命令,BANK地址与列地址,还有写数据;
lT3半周期,SDRAM读取
lT4,满足TWR;
lT5,满足TRP。
正如前面说过,ACT命令式用来选择BANK地址与行地址,然而关键就在T3。T3除了发送WR命令,列地址,还有些数据以外,A10拉高是为了执行预充电。所谓预充电就是释放BANK地址,行地址与列地址等的选择。因此,满足TWR以后,我们还要满足TRP的释放时间,好让SDRAM有足够的时间自行释放选择。
Verilog则可以这样描述,结果如代码18.4所示:
1.case(i)
[code]2.
3.0://SetIOtooutputState
4.beginisOut<=1'b1;i<=i+1'b1;end
5.
6.1://SendActiveCommandwithBankandRowaddress
7.beginrCMD<=_ACT;rBA<=iAddr[23:22];rA<=iAddr[21:9];i<=i+1'b1;end
8.
9.2://waitTRCD20ns
10.if(C1==TRCD-1)beginC1<=14'd0;i<=i+1'b1;end
11.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
12.
13.3://SendWritecmdwithrowaddress,pullupA101clktoPR
14.beginrCMD<=_WR;rBA<=iAddr[23:22];rA<={4'b0010,iAddr[8:0]};i<=i+1'b1;end
15.
16.4://waitTWR2clock
17.if(C1==TWR-1)beginC1<=14'd0;i<=i+1'b1;end
18.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
19.
20.5://waitTRP20ns
21.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
22.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
23.
24.6://Generatedonesignal
25.beginisDone<=1'b1;i<=i+1'b1;end
26.
27.7:
28.beginisDone<=1'b0;i<=4'd0;end
29.
30.endcase
[/code]
代码18.4
根据前面的计算,BA1~BA0再加上RA12~A0与CA8~A0以后,一共有24位宽,详细的位分配如表18.4所示:
表18.4Addr的位分配。
位分配 | 地址内容 |
Addr[23:22] | BANK地址 |
Addr[21:9] | 行地址 |
Addr[8:0] | 列地址 |
读操作:
图18.7读操作的理想时序。
图18.7为读操作的理想时序,大致过程如下:
lT1,发送ACT命令,BANK地址与行地址;
lT1半周期,SDRAM读取;
lT2,满足TRCD;
lT3,发送RD命令,BANK地址与列地址;
lT3半周期,SDRAM读取命令。
lT4,满足CASLatency。
lT5,读取数据。
lT6,满足TRP。
读操作与写操作的过程大同小异,除了WR命令变成RD命令以外,A10为1同样表示自行预充电,余下就是满足CASLatency。好奇的同学一定会觉得疑惑,为何CL为3呢?其实没什么,只是直感上觉得3这个数字比较顺眼一点。注意CL的计算方式是读取RD命令以后开始计算。
Verilog可以这样描述,结果如代码18.5所示:
1.case(i)
[code]2.
3.0:
4.beginisOut<=1'b0;D1<=16'd0;i<=i+1'b1;end
5.
6.1://SendActivecommandwithBankandRowaddress
7.beginrCMD<=_ACT;rBA<=iAddr[23:22];rA<=iAddr[21:9];i<=i+1'b1;end
8.
9.2://waitTRCD20ns
10.if(C1==TRCD-1)beginC1<=14'd0;i<=i+1'b1;end
11.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
12.
13.3://SendReadcommandandcolumnaddress,pullupA10toPR.
14.beginrCMD<=_RD;rBA<=iAddr[23:22];rA<={4'b0010,iAddr[8:0]};i<=i+1'b1;end
15.
16.4://waitCL3clock
17.if(C1==CL-1)beginC1<=14'd0;i<=i+1'b1;end
18.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
19.
20.5://ReadData
21.beginD1<=S_DQ;i<=i+1'b1;end
22.
23.6://waitTRP20ns
24.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
25.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
26.
27.7://Generatedonesignal
28.beginisDone<=1'b1;i<=i+1'b1;end
29.
30.8:
31.beginisDone<=1'b0;i<=4'd0;end
32.
33.endcase
[/code]
代码18.5
代码18.5完全根据图18.7描述,除了步骤7~8用于产生完成信号以外。SDRAM的基本操作大致上就是这样而已,完后我们便可以开始建模了。
图18.8SDRAM基础模块的建模图。
图18.8是SDRAM基础模块的建模图,SDRAM基础模块的内容包括SDRAM控制模块,还有SDRAM功能模块。外围的PLL模块应用频率为133Mhz向左位移210°的CLOCK1,还有133Mhz的CLOCK2。CLOCK1用作系统时钟,CLOCK用作SDRAM时钟。如果PLL模块硬要分类的话,它应该属于特殊性质的即时类吧!?
SDRAM控制模块主要负责一些操作的调度,左边2位Call/Done由外部调用,其中[1]为写操作[0]为读操作;右边4位Call/Done为调用SDRAM功能模块,其中[3]为写操作[2]为读操作[1]为刷新[0]为初始化。SDRAM功能模块的右边是驱动SDRAM硬件资源的顶层信号,左边的问答信号被控制模块调用以外,地址信号还有数据信号都直接连接外部。
sdram_funcmod.v
图18.9SDRAM功能模块的建模图。
该说的东西笔者都已经说了,具体内容我们还是来看代码吧。
1.modulesdram_funcmod
[code]2.(
3.inputCLOCK,
4.inputRESET,
5.
6.outputS_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[3:0]iCall,
13.outputoDone,
14.input[23:0]iAddr,//[23:22]BA,[21:9]Row,[8:0]Column
15.input[15:0]iData,
16.output[15:0]oData
17.);
[/code]
以上内容为相关的出入端声明。
18.parameterT100US=14'd13300;
[code]19.//tRP20ns,tRRC63ns,tRCD20ns,tMRD2CLK,tWR/tDPL2CLK,CASLatency3CLK
20.parameterTRP=14'd3,TRRC=14'd9,TMRD=14'd2,TRCD=14'd3,TWR=14'd2,CL=14'd3;
21.parameter_INIT=5'b01111,_NOP=5'b10111,_ACT=5'b10011,_RD=5'b10101,_WR=5'b10100,
22._BSTP=5'b10110,_PR=5'b10010,_AR=5'b10001,_LMR=5'b10000;
23.
[/code]
以上内容为相关的常量声明,其中第18~20行的是将常量都是经由133Mhz量化。
24.reg[4:0]i;
[code]25.reg[13:0]C1;
26.reg[15:0]D1;
27.reg[4:0]rCMD;
28.reg[1:0]rBA;
29.reg[12:0]rA;
30.reg[1:0]rDQM;
31.regisOut;
32.regisDone;
33.
34.always@(posedgeCLOCKornegedgeRESET)
35.if(!RESET)
36.begin
37.i<=4'd0;
38.C1<=14'd0;
39.D1<=16'd0;
40.rCMD<=_NOP;
41.rBA<=2'b11;
42.rA<=13'h1fff;
43.rDQM<=2'b00;
44.isOut<=1'b1;
45.isDone<=1'b0;
46.end
[/code]
以上内容为相关的寄存器声明以及复位操作。
47.elseif(iCall[3])
[code]48.case(i)
49.
50.0://SetIOtooutputState
51.beginisOut<=1'b1;i<=i+1'b1;end
52.
53.1://SendActiveCommandwithBankandRowaddress
54.beginrCMD<=_ACT;rBA<=iAddr[23:22];rA<=iAddr[21:9];i<=i+1'b1;end
55.
56.2://waitTRCD20ns
57.if(C1==TRCD-1)beginC1<=14'd0;i<=i+1'b1;end
58.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
59.
60./*********************************************/
61.
62.3://SendWritecommandwithrowaddress,pullupA101clktoPR
63.beginrCMD<=_WR;rBA<=iAddr[23:22];rA<={4'b0010,iAddr[8:0]};i<=i+1'b1;end
64.
65.4://waitTWR2clock
66.if(C1==TWR-1)beginC1<=14'd0;i<=i+1'b1;end
67.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
68.
69.5://waitTRP20ns
70.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
71.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
72.
73./**********************************************/
74.
75.6://Generatedonesignal
76.beginisDone<=1'b1;i<=i+1'b1;end
77.
78.7:
79.beginisDone<=1'b0;i<=4'd0;end
80.
81.endcase
[/code]
以上内容为部分核心操作。第47行的if(iCall[3])表示余下内容为写操作。
82.elseif(iCall[2])
[code]83.case(i)
84.
85.0:
86.beginisOut<=1'b0;D1<=16'd0;i<=i+1'b1;end
87.
88.1://SendActivecommandwithBankandRowaddress
89.beginrCMD<=_ACT;rBA<=iAddr[23:22];rA<=iAddr[21:9];i<=i+1'b1;end
90.
91.2://waitTRCD20ns
92.if(C1==TRCD-1)beginC1<=14'd0;i<=i+1'b1;end
93.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
94.
95./********************/
96.
97.3://SendReadcommandandcolumnaddress,pullupA10toPR
98.beginrCMD<=_RD;rBA<=iAddr[23:22];rA<={4'b0010,iAddr[8:0]};i<=i+1'b1;end
99.
100.4://waitCL3clock
101.if(C1==CL-1)beginC1<=14'd0;i<=i+1'b1;end
102.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
103.
104./********************/
105.
106.5://ReadData
107.beginD1<=S_DQ;i<=i+1'b1;end
108.
109./********************/
110.
111.6://waitTRP20ns
112.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
113.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
114.
115./********************/
116.
117.7://Generatedonesignal
118.beginisDone<=1'b1;i<=i+1'b1;end
119.
120.8:
121.beginisDone<=1'b0;i<=4'd0;end
122.
123.endcase
[/code]
以上内容为部分核心操作。第82行的if(iCall[2])表示余下内容为读操作。
124.elseif(iCall[1])
[code]125.case(i)
126.
127.0://SendPrechargeCommand
128.beginrCMD<=_PR;i<=i+1'b1;end
129.
130.1://waitTRP20ns
131.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
132.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
133.
134.2://SendAutoRefreshCommand
135.beginrCMD<=_AR;i<=i+1'b1;end
136.
137.3://waitTRRC63ns
138.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
139.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
140.
141.4://SendAutoRefreshCommand
142.beginrCMD<=_AR;i<=i+1'b1;end
143.
144.5://waitTRRC63ns
145.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
146.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
147.
148./********************/
149.
150.6://Generatedonesignal
151.beginisDone<=1'b1;i<=i+1'b1;end
152.
153.7:
154.beginisDone<=1'b0;i<=4'd0;end
155.
156.endcase
[/code]
以上内容为部分核心操作。第124行的if(iCall[1])表示余下内容为刷新操作。
157.elseif(iCall[0])
[code]158.case(i)
159.
160.0://delay100us
161.if(C1==T100US-1)beginC1<=14'd0;i<=i+1'b1;end
162.elsebeginC1<=C1+1'b1;end
163.
164./********************/
165.
166.1://SendPrechargeCommand
167.beginrCMD<=_PR;{rBA,rA}<=15'h3fff;i<=i+1'b1;end
168.
169.2://waitTRP20ns
170.if(C1==TRP-1)beginC1<=14'd0;i<=i+1'b1;end
171.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
172.
173.3://SendAutoRefreshCommand
174.beginrCMD<=_AR;i<=i+1'b1;end
175.
176.4://waitTRRC63ns
177.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
178.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
179.
180.5://SendAutoRefreshCommand
181.beginrCMD<=_AR;i<=i+1'b1;end
182.
183.6://waitTRRC63ns
184.if(C1==TRRC-1)beginC1<=14'd0;i<=i+1'b1;end
185.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
186.
187./********************/
188.
189.7://SendLMRCmd.BurstRead&Write,3'b010meanCASlatecy=3,Sequential,1burstlength
190.beginrCMD<=_LMR;rBA<=2'b11;rA<={3'd0,1'b0,2'd0,3'b011,1'b0,3'b000};i<=i+1'b1;end
191.
192.8://Send2nopCLKfortMRD
193.if(C1==TMRD-1)beginC1<=14'd0;i<=i+1'b1;end
194.elsebeginrCMD<=_NOP;C1<=C1+1'b1;end
195.
196./********************/
197.
198.9://Generatedonesignal
199.beginisDone<=1'b1;i<=i+1'b1;end
200.
201.10:
202.beginisDone<=1'b0;i<=4'd0;end
203.
204.endcase
205.
[/code]
以上内容为部分核心操作。第157行的if(iCall[0])表示余下内容为初始化。
206.assign{S_CKE,S_NCS,S_NRAS,S_NCAS,S_NWE}=rCMD;
[code]207.assign{S_BA,S_A}={rBA,rA};
208.assignS_DQM=rDQM;
209.assignS_DQ=isOut?iData:16'hzzzz;
210.assignoDone=isDone;
211.assignoData=D1;
212.
213.endmodule
[/code]
以上内容为相关的输出驱动声明,注意iData直接驱动S_DQ。
sdram_ctrlmod.v
图18.10SDRAM控制模块的建模图。
前面说过该模块负责一些功能调用,此外该模块也负责定时刷新的计算,具体内容我们还是来看代码吧。
1.modulesdram_ctrlmod
[code]2.(
3.inputCLOCK,
4.inputRESET,
5.input[1:0]iCall,//[1]Write,[0]Read
6.output[1:0]oDone,
7.output[3:0]oCall,
8.inputiDone
9.);
10.parameterWRITE=4'd1,READ=4'd4,REFRESH=4'd7,INITIAL=4'd8;
11.parameterTREF=11'd1040;
12.
[/code]
以上内容为相关的出入端声明。第10行是各个入口地址的常量声明,第11行则是定时刷新的周期——7.8125us。
13.reg[3:0]i;
[code]14.reg[10:0]C1;
15.reg[3:0]isCall;//[3]Write[2]Read[1]A.Refresh[0]Initial
16.reg[1:0]isDone;
17.
18.always@(posedgeCLOCKornegedgeRESET)
19.if(!RESET)
20.begin
21.i<=INITIAL;//InitialSDRamatfirst
22.C1<=11'd0;
23.isCall<=4'b0000;
24.isDone<=2'b00;
25.end
[/code]
以上内容为相关的寄存器声明以及复位操作。第21行表示i首先会指向初始化。
26.else
[code]27.case(i)
28.
29.0://IDLE
30.if(C1>=TREF)beginC1<=11'd0;i<=REFRESH;end
31.elseif(iCall[1])beginC1<=C1+1'b1;i<=WRITE;end
32.elseif(iCall[0])beginC1<=C1+1'b1;i<=READ;end
33.elsebeginC1<=C1+1'b1;end
34.
35./***********************/
36.
[/code]
以上内容为部分核心操作。步骤0为待机状态,期间第33行的C1会一直递增,如果期间没有任何读写操作,而且C1的计数内容也超过TREF,那么C1会清零,i指向REFRESH(第30行)。反之,如果读写操作被使能,i指向相关的步骤入口,期间C1也会递增以示步骤翻转所用掉的时钟。
37.1://Write
[code]38.if(iDone)beginisCall[3]<=1'b0;C1<=C1+1'b1;i<=i+1'b1;end
39.elsebeginisCall[3]<=1'b1;C1<=C1+1'b1;end
40.
41.2:
42.beginisDone[1]<=1'b1;C1<=C1+1'b1;i<=i+1'b1;end
43.
44.3:
45.beginisDone[1]<=1'b0;C1<=C1+1'b1;i<=4’d0;end
46.
47./***********************/
48.
[/code]
以上内容为部分核心操作。步骤1~3是写操作。步骤1表示,功能模块反馈完成信号之前,C1会不停递增。当完成信号接收到手,isCall[3]拉低,C1递增,i也递增。步骤2~3则是用来反馈写操作的完成信号,期间C1也会递增。
49.4://Read
[code]50.if(iDone)beginisCall[2]<=1'b0;C1<=C1+1'b1;i<=i+1'b1;end
51.elsebeginisCall[2]<=1'b1;C1<=C1+1'b1;end
52.
53.5:
54.beginisDone[0]<=1'b1;C1<=C1+1'b1;i<=i+1'b1;end
55.
56.6:
57.beginisDone[0]<=1'b0;C1<=C1+1'b1;i<=4'd0;end
58.
59./***********************/
60.
[/code]
以上内容为部分核心操作。步骤4~6是读操作。步骤4表示接收完成信号之前,isCall[2]会不停拉高,C1也会不停递增...直至接收完成信号,isCall[2]才会拉低,然而C1也会递增。步骤5~6用反馈读操作的完成信号。
61.7://AutoRefresh
[code]62.if(iDone)beginisCall[1]<=1'b0;i<=4'd0;end
63.elsebeginisCall[1]<=1'b1;end
64.
65./***********************/
66.
[/code]
以上内容为部分核心操作。步骤7是刷新操作,接收完成信号之前isCall[1]会不停拉高,直至接收完成信号为止,isCall[1]才会拉低,然后i指向步骤0。
67.8://Initial
[code]68.if(iDone)beginisCall[0]<=1'b0;i<=4'd0;end
69.elsebeginisCall[0]<=1'b1;end
70.
71.endcase
72.
73.assignoDone=isDone;
74.assignoCall=isCall;
75.
76.endmodule
[/code]
以上内容为部分核心操作。步骤8用来执行初始化,接收完成信号之前,isCall[0]会不停拉高,直至接收完成信号为止,isCall[0]才会拉低,然后i指向步骤0。第73~74行则是相关的输出驱动。整体而言,除了读写操作必须反馈完成信号给上层以外,其余的定期刷新还有初始化都是该内部操作,所以不用反馈完成信号。
sdram_basemod.v
内容的连线部署完全依照图18.8。
1.modulesdram_basemod
[code]2.(
3.inputCLOCK,
4.inputRESET,
5.
6.outputS_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.input[23:0]iAddr,
15.input[15:0]iData,
16.output[15:0]oData
17.);
[/code]
以上内容为相关的出入端声明,第5~10行是顶层信号,第12~16行是模块左右两边的信号。
18.wire[3:0]CallU1;//[3]Refresh,[2]Read,[1]Write,[0]Initial
[code]19.
20.sdram_ctrlmodU1
21.(
22..CLOCK(CLOCK),
23..RESET(RESET),
24..iCall(iCall),//<top,[1]Write[0]Read
25..oDone(oDone),//>top,[1]Write[0]Read
26..oCall(CallU1),//>U2
27..iDone(DoneU2)//<U2
28.);
29.
[/code]
以上内容为控制模块的实例化。
30.wireDoneU2;
[code]31.
32.sdram_funcmodU2
33.(
34..CLOCK(CLOCK),
35..RESET(RESET),
36..S_CKE(S_CKE),//>top
37..S_NCS(S_NCS),//>top
38..S_NRAS(S_NRAS),//>top
39..S_NCAS(S_NCAS),//>top
40..S_NWE(S_NWE),//>top
41..S_BA(S_BA),//>top
42..S_A(S_A),//>top
43..S_DQM(S_DQM),//>top
44..S_DQ(S_DQ),//<>top
45..iCall(CallU1),//<U1
46..oDone(DoneU2),//>U1
47..iAddr(iAddr),//<top
48..iData(iData),//<top
49..oData(oData)//>top
50.);
51.
52.endmodule
[/code]
以上内容为功能模块的实例化。
sdram_demo.v
图18.11实验十八的建模图。
图18.11是实验十八的建模图,其中sdram_demo包含PLL模块,核心操作还有SDRAM基础模块。PLL模块将50Mhz的时钟倍频为133Mhz而且左移210°的CLOCK1,还有133Mhz的CLOCK2,它直接驱动S_CLK顶层信号。核心操作负责调用SDRAM基础模块,并且将读写内容经由TXD发送出去。SDRAM基础模块左边的问答信号只有两位,其中[1]为写[0]为读,具体内容我们还是来看代码吧。
1.modulesdram_demo
[code]2.(
3.inputCLOCK,
4.inputRESET,
5.outputS_CLK,
6.outputS_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.outputTXD
12.);
[/code]
以上内容为相关的出入端声明。
13.wireCLOCK1,CLOCK2;
[code]14.
15.pll_moduleU1
16.(
17..inclk0(CLOCK),//50Mhz
18..c0(CLOCK1),//133Mhz-210degreephase
19..c1(CLOCK2)//133Mhz
20.);
21.
[/code]
以上内容为PLL模块的实例化,CLOCK1为133Mhz频率并且左移210°,CLOCK2为133Mhz频率,并且直接驱动S_CLK。
22.wire[1:0]DoneU2;
23.wire[15:0]DataU2;
24.
25.sdram_basemodU2
26.(
27..CLOCK(CLOCK1),
28..RESET(RESET),
29..S_CKE(S_CKE),
30..S_NCS(S_NCS),
31..S_NRAS(S_NRAS),
32..S_NCAS(S_NCAS),
33..S_NWE(S_NWE),
34..S_A(S_A),
35..S_BA(S_BA),
36..S_DQM(S_DQM),
37..S_DQ(S_DQ),
38..iCall(isCall),
39..oDone(DoneU2),
40..iAddr(D1),
41..iData(D2),
42..oData(DataU2)
43.);
44.
以上内容为SDRAM基础模块的实例化,第40~41行表示iAddr为D1驱动,iData为D2驱动。
45.parameterB115K2=11'd1157,TXFUNC=6'd16;
[code]46.
47.reg[5:0]i,Go;
48.reg[10:0]C1;
49.reg[23:0]D1;
50.reg[15:0]D2,D3;
51.reg[10:0]T;
52.reg[1:0]isCall;
53.regrTXD;
54.
55.always@(posedgeCLOCK1ornegedgeRESET)
56.if(!RESET)
57.begin
58.i<=6'd0;
59.Go<=6'd0;
60.C1<=11'd0;
61.D1<=24'd0;
62.D2<=16'd0;
63.D3<=16'd0;
64.T<=11'd0;
65.isCall<=2'b00;
66.rTXD<=1'b1;
67.end
[/code]
以上内容为相关的寄存器以及复位操作。第45行是波特率为115200还有伪函数入口的常量声明。
[code]68.else
69.case(i)
70.
71.0:
72.if(DoneU2[1])beginisCall[1]<=1'b0;i<=i+1'b1;end
73.elsebeginisCall[1]<=1'b1;D1<=24'd0;D2<=16'hABCD;end
74.
75.1:
76.if(DoneU2[0])beginD3<=DataU2;isCall[0]<=1'b0;i<=i+1'b1;end
77.elsebeginD1<=24'd0;isCall[0]<=1'b1;end
78.
79.2:
80.beginT<={2'b11,D3[15:8],1'b0};i<=TXFUNC;Go<=i+1'b1;end
81.
82.3:
83.beginT<={2'b11,D3[7:0],1'b0};i<=TXFUNC;Go<=i+1'b1;end
84.
85.4:
86.i<=i;
87.
88./******************************/
89.
[/code]
以上内容为部分核心操作。步骤0将数据16’hABCD写入地址0。步骤1从地址0读出数据16’hABCD,并且暂存至D3。步骤2先发送D3的高8位,步骤3则发送D3的低8位。步骤4发呆。
90.16,17,18,19,20,21,22,23,24,25,26:
[code]91.if(C1==B115K2-1)beginC1<=11'd0;i<=i+1'b1;end
92.elsebeginrTXD<=T[i-16];C1<=C1+1'b1;end
93.
94.27:
95.i<=Go;
96.
97.endcase
98.
99.assignS_CLK=CLOCK2;
100.assignTXD=rTXD;
101.
102.endmodule
[/code]
以上内容为部分核心操作。步骤16~27是发送一帧数据的伪函数。第99~100行则是相关的输出驱动。综合完毕并且下载程序,如果串口调试软件出现ABCD等两字节数据,结果表示实验成功。
细节一:完整的个体模块
SDRAM基础模块已经就绪完毕。
细节二:其它时序参数
驱动SDRAM最大的收获莫过于学习各种稀奇古怪的时序参数,虽然实验十六的IIC,也有时序参数,但是前者好比一粒面包屑,后者则是一片面包,两种时序参数有“体积”上的明确差距。笔者曾经说过,时序参数即时间要求有第一层与第二层之分,第一层时间要求正如IIC的时序参数,打得像面包一样...反之,第二层时间要求宛如SDRAM的时序参数,小得似面包屑一般。
SDRAM的时序参数除了tRP,TRRC,TMRD,CASLatency等这些东西以外,它还有更为极为,而且不能控制的时序参数。更确切来说,这些时序参数都属于物理因数的范围...难得有机会学习SDRAM,笔者就稍微聊聊它们吧。
图18.12时序参数①。
图18.12是读操作的部分时序,当CL得到满足以后,数据就会被吐出来,其中:
TLZ(TLOZ)为clocktodataoutputinlow-Ztime。简单来说,就是数据被出发沿吐出之前,必须经过的延迟时间。根据手册,133Mhz为1ns。
TAC为accesstimefromclock。简单来说就是有效时间。根据手册,133Mhz为5.4ns
TOH为dataoutholdtime。简单来说就是常见的THOLD。根据手册,133Mhz为2.5ns
图18.13时序参数②。
图18.13是写操作的部分时序图,然而重点家伙就是当中T××S或者T××H。一般××是指数据的属性或者类别,不过S与H都有相同的意义,就是典型的TSETUP还有THOLD。笔者习惯称呼它们为寄存器特性,因为只要任何一方得不到满足,数据读入寄存器就得不到保证。寄存器特性好比哥布林一样,数量常常多到令人喷饭,如果一一分析会耗死爷爷不偿命。
图18.14对外的理想时序。
为了用足一支竹竿扫尽一切,笔者才故意向将CLOCK1左移180°测试手气,看看SDRAM能不能读出正确的结果,如果不是再追加位移或者减少位移以致修正,结果如图18.14所示。一般而言,T××S或者T××H这些家伙都会得到满足,然后乖乖就范。话虽如此,同学们还须注意,Verilog充其量只能满足第二层的时间要求,却不能涉及(解决)其中,我们往往只能依赖运气与直觉。当然,我们可以借助静态时序分析的力量去搞定一切,有兴趣的朋友请看《工具篇I》。
相关文章推荐
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十一:SDRAM模块④ — 页读写 β
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十二:SDRAM模块⑤ — FIFO读写
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十九:SDRAM模块② — 多字读写
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十:SDRAM模块③ — 页读写 α
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验七:PS/2模块① — 键盘
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十三:串口模块② — 接收
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】【实验一】流水灯模块
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验三:按键模块② — 点击与长点击
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十五:FIFO储存模块(同步)
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验五:按键模块④ — 点击,长点击,双击
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十一:PS/2模块⑤ — 扩展鼠标
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】【实验一】流水灯模块
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十:PS/2模块④ — 普通鼠标
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十二:串口模块① — 发送
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十六:VGA模块
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验四:按键模块③ — 单击与双击
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二:按键模块① - 消抖
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十五:SDHC模块
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十九:LCD模块
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十四:储存模块