在Kevin写的上一篇博文《SDRAM理论篇之基础知识及操作时序》中,已经把SDRAM工作的基本原理和SDRAM初始化、读、写及自动刷新操作的时序讲清楚了,在这一片博文中,Kevin来根据在上一篇博文中分析的思路来把写一个简单的SDRAM控制器。
我们在上一篇博文中提到了这样一个问题,SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?因为我们是肯定需要保证写的数据不能丢失,所以,我们可以考虑这样来做:如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作。而如果在执行读操作也遇到需要刷新的情况,我们也可以这样来做,先让数据读完,再去执行刷新操作。
大家看完可能会想,说是这么说,那代码怎么来写呢?似乎还是没什么思路。大家可以想象一下,我们写的SDRAM控制器是肯定包括初始化、读操作、写操作及自动刷新这些操作的,既然这样,我们就可以给每一个操作写上一个模块独立开来,这样也便于我们每个模块的调试,显然这种思路是正确的。那怎么让我们的各个模块工作起来呢,虽然都是独立的模块,但很显然这几个模块之间又是相互关联的。就拿上面刚才说的那个情况来讲,如果SDRAM需要刷新了,而SDRAM却正在执行写操作,那我们刷新模块与写模块之间怎么进行控制呢?这个问题解决了,读模块与刷新模块之间的这个问题也可以很轻松的解决。大家不妨可以自己先想一下。
主状态机与各模块间的连线
为了解决各个模块之间不方便控制的情况,我们引入一个新的机制 ——“仲裁”机制。“仲裁”用来干什么呢?在这里边,“仲裁”相当于我们这个SDRAM控制器的老大,对SDRAM的各个操作统一协调:读、写及自动刷新都由“仲裁”来控制。说到这里,显然我们可以再写一个“仲裁”模块,既然在仲裁模块中要控制这么多操作,那自然而然的肯定想到了利用状态机。那我们的状态机怎么来设计呢?请看下图:
只给一个状态机的图,Kevin还是觉得不够说明问题,再上一个模块之间的示意图:
在讲之前,Kevin 要给大家打一下预防针:在接下来讲的过程中,大家一定要搞清楚Kevin说的是模块之间连线的关系还是状态机之间跳转的关系哦。
在仲裁模块中,初始化操作完成之后便进入到了“ARBIT”仲裁状态,只有处于仲裁状态的时候,“仲裁老大”才能进行下命令。我们先来模拟一下,当状态机处于“WRITE”写状态时,如果SDRAM刷新的时间到了,刷新模块同时向写模块和仲裁模块发送刷新请求ref_req信号,当写模块接受到ref_req之后,写模块在写完当前4个数据(突发长度为4)之后,写模块的写结束标志flag_wr_end拉高,然后状态机进入“ARBIT”仲裁状态,处于仲裁状态之后,此时有刷新请求ref_req,然后状态机跳转到“AREF”状态并且仲裁模块发送ref_en刷新使能,然后呢,刷新模块将刷新请求信号ref_req拉低并给sdram发送刷新的命令。等刷新完毕之后,刷新模块给仲裁模块发送flag_ref_end刷新结束标志,状态机跳转到“ARBIT”仲裁状态。
注意了,当刷新完跳转到“ARBIT”仲裁状态之后,如果之前我们的全部数据仍然没有写完(Kevin指的是全部数据,并不是一个突发长度的4个数据哦),那么此时我们仍然要给仲裁模块写请求“wr_req”,然后仲裁模块经过一系列判断之后,如果符合写操作的时机,那就给写模块一个写使能信号“wr_en”,然后跳转到“WRITE”写状态并且写模块开始工作。
对于读模块与刷新操作之间的协调,相信大家应该也能想象得到了,Kevin在这里就不再啰嗦了。
仲裁模块(顶层模块)代码介绍
下面先看一下在我们的仲裁模块(顶层模块)中状态机的定义:
- //state
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- state<=IDLE;
- else case(state)
- IDLE:
- if(key[0] == 1'b1)
- state<=INIT;
- else
- state<=IDLE;
- INIT:
- if(flag_init_end == 1'b1)//初始化结束标志
- state<=ARBIT;
- else
- state<=INIT;
- ARBIT:
- if(ref_req == 1'b1)//刷新请求到来且已经写完
- state<=AREF;
- else if(ref_req == 1'b0 && rd_en == 1'b1)//默认读操作优先于写操作
- state<=READ;
- else if(ref_req == 1'b0 && wr_en == 1'b1)//无刷新请求且写请求到来
- state<=WRITE;
- else
- state<=ARBIT;
- AREF:
- if(flag_ref_end == 1'b1)
- state<=ARBIT;
- else
- state<=AREF;
- WRITE:
- if(flag_wr_end == 1'b1)
- state<=ARBIT;
- else
- state<=WRITE;
- READ:
- if(flag_rd_end == 1'b1)
- state<=ARBIT;
- else
- state<=READ;
- default:
- state<=IDLE;
- endcase
下面简单的介绍一下状态机代码:
key[0]作为我们初始化的一个使能信号,如果是实际下板子的时候,我们还需要给按键加一个按键消抖模块。当按键0按下之后,代表我们的SDRAM的初始化使能信号来了,所以状态机从“IDLE”跳转到了“INIT”状态。在初始化状态,如果我们的初始化模块传来了初始化结束标志“flag_init_end”,那状态机跳转到“ARBIT”仲裁状态,在仲裁状态中,第一个“if”是判断刷新请求的,这也就说明了我们刷新的优先级最高。之后,如果处于仲裁状态,来了读使能信号或者写使能信号并且没有刷新请求,那状态机就跳转到对应的状态。如果处于读或写的状态,当读结束标志或者写结束标志来临的时候(这里的写结束标志和读结束标志都是指突发读或突发写的结束标志),那么就会跳转到仲裁状态。
初始化模块代码简单介绍
下面再简单的看下初始化模块中的代码:
- /***********************************************
- *ModuleName:sdram_init
- *Engineer:Kevin
- *Function:sdram初始化模块
- *Date:2016.01.10
- *BlogWebsite:dengkanwen.com
- *Version:v1.0
- ***********************************************/
- modulesdram_init(
- inputwiresclk,//系统时钟为50M,即T=20ns
- inputwires_rst_n,
- outputreg[3:0]cmd_reg,//sdram命令寄存器
- outputreg[11:0]sdram_addr,//地址线
- outputreg[1:0]sdram_bank,//bank地址
- outputregflag_init_end//sdram初始化结束标志
- );
- parameterCMD_END=4'd11,//初始化结束时的命令计数器的值
- CNT_200US=14'd1_0000,
- NOP=4'b0111,//空操作命令
- PRECHARGE=4'b0010,//预充电命令
- AUTO_REF=4'b0001,//自刷新命令
- MRSET=4'b0000;//模式寄存器设置命令
-
- reg[13:0]cnt_200us;//200us计数器
- regflag_200us;//200us结束标志(200us结束后,一直拉高)
- reg[3:0]cnt_cmd;//命令计数器,便于控制在某个时候发送特定指令
- regflag_init;//初始化标志:初始化结束后,该标志拉低
- //flag_init
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_init<=1'b1;
- else if(cnt_cmd == CMD_END)
- flag_init<=1'b0;
- //cnt_200us
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cnt_200us<=14'd0;
- else if(cnt_200us == CNT_200US)
- cnt_200us<=14'd0;
- else if(flag_200us == 1'b0)
- cnt_200us<=cnt_200us + 1'b1;
- //flag_200us
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_200us<=1'b0;
- else if(cnt_200us == CNT_200US)
- flag_200us<=1'b1;
- //cnt_cmd
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cnt_cmd<=4'd0;
- else if(flag_200us == 1'b1 && flag_init == 1'b1)
- cnt_cmd<=cnt_cmd + 1'b1;
- //flag_init_end
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_init_end<=1'b0;
- else if(cnt_cmd == CMD_END)
- flag_init_end<=1'b1;
- else
- flag_init_end<=1'b0;
- //cmd_reg
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cmd_reg<=NOP;
- else if(cnt_200us == CNT_200US)
- cmd_reg<=PRECHARGE;
- else if(flag_200us)
- case(cnt_cmd)
- 4'd0:
- cmd_reg<=AUTO_REF;//预充电命令
- 4'd6:
- cmd_reg<=AUTO_REF;
- 4'd10:
- cmd_reg<=MRSET;//模式寄存器设置
- default:
- cmd_reg<=NOP;
- endcase
- //sdram_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_addr<=12'd0;
- else case(cnt_cmd)
- 4'd0:
- sdram_addr<=12'b0100_0000_0000;//预充电时,A10拉高,对所有Bank操作
- 4'd10:
- sdram_addr<=12'b0000_0011_0010;//模式寄存器设置时的指令:CAS=2,Burst Length=4;
- default:
- sdram_addr<=12'd0;
- endcase
- //sdram_bank
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_bank<=2'd0;//这里仅仅只是初始化,在模式寄存器设置时才会用到且其值为全零,故不赋值
-
- //sdram_clk
- assignsdram_clk=~sclk;
- endmodule
下面我们来结合代码回顾下初始化过程:
首先,我们需要有200us的稳定期,所以我们便有了一个200us的计数器cnt_200us,而这个计数器是根据flag_200us的低电平来工作的。大家可以看到,falg_200us在200us计时之后一直拉高。在200us计满,即flag_200us拉高之后,我们就需要先给一个“NOP”命令,然后给两次“Precharge”命令,同时选中ALL Banks。
SDRAM写模块介绍
下面咱们先不对SDRAM的初始化进行仿真,等把全部的模块讲完再来仿真。接下来再继续说写操作:首先在我们的仲裁模块,初始化完成之后,就已经跳转到“ARBIT”仲裁状态了,然后,我们在testbench中模拟一个外部的写请求信号。咱们先看下写模块的代码:
- modulesdram_write(
- inputwiresclk,
- inputwires_rst_n,
- inputwirekey_wr,
- inputwirewr_en,//来自仲裁模块的写使能
- inputwireref_req,//来自刷新模块的刷新请求
- inputwire[5:0]state,//顶层模块的状态
- outputreg[15:0]sdram_dq,//sdram输入/输出端口
- //outputreg[3:0]sdram_dqm,//输入/输出掩码
- outputreg[11:0]sdram_addr,//sdram地址线
- outputreg[1:0]sdram_bank,//sdram的bank地址线
- outputreg[3:0]sdram_cmd,//sdram的命令寄存器
- outputregwr_req,//写请求(不在写状态时向仲裁进行写请求)
- outputregflag_wr_end//写结束标志(有刷新请求来时,向仲裁输出写结束)
- );
- parameterNOP=4'b0111,//NOP命令
- ACT=4'b0011,//ACT命令
- WR=4'b0100,//写命令(需要将A10拉高)
- PRE=4'b0010,//precharge命令
- CMD_END=4'd8,
- COL_END=9'd508,//最后四个列地址的第一个地址
- ROW_END=12'd4095,//行地址结束
- AREF=6'b10_0000,//自动刷新状态
- WRITE=6'b00_1000;//状态机的写状态
- regflag_act;//需要发送ACT的标志
- reg[3:0]cmd_cnt;//命令计数器
- reg[11:0]row_addr;//行地址
- reg[11:0]row_addr_reg;//行地址寄存器
- reg[8:0]col_addr;//列地址
- regflag_pre;//在sdram内部为写状态时需要给precharge命令的标志
-
- //flag_pre
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_pre<=1'b0;
- else if(col_addr == 9'd0 && flag_wr_end == 1'b1)
- flag_pre<=1'b1;
- else if(flag_wr_end == 1'b1)
- flag_pre<=1'b0;
- //flag_act
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_act<=1'b0;
- else if(flag_wr_end)
- flag_act<=1'b0;
- else if(ref_req == 1'b1 && state == AREF)
- flag_act<=1'b1;
- //wr_req
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- wr_req<=1'b0;
- else if(wr_en == 1'b1)
- wr_req<=1'b0;
- else if(state != WRITE && key_wr == 1'b1)
- wr_req<=1'b1;
- //flag_wr_end
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_wr_end<=1'b0;
- else if(cmd_cnt == CMD_END)
- flag_wr_end<=1'b1;
- else
- flag_wr_end<=1'b0;
- //cmd_cnt
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cmd_cnt<=4'd0;
- else if(state == WRITE)
- cmd_cnt<=cmd_cnt + 1'b1;
- else
- cmd_cnt<=4'd0;
- //sdram_cmd
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_cmd<=4'd0;
- else case(cmd_cnt)
- 3'd1:
- if(flag_pre == 1'b1)
- sdram_cmd<=PRE;
- else
- sdram_cmd<=NOP;
- 3'd2:
- if(flag_act == 1'b1 || col_addr == 9'd0)
- sdram_cmd<=ACT;
- else
- sdram_cmd<=NOP;
- 3'd3:
- sdram_cmd<=WR;
-
- default:
- sdram_cmd<=NOP;
- endcase
- //sdram_dq
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_dq<=16'd0;
- else case(cmd_cnt)
- 3'd3:
- sdram_dq<=16'h0012;
- 3'd4:
- sdram_dq<=16'h1203;
- 3'd5:
- sdram_dq<=16'h562f;
- 3'd6:
- sdram_dq<=16'hfe12;
- default:
- sdram_dq<=16'd0;
- endcase
- /* //sdram_dq_m
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_dqm<=4'd0; */
- //row_addr_reg
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- row_addr_reg<=12'd0;
- else if(row_addr_reg == ROW_END && col_addr == COL_END && cmd_cnt == CMD_END)
- row_addr_reg<=12'd0;
- else if(col_addr == COL_END && flag_wr_end == 1'b1)
- row_addr_reg<=row_addr_reg + 1'b1;
- //row_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- row_addr<=12'd0;
- else case(cmd_cnt)
- //因为下边的命令是通过行、列地址分开再给addr赋值,所以需要提前一个周期赋值,以保证在命令到来时能读到正确的地址
- 3'd2:
- row_addr<=12'b0000_0000_0000;//在写命令时,不允许auto-precharge
- default:
- row_addr<=row_addr_reg;
- endcase
- //col_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- col_addr<=9'd0;
- else if(col_addr == COL_END && cmd_cnt == CMD_END)
- col_addr<=9'd0;
- else if(cmd_cnt == CMD_END)
- col_addr<=col_addr + 3'd4;
- //sdram_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_addr<=12'd0;
- else case(cmd_cnt)
- 3'd2:
- sdram_addr<=row_addr;
- 3'd3:
- sdram_addr<=col_addr;
- default:
- sdram_addr<=row_addr;
- endcase
- //sdram_bank
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_bank<=2'b00;
- endmodule
在我们的模块端口列表中,用key_wr来接收写请求信号,这个写请求信号,是在没有写完之前一直拉高的,在写完了全部数据之后才拉低的。当然这个代码的话,还是按照Kevin在上一篇博文中的理论来的,大家只要好好理解理论就可以很轻松的明白为什么代码要这样写了。
在写模块中,Kevin是让SDRAM循环着写16’h0012,16’h1203,16’h562f,16’hfe12这四个数据。
另外一点,在我们的这个写模块中,Kevin是在每写完4个数据,也就是突发结束后,有一个写完标志,从而使状态机跳转到仲裁状态,然后如果数据没写完,由于写请求是拉高的,所以如果此时没有刷新请求,那状态机还是会跳转到写状态继续写的。
SDRAM读操作模块
首先,咱们依然先上代码:
- modulesdram_read(
- inputwiresclk,
- inputwires_rst_n,
- inputwirerd_en,
- inputwire[5:0]state,
- inputwireref_req,//自动刷新请求
- inputwirekey_rd,//来自外部的读请求信号
- inputwire[15:0]rd_dq,//sdram的数据端口
- outputreg[3:0]sdram_cmd,
- outputreg[11:0]sdram_addr,
- outputreg[1:0]sdram_bank,
- outputregrd_req,//读请求
- outputregflag_rd_end//突发读结束标志
- );
-
- parameterNOP=4'b0111,
- PRE=4'b0010,
- ACT=4'b0011,
- RD=4'b0101,//SDRAM的读命令(给读命令时需要给A10拉低)
- CMD_END=4'd12,//
- COL_END=9'd508,//最后四个列地址的第一个地址
- ROW_END=12'd4095,//行地址结束
- AREF=6'b10_0000,//自动刷新状态
- READ=6'b01_0000;//状态机的读状态
- reg[11:0]row_addr;
- reg[8:0]col_addr;
- reg[3:0]cmd_cnt;
- regflag_act;//发送ACT命令标志(单独设立标志,便于跑高速)
-
- //flag_act
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_act<=1'b0;
- else if(flag_rd_end == 1'b1 && ref_req == 1'b1)
- flag_act<=1'b1;
- else if(flag_rd_end == 1'b1)
- flag_act<=1'b0;
- //rd_req
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- rd_req<=1'b0;
- else if(rd_en == 1'b1)
- rd_req<=1'b0;
- else if(key_rd == 1'b1 && state != READ)
- rd_req<=1'b1;
- //cmd_cnt
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cmd_cnt<=4'd0;
- else if(state == READ)
- cmd_cnt<=cmd_cnt + 1'b1;
- else
- cmd_cnt<=4'd0;
- //flag_rd_end
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_rd_end<=1'b0;
- else if(cmd_cnt == CMD_END)
- flag_rd_end<=1'b1;
- else
- flag_rd_end<=1'b0;
- //row_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- row_addr<=12'd0;
- else if(row_addr == ROW_END && col_addr == COL_END && flag_rd_end == 1'b1)
- row_addr<=12'd0;
- else if(col_addr == COL_END && flag_rd_end == 1'b1)
- row_addr<=row_addr + 1'b1;
- //col_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- col_addr<=9'd0;
- else if(col_addr == COL_END && flag_rd_end == 1'b1)
- col_addr<=9'd0;
- else if(flag_rd_end == 1'b1)
- col_addr<=col_addr + 3'd4;
- //cmd_cnt
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cmd_cnt<=4'd0;
- else if(state == READ)
- cmd_cnt<=cmd_cnt + 1'b1;
- else
- cmd_cnt<=4'd0;
- //sdram_cmd
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_cmd<=NOP;
- else case(cmd_cnt)
- 4'd2:
- if(col_addr == 9'd0)
- sdram_cmd<=PRE;
- else
- sdram_cmd<=NOP;
- 4'd3:
- if(flag_act == 1'b1 || col_addr == 9'd0)
- sdram_cmd<=ACT;
- else
- sdram_cmd<=NOP;
- 4'd4:
- sdram_cmd<=RD;
- default:
- sdram_cmd<=NOP;
- endcase
- //sdram_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_addr<=12'd0;
- else case(cmd_cnt)
- 4'd4:
- sdram_addr<={3'd0, col_addr};
- default:
- sdram_addr<=row_addr;
- endcase
- //sdram_bank
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_bank<=2'd0;
-
- endmodule
其实读模块和写模块是极其相似的,所以在这里,Kevin就不做赘述,大家慢慢消化吧。
SDRAM自动刷新模块
自动刷新模块算是比较简单的,等15us的时间过了,就向仲裁模块发刷新请求,然后在刷新完成之后,产生刷新结束标志:
- /*****************************************************************
- *ModuleName:auto_refresh
- *Enegineer:Kevin
- *Function:sdram自动刷新
- *BlogWebsite:http://dengkanwen.com
- *Comment:在这个模块中并没有bank地址的输出线,需要在顶层模块中设置
- ******************************************************************/
- moduleauto_refresh(
- inputwiresclk,
- inputwires_rst_n,
- inputwireref_en,
- inputwireflag_init_end,//初始化结束标志(初始化结束后,启动自刷新标志)
- outputreg[11:0]sdram_addr,
- outputreg[1:0]sdram_bank,
- outputregref_req,
- outputreg[3:0]cmd_reg,
- outputregflag_ref_end
- );
-
- parameterBANK=12'd0100_0000_0000,//自动刷新是对所有bank刷新
- CMD_END=4'd10,
- CNT_END=10'd749,//15us计时结束
- NOP=4'b0111,//
- PRE=4'b0010,//precharge命令
- AREF=4'b0001;//auto-refresh命令
- reg[9:0]cnt_15ms;//15ms计数器
- regflag_ref;//处于自刷新阶段标志
- regflag_start;//自动刷新启动标志
- reg[3:0]cnt_cmd;//指令计数器
- //flag_start
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_start<=1'b0;
- else if(flag_init_end == 1'b1)
- flag_start<=1'b1;
- //cnt_15ms
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cnt_15ms<=10'd0;
- else if(cnt_15ms == CNT_END)
- cnt_15ms<=10'd0;
- else if(flag_start == 1'b1)
- cnt_15ms<=cnt_15ms + 1'b1;
- //flag_ref
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_ref<=1'b0;
- else if(cnt_cmd == CMD_END)
- flag_ref<=1'b0;
- else if(ref_en == 1'b1)
- flag_ref<=1'b1;
- //cnt_cmd
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cnt_cmd<=4'd0;
- else if(flag_ref == 1'b1)
- cnt_cmd<=cnt_cmd + 1'b1;
- else
- cnt_cmd<=4'd0;
- //flag_ref_end
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_ref_end<=1'b0;
- else if(cnt_cmd == CMD_END)
- flag_ref_end<=1'b1;
- else
- flag_ref_end<=1'b0;
- //cmd_reg
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- cmd_reg<=NOP;
- else case(cnt_cmd)
- 3'd0:
- if(flag_ref == 1'b1)
- cmd_reg<=PRE;
- else
- cmd_reg<=NOP;
- 3'd1:
- cmd_reg<=AREF;
- 3'd5:
- cmd_reg<=AREF;
- default:
- cmd_reg<=NOP;
- endcase
- //sdram_addr
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_addr<=12'd0;
- else case(cnt_cmd)
- 4'd0:
- sdram_addr<=BANK;//bank进行刷新时指定allbank or signle bank
- default:
- sdram_addr<=12'd0;
- endcase
- //sdram_bank
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- sdram_bank<=2'd0;//刷新指定的bank
- //ref_req
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- ref_req<=1'b0;
- else if(ref_en == 1'b1)
- ref_req<=1'b0;
- else if(cnt_15ms == CNT_END)
- ref_req<=1'b1;
- //flag_ref_end
- always@(posedge sclk or negedge s_rst_n)
- if(s_rst_n == 1'b0)
- flag_ref_end<=1'b0;
- else if(cnt_cmd == CMD_END)
- flag_ref_end<=1'b1;
- else
- flag_ref_end<=1'b0;
-
- endmodule
至此,咱们的整个设计就已经讲完了,至于模块之间怎么连线,Kevin就不再硬性灌输了,留给大家自己完成吧。当然Kevin还是想提醒大家,因为SDRAM的数据总线是双向的,所以需要弄个三态门,在向SDRAM写数据的时候,模块定义的数据总线应为输出型,在接收数据时,需要定义成高阻态。
SDRAM仿真
设计讲完了,咱们来说下仿真,在我们仿真的时候,我们需要用到SDRAM的仿真模型(关于仿真模型,Kevin已经上传到“福利/文档手册”这个栏目下了)。
因为我们需要有一个200us的稳定期,所以我们可以先让Modelsim跑个200us,可以直接在命令窗口输入”run 200us”;200us的稳定器过了之后,接下来应该就是咱们的初始话了,所以我们在让modelsim跑600ns,下面是仿真的结果:
在上边的仿真中,我们已经知道SDRAM已经初始化成功了,设置的潜伏期为3,突发长度为4
下面我们再运行一段时间,就运行1us吧,往SDRAM中写数据:
这里的写的数据,就是咱们在写模块中设置的那4个数,只是之前的是用16进制定义的,这里显示的是10进制。
然后我们再看一下读数据:
大家可以看下,我们读出来的数据和写进来的数据是不是一样的呢?
0 0