简易SDRAM控制器的verilog代码实现

简易SDRAM控制器的verilog代码实现SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?需要保证写的数据不能丢失,所以,如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作;而如果在执行读操作也遇到需要刷新的情况,也可以先让数据读完,再去执行刷新操作。思路:SDRAM控制器包括初始化、读操作、写操作…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

SDRAM是每隔15us进行刷新一次,但是如果当SDRAM需要进行刷新时,而SDRAM正在写数据,这两个操作之间怎么进行协调呢?

需要保证写的数据不能丢失,所以,如果刷新的时间到了,先让写操作把正在写的4个数据(突发长度为4)写完,然后再去进行刷新操作;

而如果在执行读操作也遇到需要刷新的情况,也可以先让数据读完,再去执行刷新操作。

思路:SDRAM控制器包括初始化、读操作、写操作及自动刷新这些操作,给每一个操作写上一个模块独立开来,也便于我们每个模块的调试,显然这种思路是正确的;

虽然都是独立的模块,但很显然这几个模块之间又是相互关联的。如果SDRAM需要刷新了,而SDRAM却正在执行写操作,为了控制各个模块之间的工作关系,引入仲裁机制。


仲裁状态机 ↓

简易SDRAM控制器的verilog代码实现

仲裁机工作原理框图 ↓

简易SDRAM控制器的verilog代码实现

在仲裁模块中,初始化操作完成之后便进入到了“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”仲裁状态之后,如果之前全部数据仍然没有写完(指的是全部数据,并不是一个突发长度的4个数据),那么此时仍然要给仲裁模块写请求“wr_req”,然后仲裁模块经过一系列判断之后,如果符合写操作的时机,那就给写模块一个写使能信号“wr_en”,然后跳转到“WRITE”写状态并且写模块开始工作。


 

 仲裁模块中状态机定义 ↓

//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 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。


写操作模块 ↓

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来接收写请求信号,这个写请求信号,是在没有写完之前一直拉高的,在写完了全部数据之后才拉低的。

在写模块中,让SDRAM循环着写16’h0012,16’h1203,16’h562f,16’hfe12这四个数据。

另外一点,在这个写模块中,每写完4个数据,也就是突发结束后,有一个写完标志,从而使状态机跳转到仲裁状态,然后如果数据没写完,由于写请求是拉高的,所以如果此时没有刷新请求,那状态机还是会跳转到写状态继续写的。


读操作模块 ↓

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


自动刷新模块 ↓

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


。。。。。。。。。。。。。。。待续。。。2017-06-05。。。


 

转载于:https://www.cnblogs.com/JissXbon/p/6947279.html

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/160094.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号