SDRAM驱动篇之简易SDRAM控制器的verilog代码实现
在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”是判断刷新请求的,这也就说明了我们刷新的优先级最高。之后,如果处于仲裁状态,来了读使能信号或者写使能信号并且没有刷新请求,那状态机就跳转到对应的状态。如果处于读或写的状态,当读结束标志或者写结束标志来临的时候(这里的写结束标志和读结束标志都是指突发读或突发写的结束标志),那么就会跳转到仲裁状态。
初始化模块代码简单介绍
下面再简单的看下初始化模块中的代码:
- /***********************************************
- * Module Name : sdram_init
- * Engineer : Kevin
- * Function : sdram初始化模块
- * Date : 2016.01.10
- * Blog Website : dengkanwen.com
- * Version : v1.0
- ***********************************************/
- module sdram_init(
- input wire sclk, //系统时钟为50M,即T=20ns
- input wire s_rst_n,
- output reg [3:0] cmd_reg, //sdram命令寄存器
- output reg [11:0] sdram_addr, //地址线
- output reg [1:0] sdram_bank, //bank地址
- output reg flag_init_end //sdram初始化结束标志
- );
- parameter CMD_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计数器
- reg flag_200us; //200us结束标志(200us结束后,一直拉高)
- reg [3:0] cnt_cmd; //命令计数器,便于控制在某个时候发送特定指令
- reg flag_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
- assign sdram_clk = ~sclk;
- endmodule
下面我们来结合代码回顾下初始化过程:
首先,我们需要有200us的稳定期,所以我们便有了一个200us的计数器cnt_200us,而这个计数器是根据flag_200us的低电平来工作的。大家可以看到,falg_200us在200us计时之后一直拉高。在200us计满,即flag_200us拉高之后,我们就需要先给一个“NOP”命令,然后给两次“Precharge”命令,同时选中ALL Banks。
SDRAM写模块介绍
下面咱们先不对SDRAM的初始化进行仿真,等把全部的模块讲完再来仿真。接下来再继续说写操作:首先在我们的仲裁模块,初始化完成之后,就已经跳转到“ARBIT”仲裁状态了,然后,我们在testbench中模拟一个外部的写请求信号。咱们先看下写模块的代码:
- module sdram_write(
- input wire sclk,
- input wire s_rst_n,
- input wire key_wr,
- input wire wr_en, //来自仲裁模块的写使能
- input wire ref_req, //来自刷新模块的刷新请求
- input wire [5:0] state, //顶层模块的状态
- output reg [15:0] sdram_dq, //sdram输入/输出端口
- //output reg [3:0] sdram_dqm, //输入/输出掩码
- output reg [11:0] sdram_addr, //sdram地址线
- output reg [1:0] sdram_bank, //sdram的bank地址线
- output reg [3:0] sdram_cmd, //sdram的命令寄存器
- output reg wr_req, //写请求(不在写状态时向仲裁进行写请求)
- output reg flag_wr_end //写结束标志(有刷新请求来时,向仲裁输出写结束)
- );
- parameter NOP = 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; //状态机的写状态
- reg flag_act; //需要发送ACT的标志
- reg [3:0] cmd_cnt; //命令计数器
- reg [11:0] row_addr; //行地址
- reg [11:0] row_addr_reg; //行地址寄存器
- reg [8:0] col_addr; //列地址
- reg flag_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读操作模块
首先,咱们依然先上代码:
- module sdram_read(
- input wire sclk,
- input wire s_rst_n,
- input wire rd_en,
- input wire [5:0] state,
- input wire ref_req, //自动刷新请求
- input wire key_rd, //来自外部的读请求信号
- input wire [15:0] rd_dq, //sdram的数据端口
- output reg [3:0] sdram_cmd,
- output reg [11:0] sdram_addr,
- output reg [1:0] sdram_bank,
- output reg rd_req, //读请求
- output reg flag_rd_end //突发读结束标志
- );
- parameter NOP = 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;
- reg flag_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的时间过了,就向仲裁模块发刷新请求,然后在刷新完成之后,产生刷新结束标志:
- /*****************************************************************
- * Module Name : auto_refresh
- * Enegineer : Kevin
- * Function : sdram自动刷新
- * Blog Website : http://dengkanwen.com
- * Comment : 在这个模块中并没有bank地址的输出线,需要在顶层模块中设置
- ******************************************************************/
- module auto_refresh(
- input wire sclk,
- input wire s_rst_n,
- input wire ref_en,
- input wire flag_init_end, //初始化结束标志(初始化结束后,启动自刷新标志)
- output reg [11:0] sdram_addr,
- output reg [1:0] sdram_bank,
- output reg ref_req,
- output reg [3:0] cmd_reg,
- output reg flag_ref_end
- );
- parameter BANK = 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计数器
- reg flag_ref; //处于自刷新阶段标志
- reg flag_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进制。
然后我们再看一下读数据:
大家可以看下,我们读出来的数据和写进来的数据是不是一样的呢?
转载:http://blog.csdn.net/eydwyz/article/details/52945642
SDRAM驱动篇之简易SDRAM控制器的verilog代码实现的更多相关文章
- 【转】S3C2440存储系统-SDRAM驱动
SDRAM(Synchronous Dynamic Random Access Memory,同步动态随机存储器)也就是通常所说的内存.内存的工作原理.控制时序.及相关控制器的配置方法一直是嵌入式系统 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】连载导读
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- 【黑金原创教程】【FPGA那些事儿-驱动篇I 】原创教程连载导读【连载完成,共二十九章】
前言: 无数昼夜的来回轮替以后,这本<驱动篇I>终于编辑完毕了,笔者真的感动到连鼻涕也流下来.所谓驱动就是认识硬件,还有前期建模.虽然<驱动篇I>的硬件都是我们熟悉的老友记,例 ...
- JZ2440 裸机驱动 第6章 存储控制器
本章目标: 了解S3C2410/S3C2440地址空间的布局 掌握如何通过总线形式访问扩展的外设,比如内存.NOR Flash.网卡等 ························ ...
- Linux SPI总线和设备驱动架构之三:SPI控制器驱动
通过第一篇文章,我们已经知道,整个SPI驱动架构可以分为协议驱动.通用接口层和控制器驱动三大部分.其中,控制器驱动负责最底层的数据收发工作,为了完成数据的收发工作,控制器驱动需要完成以下这些功能:1. ...
- ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程
ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程 原文地址:http://www.cnblogs.com/NickQ/p/9026545.html 一.开发板与ds18b20的入门 ...
- linux设备驱动第四篇:从如何定位oops的代码行谈驱动调试方法
上一篇我们大概聊了如何写一个简单的字符设备驱动,我们不是神,写代码肯定会出现问题,我们需要在编写代码的过程中不断调试.在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序 ...
- 理解 Android Binder 机制(一):驱动篇
Binder的实现是比较复杂的,想要完全弄明白是怎么一回事,并不是一件容易的事情. 这里面牵涉到好几个层次,每一层都有一些模块和机制需要理解.这部分内容预计会分为三篇文章来讲解.本文是第一篇,首先会对 ...
- 羽夏看Win系统内核——驱动篇
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
随机推荐
- [BZOJ 3144] 切糕
Link: BZOJ 3144 传送门 Solution: 发现要把点集分成不连通的两部分,最小割的模型还是很明显的 首先我们将原图转化为$R+1$层,从而将点权化为边权 关键还是在于建图是怎么保证$ ...
- 5.7(java学习笔记)Vector、Enumeration
一.Vector Vector类实现一个可扩展的数组对象.与数组一样,它包含可以使用整数索引访问. 它的基本操作方法add(int index, E element),get(int index),i ...
- 调用上一个页面的js方法
点击商品分类,弹出下框 点击确定,将选中的类别的name和唯一的code返回到上个页面 function save(){ var ids = getIdSelections(); jp.get(&qu ...
- LongPathException问题解析
一.背景 当windows系统下使用System.IO命名空间下的方法,目录长度超过260个字符时,.net framework会抛出LongPathException.查阅相关资料,发现是 ...
- hdu 1054 Strategic Game(tree dp)
Strategic Game Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- VB6的UTF8编码解码
'UTF-8编码 Public Function UTF8Encode(ByVal szInput As String) As String Dim wch As String D ...
- ubuntu ufw防火墙简易教程(转)
ufw是一个主机端的iptables类防火墙配置工具,比较容易上手.一般桌面应用使用ufw已经可以满足要求了. 安装方法 sudo apt-get install ufw 当然,这是有图形界面的(比较 ...
- Android之——获取手机安装的应用程序
转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47114331 前几篇有关Android的博文中.向大家介绍了几个项目中经常使用的有 ...
- 学习ajax总结
之前公司的ajax学习分享,做一点总结,加深记忆 什么是ajax? 异步的的js和xml,用js异步形式操作xml,工作主要是数据交互 借阅用户操作时间,减少数据请求,可以无刷新请求数据 创建一个对象 ...
- 微信小程序 - 滑动显示地点信息(map)
演示效果如下: 资源如下 marker,png index.wxml <view class="map-container"> <map id="map ...