SDRAM读写控制器,这里分为三个部分,分别是SDRAM的基本操作实现,SDRAM控制器,封装成FIFO,以方便使用。
一、SDRAM的基本操作:初始化模块、自动刷新模块、写操作模块、读操作模块、SDRAM仲裁模块,顶层模块。
1、初始化模块

上图是初始化模块的时序图。有图可知,SDRAM上电并且时钟稳定后,SDRAM首先的延迟等待100us,等待期间只能赋予禁止指令或者空操作指令。等待延时完成后,需要对SDRAM所有bank进行一次预充电操作(A10)置为高电平。然后进入预充电完成等待时间tRP,等待完成后至少执行两次自动刷新指令。之后对SDRAM进行加载模式寄存器。

时序分析图

寄存器模式配置

/*Sdram 初始化
1、工作时钟定为100Mhz
2、SDRAM型号:W9825G6DH 4M x 4banks x 16bits
3、 行地址位宽:13
4、列地址位宽:9
5、数据位宽:16
6、Sdram 的自动刷新功能:每隔 64ms/2^12=15625 个时钟周期,给出刷新命令。*/
module sdram_init
(input wire sys_clk ,input wire sys_rst_n ,output reg [3:0] cmd_init ,//操作指令(包括片选信号,行选同步信号,列选同步信号,使能信号)output reg [1:0] ba_init ,//板块地址output reg [12:0] addr_init ,//地址总线output wire init_end //初始化完成标志
);//格雷码
parameter INIT_IDLE = 3'b000, //初始状态INIT_PRE = 3'b001, //预充电状态INIT_TRP = 3'b011, //预充电等待时间状态(12-20ns),具体时间看数据手册INIT_AR = 3'b010, //自动刷新状态INIT_TRF = 3'b110, //自动刷新等待时间状态(66ns)INIT_MRS = 3'b111, //寄存器配置状态INIT_TMRD = 3'b101, //寄存器配置到active状态等待时间,两个工作时钟周期INIT_END = 3'b100; //初始化完成状态//初始等待时间计数
parameter CNT_200 = 15'd20000;//等待时间周期
parameter TRP = 2'd2,//15~20ns预充电等待时间TRFC = 3'd7,//66ns自动刷新等待时间TMRD = 2'd3;//3tclk寄存器配置等待时间parameter NOP = 4'b0111, //空操作指令AUTO_RFE= 4'b0001, //自动刷新指令MREG_ST = 4'b0000, //配置寄存器指令PCHARGE = 4'b0010; //预充电指令reg [2:0] init_state;
reg [14:0] cnt_200us;
wire wait_end; //初始等待时间结束(200us),高电平有效
reg [2:0] cnt_clk;
reg cnt_clk_rst;
wire trp_end;
wire trfc_end;
wire tmrd_end;
reg [3:0] cnt_aref;//sdram初始化状态机
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)init_state <= INIT_IDLE;else case(init_state)INIT_IDLE: //系统上电后,在初始状态等待200us跳转到预充电状态if(wait_end)init_state <= INIT_PRE;elseinit_state <= init_state;INIT_PRE : //预充电状态,直接跳转到预充电等待状态init_state <= INIT_TRP;INIT_TRP : //预充电等待状态,等待结束,跳转到自动刷新状态if(trp_end)init_state <= INIT_AR;elseinit_state <= init_state;INIT_AR : //自动刷新状态,直接跳转到自动刷新等待状态init_state <= INIT_TRF;INIT_TRF : //自动刷新8次,自动跳转到模式寄存器设置状态if(trfc_end & cnt_aref == 4'd8)init_state <= INIT_MRS;else if(trfc_end)init_state <= INIT_AR;elseinit_state <= init_state;INIT_MRS : //模式寄存器设置状态,直接跳转到模式寄存器设置等待状态init_state <= INIT_TMRD; INIT_TMRD: //模式寄存器设置等待状态,等待结束,跳到初始化完成状态if(tmrd_end)init_state <= INIT_END;elseinit_state <= init_state;INIT_END : //初始化完成状态,保持此状态init_state <= INIT_END;default:init_state <= INIT_IDLE;endcase//计时200us
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_200us <= 15'd0;else if(cnt_200us == CNT_200)cnt_200us <= CNT_200;elsecnt_200us <= cnt_200us + 1'b1;//wait_end:上电后200us等待结束标志
assign wait_end = (cnt_200us == CNT_200 - 1'b1)?1'b1:1'b0;//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 3'd0;else if(cnt_clk_rst)cnt_clk <= 3'd0;elsecnt_clk <= cnt_clk + 1'b1;//cnt_clk_rst:时钟周期计数复位标志
always@(*)begincase(init_state)INIT_IDLE : cnt_clk_rst <= 1'b1;INIT_TRP : cnt_clk_rst <= (trp_end)?1'b1:1'b0;INIT_TRF : cnt_clk_rst <= (trfc_end)?1'b1:1'b0;INIT_TMRD : cnt_clk_rst <= (tmrd_end)?1'b1:1'b0;INIT_END : cnt_clk_rst <= 1'b1;default : cnt_clk_rst <= 1'b0;endcaseend//trp_end,trc_end,tmrd_end:等待结束标志
assign trp_end = (cnt_clk == TRP & init_state == INIT_TRP)?1'b1:1'b0;
assign trfc_end = (cnt_clk == TRFC & init_state == INIT_TRF)?1'b1:1'b0;
assign tmrd_end = (cnt_clk == TMRD & init_state == INIT_TMRD)?1'b1:1'b0;//对自动刷新计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_aref <= 4'd0;else if(init_state == INIT_AR)cnt_aref <= cnt_aref + 1'b1;else if(init_state == INIT_IDLE)cnt_aref <= 4'd0;//输出配置
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)begincmd_init <= NOP;ba_init <= 2'b11;addr_init<= 13'h1fff;endelse case(init_state)INIT_IDLE,INIT_TRF,INIT_TRP,INIT_TMRD,INIT_END:begincmd_init <= NOP;ba_init <= 2'b11;addr_init<= 13'h1fff; endINIT_PRE:begincmd_init <= PCHARGE;ba_init <= 2'b11;addr_init<= 13'h1fff;//预充电时,A10为高电平对所有bank进行预充电 endINIT_AR:begincmd_init <= AUTO_RFE;ba_init <= 2'b11;addr_init<= 13'h1fff; endINIT_MRS:begincmd_init <= MREG_ST;ba_init <= 2'b00; //配置寄存器模式时bank地址给00addr_init<= { //地址辅助配置模式寄存器,参数不同,配置的模式不同3'b000, //A12-A10:预留1'b0, //A9=0:读写方式,0:突发读&突发写,1:突发读&单写2'b00, //{A8,A7}=00:标准模式,默认3'b011, //{A6,A5,A4}=011:CAS潜伏期,010:2,011:3,其他:保留1'b0, //A3=0:突发传输方式,0:顺序,1:隔行3'b111 //{A2,A1,A0}=111:突发长度,000:单字节,001:2字节//010:4字节,011:8字节,111:整页,其他:保留 }; enddefault:begincmd_init <= NOP;ba_init <= 2'b11;addr_init<= 13'h1fff;endendcaseassign init_end = (init_state == INIT_END)?1'b1:1'b0; endmodule



观察仿真模型结果,初始化操作的状态跳转和写入的操作指令正确。初始化对所有bank进行预充电,经过8次自动刷新后开始配置寄存器模式,对地址线A0~A12配置,参照数据手册可知,列选通潜伏期为3,突发传输方式为顺序突发,突发长度为整页突发。
2、自动刷新模块
自动刷新时序

状态转移图

/*Sdram自动刷新
1、工作时钟定为100Mhz
2、SDRAM型号:W9825G6DH 4M x 4banks x 16bits
3、 行地址位宽:13
4、列地址位宽:9
5、数据位宽:16
6、Sdram 的自动刷新功能:每隔 64ms/2^13=7812.5 ns,给出刷新命令。*/module sdram_aref
(input wire sys_clk ,//系统时钟,频率100MHzinput wire sys_rst_n ,//复位信号,低电平有效input wire init_end ,//初始化结束信号input wire aref_en ,//自动刷新使能,由仲裁模块返回output reg [3:0] aref_cmd ,//自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}output reg [1:0] aref_ba ,//自动刷新阶段Bank地址output reg [12:0] aref_addr ,//地址数据,辅助预充电操作,A12-A0,13位地址output wire aref_end ,//自动刷新结束标志output reg aref_req //向仲裁模块发送自动刷新请求信号
);parameter CNT_MAX = 10'd750;parameter AREF_IDLE = 3'b000, //初始状态AREF_PCH = 3'b001, //预充电AREF_TRP = 3'b011, //预充电等待AUTO_REF = 3'b010, //自动刷新指令写入AREF_TRF = 3'b110, //自动刷新等待时间AREF_END = 3'b111; //结束状态parameter TRP = 2'd2,//15~20nsTRFC = 3'd7;//66nsparameter NOP = 4'b0111, //空操作指令AUTOREF= 4'b0001, //自动刷新指令PCHARGE = 4'b0010; //预充电指令reg [9:0] cnt_ref; //刷新周期,去7500ns,计时750次,给仲裁预留时间
wire aref_ack; //自动刷新响应信号
reg [3:0] cnt_clk;
reg [2:0] aref_state;
reg cnt_clk_rst; //等待时间计数复位
wire trp_end;
wire trf_end;
reg [1:0] cnt_aref; //自动刷新次数,至少两次//cnt_ref:刷新计数器
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_ref <= 10'd0;else if(cnt_ref >= CNT_MAX -1'b1)cnt_ref <= 10'd0;else if(init_end)cnt_ref <= cnt_ref + 1'b1;assign aref_ack = (aref_state == AREF_PCH)?1'b1:1'b0;//aref_req:自动刷新请求
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_req <= 1'b0;else if(cnt_ref == CNT_MAX - 1'b1)aref_req <= 1'b1;else if(aref_ack == 1'b1)aref_req <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_state <= AREF_IDLE;else case(aref_state)AREF_IDLE : //初始化结束且自动刷新使能有效,跳转到预充电状态if(init_end && aref_en)aref_state <= AREF_PCH;elsearef_state <=aref_state;AREF_PCH : //预充电状态,直接跳转到预充电等待状态aref_state <= AREF_TRP;AREF_TRP :if(trp_end) //预充电等待结束,跳转到自动刷新状态aref_state <= AUTO_REF;elsearef_state <= aref_state;AUTO_REF : //直接跳转到自动刷新等待状态aref_state <= AREF_TRF;AREF_TRF : //自动刷新等待完成且自动刷新两次,跳转到结束状态。不满足两次则跳转到自动刷新状态if(trf_end && (cnt_aref == 2'd2))aref_state <= AREF_END;else if(trf_end)aref_state <= AUTO_REF;AREF_END : //直接跳转到初始状态aref_state <= AREF_IDLE;default:aref_state <= AREF_IDLE;endcase//cnt_clk:时钟周期计数,记录初始化各状态等待时间
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 3'd0;else if(cnt_clk_rst)cnt_clk <= 3'd0;elsecnt_clk <= cnt_clk + 1'b1;always@(*)begincase(aref_state)AREF_IDLE : cnt_clk_rst <= 1'b1;AREF_TRP : cnt_clk_rst <= (trp_end)?1'b1:1'b0;AREF_TRF : cnt_clk_rst <= (trf_end)?1'b1:1'b0;AREF_END : cnt_clk_rst <= 1'b1;default : cnt_clk_rst <= 1'b0;endcaseend
//预充电等待时间结束信号
assign trp_end = (cnt_clk == TRP & aref_state == AREF_TRP)?1'b1:1'b0;
//自动刷新等待时间结束信号
assign trf_end = (cnt_clk == TRFC & aref_state == AREF_TRF)?1'b1:1'b0;//自动刷新计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_aref <= 2'd0;else if(aref_state == AUTO_REF)cnt_aref <= cnt_aref + 1'b1;else if(aref_state == AREF_IDLE)cnt_aref <= 2'd0;/* always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_cmd <= NOP;else case(aref_state)AREF_IDLE,AREF_TRP,AREF_TRF,AREF_END:aref_cmd <= NOP;AREF_PCH:aref_cmd <= PCHARGE;AUTO_REF:aref_cmd <= AUTOREF;default: aref_cmd <= NOP;endcase */always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endelsecase(aref_state)AREF_IDLE,AREF_TRP,AREF_TRF: //执行空操作指令beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endAREF_PCH: //预充电指令beginaref_cmd <= PCHARGE;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end AUTO_REF: //自动刷新指令beginaref_cmd <= AUTOREF;aref_ba <= 2'b11;aref_addr <= 13'h1fff;endAREF_END: //一次自动刷新完成beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end default:beginaref_cmd <= NOP;aref_ba <= 2'b11;aref_addr <= 13'h1fff;end endcase/* assign aref_ba = 2'b11;assign aref_addr = 13'h1fff; */assign aref_end = (aref_state == AREF_END)?1'b1:1'b0;endmodule
SDRAM规定的刷新时间为64ms,也就是在该时间内要对所有的行进行刷新,这里SDRAM对应的行地址为13位宽,对应8192行,64ms/8192=7812.5ns,那么必须大约7.8us的时间就要发出一次自动刷新命令,这是为了保证SDRAM内的数据能够上电后一直保存。以工作时钟频率为100MHz,一个时钟就是10ns,则需要780个时钟周期刷新一次,但是在设计的过程中,我们只是让他计数到750,因为当计数到该值时发出自动刷新请求会经仲裁模块,这里留一下余量。
而且在此过程中有一问题,就是自动刷新的命令会不会影响到读写操作,假设我就以7812.5ns为周期进行自动刷新,当读写操作还在进行着时,刷新请求来到了,这时难道让我们打断读写操作?这肯定是不行的,要等待读写操作完成后再发出自动刷新请求。再假设每次刷新请求操作都超过7812.5ns,那么总时长就超过64ms了,数据可能会丢失。所里这里需要留有余量,设定在7500ns刷新一次,这时余量就为三百多ns,假设你的读写突发长度为10,读写完总共花的时间为100ns,远小于余量,这时就不会出现上诉情况,但如果读写突发长度为整页突发或者大于余量/时钟周期的突发长度(321/10=32),这时也有可能会出现数据丢失的情况。

自动刷新模块的仿真情况,当刷新计数器计数到最大值时自动刷新请求拉高
3、SDRAM写操作模块
写操作时序图,页突发

当初始化完成且写使能信号拉高时,开始写激活,激活指定bank的某一行,经过写激活等待tRCD,开始写入数据,这时需要给列首地址A0~A8和已激活的bank地址,页突发可以写入页突发终止指令。
写模块状态转移图

module sdram_write
(input wire sys_clk ,input wire sys_rst_n ,input wire init_end ,//初始化完成信号,由初始化模块传入input wire wr_en ,//写使能,由仲裁模块传入input wire [23:0] wr_addr ,//包括bank地址,行地址,列地址,fifo传入input wire [15:0] wr_data ,//待写入sdram的数据,由fifo传入input wire [9:0] wr_burst_len ,//整页突发,这里10突发终止,如果没有突发终止会写满一行//输出到仲裁模块output wire wr_ack ,output wire wr_end ,//一次突发写结束output reg [3:0] write_cmd ,output reg [1:0] write_ba ,output reg [12:0] write_addr ,output reg wr_sdram_en ,//写sdram数据有效使能,与wr_ackoutput wire [15:0] wr_sdram_data );parameter WR_IDEL = 3'b000 ,//初始状态WR_ACTIVE = 3'b001 ,//激活状态WR_TRCD = 3'b011 ,//激活等待状态WR_WRITE = 3'b010 ,//写操作WR_DATA = 3'b110 ,//写数据WR_PRE = 3'b111 ,//预充电WR_TRP = 3'b101 ,//预充电等待WR_END = 3'b100 ;//一次突发写结束parameter TRCD_CLK = 10'd2 , //激活周期TRP_CLK = 10'd2 ; //预充电周期parameter NOP = 4'b0111,//空操作ACTIVE = 4'b0011,//激活WRITE = 4'b0100,//写STOP = 4'b0110,//突发停止P_CHARGE = 4'b0010;//预充电reg [2:0] write_state ;
reg [3:0] cnt_clk ;
reg cnt_clk_rst ;
wire trcd_end ;//激活等待周期结束
wire twrite_end ;//突发写结束
wire trp_end ;//预充电结束always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)write_state <= WR_IDEL;else case(write_state)WR_IDEL : //初始化完成且仲裁模块返回写使能,转激活状态if(init_end && wr_en)write_state <= WR_ACTIVE;elsewrite_state <= write_state;WR_ACTIVE : //激活状态直接跳转到激活等待write_state <= WR_TRCD;WR_TRCD : //激活等待结束,跳转到写命令状态if(trcd_end)write_state <= WR_WRITE;elsewrite_state <= write_state;WR_WRITE : //写指令状态直接跳转到写数据状态write_state <= WR_DATA;WR_DATA : //突发写结束,跳转到预充电状态if(twrite_end)write_state <= WR_PRE;elsewrite_state <= write_state;WR_PRE : //预充电状态直接跳转到预充电等待状态write_state <= WR_TRP;WR_TRP : //预充电等待结束,跳转到结束状态if(trp_end)write_state <= WR_END;elsewrite_state <= write_state;WR_END : //完成一次突发写,跳回到初始状态write_state <= WR_IDEL;default: write_state <= WR_IDEL;endcase//周期计数,对各个状态的等待时间周期计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 4'd0;else if(cnt_clk_rst)cnt_clk <= 4'd0;elsecnt_clk <= cnt_clk + 1'b1;always@(*)begincase(write_state)WR_IDEL : cnt_clk_rst <= 1'b1;WR_TRCD : cnt_clk_rst <= (trcd_end)? 1'b1:1'b0;WR_WRITE: cnt_clk_rst <= 1'b1;WR_DATA : cnt_clk_rst <= (twrite_end)? 1'b1:1'b0;WR_TRP : cnt_clk_rst <= (trp_end)? 1'b1:1'b0;WR_END : cnt_clk_rst <= 1'b1;default : cnt_clk_rst <= 1'b0;endcaseendassign trcd_end = (write_state == WR_TRCD && cnt_clk == TRCD_CLK)? 1'b1:1'b0;
assign twrite_end = (write_state == WR_DATA && cnt_clk == wr_burst_len - 1'b1)? 1'b1:1'b0;
assign trp_end = (write_state == WR_TRP && cnt_clk == TRP_CLK)? 1'b1:1'b0; //一次突发写结束
assign wr_end = (write_state == WR_END)? 1'b1:1'b0; //sdram操作指令
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginwrite_cmd <= NOP;write_ba <= 2'b11;write_addr<= 13'h1fff;endelse case(write_state)WR_IDEL,WR_TRCD,WR_TRP,WR_END :beginwrite_cmd <= NOP;write_ba <= 2'b11;write_addr<= 13'h1fff;endWR_ACTIVE :beginwrite_cmd <= ACTIVE;write_ba <= wr_addr[23:22];write_addr <= {4'b0000,wr_addr[8:0]};end WR_WRITE :beginwrite_cmd <= WRITE;write_ba <= 2'b00;write_addr<= 13'h0000;endWR_DATA :if(twrite_end)write_cmd <= STOP;else beginwrite_cmd <= NOP;write_ba <= 2'b11;write_addr<= 13'h1fff;endWR_PRE :beginwrite_cmd <= P_CHARGE;write_ba <= wr_addr[23:22];write_addr<= 13'h0400;enddefault : beginwrite_cmd <= NOP;write_ba <= 2'b11;write_addr<= 13'h1fff;endendcase//wr_ack:输出传到fifo控制模块
assign wr_ack = ( write_state == WR_WRITE)|| ((write_state == WR_DATA) && (cnt_clk <= (wr_burst_len - 2'd2)));//wr_sdram_en:写SDRAM数据有效信号
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)wr_sdram_en <= 1'b0;elsewr_sdram_en <= wr_ack;assign wr_sdram_data = (wr_sdram_en)? wr_data:16'd0;
endmodule


仿真结果写入十个数据后执行了突发终止指令
4、SDRAM读操作模块
页突发读模式时序图

发送读激活命令给SDRAM,选中要激活的bank,读指令后等待tRCD后通过地址总线传入行地址,等待列选通潜伏期,由配置寄存器模式的选定,然后开始读取数据,读完所需数据之后写入读突发终止指令,与写模块不同,当读突发终止指令执行后,还会读取潜伏期个数的数据。入潜伏期为3,则突发终止后还会读出三个数据。
读模块状态转移图


读突发长度为10,读数据状态要保持读突发长度加上列选通潜伏期的个数,10+3。读突发终止指令则要在读数据状态第八个时钟周期开始写入。
module sdram_read
(input wire sys_clk ,input wire sys_rst_n ,input wire init_end ,//初始化完成信号,由初始化模块传入input wire rd_en ,//写使能,由仲裁模块传入input wire [23:0] rd_addr ,//包括bank地址,行地址,列地址input wire [15:0] rd_data ,//读出sdram的数据,由fifo传入input wire [9:0] rd_burst_len ,//整页突发,这里10突发终止,如果没有突发终止会读满一行output wire rd_ack ,//读sdram有效信号output wire rd_end ,//一次突发读结束output reg [3:0] read_cmd ,output reg [1:0] read_ba ,output reg [12:0] read_addr ,output wire [15:0] rd_sdram_data //sdram读出数据);parameter RD_IDLE = 4'b0000, RD_ACTIVE= 4'b0001, //读激活RD_TRCD = 4'b0011, //读激活等待RD_READ = 4'b0010, //读指令写入RD_CL = 4'b0110, //列选通潜伏期RD_DATA = 4'b0111, //读数据RD_PRE = 4'b0101, //预充电RD_TRP = 4'b0100, //预充电等待RD_END = 4'b1100; //结束一次突发读parameter TRCD_CLK = 10'd2 , //激活周期CL_CLK = 10'd3 , //列选通潜伏期TRP_CLK = 10'd2 ; //预充电周期parameter NOP = 4'b0111,//空操作ACTIVE = 4'b0011,//激活READ = 4'b0101,//读指令STOP = 4'b0110,//突发停止P_CHARGE = 4'b0010;//预充电reg [15:0] rd_data_reg ;
reg [3:0] read_state ;
reg [3:0] cnt_clk ;
reg cnt_clk_rst ;
wire trcd_end ;//激活等待周期结束
wire trp_end ;//预充电结束
wire tread_end ;
wire tcl_end ;//列选通等待结束
wire stop_end ;//读突发终止//对数据打1拍,rd_data同步在sys_clk下,只是相位不同步,所以能直接打拍
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_data_reg <= 16'd0;elserd_data_reg <= rd_data;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)read_state <= RD_IDLE;else case(read_state)RD_IDLE : //初始化完成且仲裁模块返回写使能,转激活状态if(init_end && rd_en)read_state <= RD_ACTIVE;elseread_state <= read_state;RD_ACTIVE : //激活状态直接跳转到激活等待read_state <= RD_TRCD;RD_TRCD : //激活等待结束,跳转到读命令状态if(trcd_end)read_state <= RD_READ;elseread_state <= read_state;RD_READ : //读指令状态直接跳转到列选通潜伏期状态read_state <= RD_CL;RD_CL : //列选通潜伏期结束,跳转到读数据状态if(tcl_end)read_state <= RD_DATA;elseread_state <= read_state;RD_DATA : //突发读结束,跳转到预充电状态if(tread_end)read_state <= RD_PRE;elseread_state <= read_state;RD_PRE : //预充电状态直接跳转到预充电等待状态read_state <= RD_TRP;RD_TRP : //预充电等待结束,跳转到结束状态if(trp_end)read_state <= RD_END;elseread_state <= read_state;RD_END : //完成一次突发读,跳回到初始状态read_state <= RD_IDLE;default: read_state <= RD_IDLE;endcase//周期计数,对各个状态的等待时间周期计数
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_clk <= 4'd0;else if(cnt_clk_rst)cnt_clk <= 4'd0;elsecnt_clk <= cnt_clk + 1'b1;always@(*)begincase(read_state)RD_IDLE : cnt_clk_rst <= 1'b1;RD_TRCD : cnt_clk_rst <= (trcd_end)? 1'b1:1'b0;RD_READ: cnt_clk_rst <= 1'b1;RD_CL : cnt_clk_rst <= (tcl_end)? 1'b1:1'b0;RD_DATA : cnt_clk_rst <= (tread_end)? 1'b1:1'b0;RD_TRP : cnt_clk_rst <= (trp_end)? 1'b1:1'b0;RD_END : cnt_clk_rst <= 1'b1;default : cnt_clk_rst <= 1'b0;endcaseendassign trcd_end = (read_state == RD_TRCD && cnt_clk == TRCD_CLK)? 1'b1:1'b0;
assign tread_end = (read_state == RD_DATA && cnt_clk == rd_burst_len + 10'd2)? 1'b1:1'b0;
assign trp_end = (read_state == RD_TRP && cnt_clk == TRP_CLK)? 1'b1:1'b0;
assign tcl_end = (read_state == RD_CL && cnt_clk == CL_CLK - 1'b1)? 1'b1:1'b0;
assign stop_end = (read_state == RD_DATA && cnt_clk == rd_burst_len - 10'd1 - CL_CLK)? 1'b1:1'b0;//一次突发写结束
assign rd_end = (read_state == RD_END)? 1'b1:1'b0; //sdram操作指令
always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginread_cmd <= NOP;read_ba <= 2'b11;read_addr<= 13'h1fff;endelse case(read_state)RD_IDLE,RD_TRCD,RD_TRP,RD_END,RD_CL :beginread_cmd <= NOP;read_ba <= 2'b11;read_addr<= 13'h1fff;endRD_ACTIVE :beginread_cmd <= ACTIVE;read_ba <= rd_addr[23:22];read_addr <= {4'b0000,rd_addr[8:0]};end RD_READ :beginread_cmd <= READ;read_ba <= 2'b00;read_addr<= 13'h0000;endRD_DATA :if(stop_end)read_cmd <= STOP;else beginread_cmd <= NOP;read_ba <= 2'b11;read_addr<= 13'h1fff;endRD_PRE :beginread_cmd <= P_CHARGE;read_ba <=rd_addr[23:22];read_addr<= 13'h0400;enddefault : beginread_cmd <= NOP;read_ba <= 2'b11;read_addr<= 13'h1fff;endendcase//rd_ack:读SDRAM有效信号
//输出会少一位数据,
assign rd_ack = ( read_state == RD_DATA)&& ((cnt_clk >= 10'd1) && (cnt_clk < (rd_burst_len + 1'd1)));//这个不会读漏
/* assign rd_ack = ( read_state == RD_DATA)&& ((cnt_clk >= 10'd0) && (cnt_clk < (rd_burst_len))); */assign rd_sdram_data = (rd_ack)? rd_data_reg:16'd0;
endmodule


5、SDRAM仲裁模块

前面四个模块都与用户接口和SDRAM建立起了双向数据通信,如果多个指令一起发送请求,如果没有优先级,就有可能造成冲突,从而导致SDRAM工作出错,因为就需要引入仲裁模块,对前面四个模块进行优先级的划分。

当初始化完成时,跳转到仲裁状态,当读写与刷新,其中有一个为有效,其他两个无效,则执行操作,当执行操作之后,回到仲裁状态,若两路或多路请求信号同时有效,优先执行优先级较高的操作,默认优先级为自动刷新操作>数据写操作>数据读操作。
module sdram_arbit
(input wire sys_clk ,input wire sys_rst_n ,input wire init_end ,input wire [3:0] init_cmd ,input wire [1:0] init_ba ,input wire [12:0] init_addr ,input wire aref_req ,input wire aref_end ,input wire [3:0] aref_cmd ,input wire [1:0] aref_ba ,input wire [12:0] aref_addr , input wire wr_req ,input wire wr_end ,input wire [3:0] wr_cmd ,input wire [1:0] wr_ba ,input wire [12:0] wr_addr ,input wire wr_sdram_en ,input wire [15:0] wr_data , input wire rd_req ,input wire rd_end ,input wire [3:0] rd_cmd ,input wire [1:0] rd_ba ,input wire [12:0] rd_addr ,output reg aref_en ,output reg wr_en ,output reg rd_en ,output wire sdram_cke ,output wire sdram_cs_n ,output wire sdram_ras_n ,output wire sdram_cas_n ,output wire sdram_we_n ,output reg [1:0] sdram_ba ,output reg [12:0] sdram_addr ,inout wire [15:0] sdram_dq );reg [4:0] state;
reg [3:0] sdram_cmd;parameter IDLE = 5'b00001,ARBIT = 5'b00010,WRITE = 5'b00100,READ = 5'b01000,A_REF = 5'b10000;parameter NOP = 4'b0111; //空操作指令always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)state <= IDLE;else case(state)IDLE :if(init_end)state <= ARBIT;elsestate <= IDLE;ARBIT :if(aref_req)state <= A_REF;else if(wr_req)state <= WRITE;else if(rd_req)state <= READ;elsestate <= ARBIT;WRITE :if(wr_end)state <= ARBIT;elsestate <= WRITE;READ :if(rd_end)state <= ARBIT;elsestate <= READ;A_REF :if(aref_end)state <= ARBIT;elsestate <= A_REF;default:state <= IDLE;endcasealways@(*)case(state)IDLE :beginsdram_cmd <= init_cmd;sdram_ba <= init_ba;sdram_addr <= init_addr;end
// ARBIT :
// begin
// sdram_cmd <= NOP;
// sdram_ba <= 2'b11;
// sdram_addr <= 13'h1fff;
// end WRITE :beginsdram_cmd <= wr_cmd;sdram_ba <= wr_ba;sdram_addr <= wr_addr; end READ :beginsdram_cmd <= rd_cmd;sdram_ba <= rd_ba;sdram_addr <= rd_addr; end A_REF :beginsdram_cmd <= aref_cmd;sdram_ba <= aref_ba;sdram_addr <= aref_addr; enddefault:beginsdram_cmd <= NOP;sdram_ba <= 2'b11;sdram_addr <= 13'h1fff;endendcasealways@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)aref_en <= 1'b0;else if((aref_req == 1'b1)&& (state == ARBIT))aref_en <= 1'b1;else if(aref_end == 1'b1)aref_en <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)wr_en <= 1'b0;else if((aref_req == 1'b0) && (wr_req == 1'b1) && (state == ARBIT))wr_en <= 1'b1;else if(wr_end == 1'b1)wr_en <= 1'b0;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_en <= 1'b0;else if((aref_req == 1'b0) && (rd_req == 1'b1) && (state == ARBIT))rd_en <= 1'b1;else if(rd_end == 1'b1)rd_en <= 1'b0;assign {sdram_cs_n,sdram_ras_n,sdram_cas_n,sdram_we_n} = sdram_cmd;assign sdram_cke = 1'b1;assign sdram_dq = (wr_sdram_en)? wr_data: 16'bz;endmodule
把五个合成一个顶层模块
module sdram_ctrl
(//时钟、复位,初始化端口input wire sys_clk ,input wire sys_rst_n ,output wire init_end ,//sdram writeinput wire [23:0] wr_sdram_addr,input wire [15:0] wr_sd_data,input wire [9:0] wr_burst_len,input wire wr_sdram_req,//写SDRAM请求output wire wr_sdram_ack,//写SDRAM有效信号//sdram readinput wire [23:0] rd_sdram_addr,input wire [9:0] rd_burst_len,input wire rd_sdram_req,output wire rd_sdram_ack,output wire [15:0] rd_sdram_out,//sdram引脚接口output wire sdram_cke ,output wire sdram_cs_n ,output wire sdram_cas_n ,output wire sdram_ras_n ,output wire sdram_we_n ,output wire [1:0] sdram_ba ,output wire [12:0] sdram_addr ,inout wire [15:0] sdram_dq
);wire [3:0] init_cmd ;
wire [1:0] init_ba ;
wire [12:0] init_addr;wire aref_req ;
wire aref_end ;
wire [3:0] aref_cmd ;
wire [1:0] aref_ba ;
wire [12:0] aref_addr;
wire wr_end ;
wire [3:0] wr_cmd ;
wire [1:0] wr_ba ;
wire [12:0] wr_addr;
wire wr_sdram_en;
wire [15:0] wr_sdram_data;
wire rd_end ;
wire [3:0] rd_cmd ;
wire [1:0] rd_ba ;
wire [12:0] rd_addr;
wire aref_en;
wire wr_en;
wire rd_en; sdram_init sdram_init_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.cmd_init (init_cmd),//操作指令(包括片选信号,行选同步信号,列选同步信号,使能信号).ba_init (init_ba),//板块地址.addr_init (init_addr),//地址总线.init_end (init_end) //初始化完成标志
);sdram_aref sdram_aref_inst
(.sys_clk (sys_clk),//系统时钟,频率100MHz.sys_rst_n (sys_rst_n),//复位信号,低电平有效.init_end (init_end),//初始化结束信号.aref_en (aref_en),//自动刷新使能,由仲裁模块返回.aref_cmd (aref_cmd),//自动刷新阶段写入sdram的指令,{cs_n,ras_n,cas_n,we_n}.aref_ba (aref_ba),//自动刷新阶段Bank地址.aref_addr (aref_addr),//地址数据,辅助预充电操作,A12-A0,13位地址.aref_end (aref_end),//自动刷新结束标志.aref_req (aref_req) //向仲裁模块发送自动刷新请求信号
);sdram_write sdram_write_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.init_end (init_end),//初始化完成信号,由初始化模块传入.wr_en (wr_en),//写使能,由仲裁模块传入.wr_addr (wr_sdram_addr),//包括bank地址,行地址,列地址.wr_data (wr_sd_data),//待写入sdram的数据,由fifo传入.wr_burst_len (wr_burst_len),//整页突发,这里10突发终止,如果没有突发终止会写满一行.wr_ack (wr_sdram_ack),//写sdram有效信号.wr_end (wr_end),//一次突发写结束.write_cmd (wr_cmd),.write_ba (wr_ba),.write_addr (wr_addr),.wr_sdram_en (wr_sdram_en),.wr_sdram_data (wr_sdram_data));sdram_read sdram_read_inst
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.init_end (init_end),//初始化完成信号,由初始化模块传入.rd_en (rd_en),//写使能,由仲裁模块传入.rd_addr (rd_sdram_addr),//包括bank地址,行地址,列地址.rd_data (sdram_dq),//读出sdram的数据,由sdram读出.rd_burst_len (rd_burst_len),//整页突发,这里10突发终止,如果没有突发终止会读满一行.rd_ack (rd_sdram_ack),//读sdram有效信号.rd_end (rd_end),//一次突发读结束.read_cmd (rd_cmd),.read_ba (rd_ba),.read_addr (rd_addr),.rd_sdram_data (rd_sdram_out) //sdram读出数据);sdram_arbit sdram_arbit
(.sys_clk (sys_clk),.sys_rst_n (sys_rst_n),.init_end (init_end ),.init_cmd (init_cmd ),.init_ba (init_ba ),.init_addr (init_addr),.aref_req (aref_req ),.aref_end (aref_end ),.aref_cmd (aref_cmd ),.aref_ba (aref_ba ),.aref_addr (aref_addr),.wr_req (wr_sdram_req),.wr_end (wr_end ),.wr_cmd (wr_cmd ),.wr_ba (wr_ba ),.wr_addr (wr_addr ),.wr_sdram_en (wr_sdram_en),.wr_data (wr_sdram_data),.rd_req (rd_sdram_req),.rd_end (rd_end),.rd_cmd (rd_cmd),.rd_ba (rd_ba),.rd_addr (rd_addr),.aref_en (aref_en),.wr_en (wr_en),.rd_en (rd_en),.sdram_cke (sdram_cke),.sdram_cs_n (sdram_cs_n),.sdram_ras_n (sdram_ras_n),.sdram_cas_n (sdram_cas_n),.sdram_we_n (sdram_we_n),.sdram_ba (sdram_ba),.sdram_addr (sdram_addr),.sdram_dq (sdram_dq));endmodule


二、添加FIFO控制模块
异步FIFO模块能够解决多bit数据跨时钟域处理的问题,在SDRAM操作冲突时可以将数据寄存在FIFO以防止数据被覆盖或者丢失,为数据读写模块提供SDRAM读写地址,产生读写请求。引入FIFO,相当于把对SDRAM的操作封装成了一个FIFO。
module sdram_fifo_ctrl
(input wire sys_clk ,//input wire sys_rst_n ,//input wire wr_rst ,input wire [9:0] wr_burst_len ,//input wire wr_fifo_clk ,//写fifo的时钟input wire wr_fifo_req ,//写fifo的写请求input wire [15:0] wr_fifo_data ,//写入写FIFO的数据,由外部传入,如ad采集input wire [23:0] sdram_wr_b_addr ,//input wire [23:0] sdram_wr_e_addr ,//input wire rd_rst ,input wire rd_fifo_clk ,//读fifo时钟,50Minput wire rd_fifo_req ,//读fifo读请求input wire [23:0] sdram_rd_b_addr ,//sdram读数据首地址input wire [9:0] rd_burst_len ,//input wire [23:0] sdram_rd_e_addr ,//input wire read_valid ,//input wire init_end ,//初始化完成信号,由sdram_ctrl传入input wire sdram_wr_ack ,//sdram写数据有效信号,提前一个时钟周期,写fifo的读请求input wire sdram_rd_ack ,//sdram读数据有效信号,读FIFO的写请求input wire [15:0] sdram_data_out ,//由sdram读出到读fifo中缓存output reg sdram_wr_req ,//sdram写数据请求output reg [23:0] sdram_wr_addr ,//sdram写地址,首地址开始写,写突发output wire [15:0] sdram_data_in ,//写fifo的输出,待写入sdram的数据output reg sdram_rd_req ,//sdram读数据请求output reg [23:0] sdram_rd_addr ,//sdram读地址,首地址开始读output wire [15:0] rd_fifo_rd_data ,//由读fifo读出缓存的数据output wire [9:0] rd_fifo_num //对sdram读出到fifo的数据计数
);reg wr_ack_reg;
wire wr_ack_fall;
reg rd_ack_reg;
wire rd_ack_fall;
wire [9:0] wr_fifo_num;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)wr_ack_reg <= 1'b0;elsewr_ack_reg <= sdram_wr_ack;always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)rd_ack_reg <= 1'b0;elserd_ack_reg <= sdram_rd_ack;assign wr_ack_fall = (wr_ack_reg && ~sdram_wr_ack);
assign rd_ack_fall = (rd_ack_reg && ~sdram_rd_ack);always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)sdram_wr_addr <= 24'd0;else if(wr_rst)sdram_wr_addr <= sdram_wr_b_addr;else if(wr_ack_fall)beginif(sdram_wr_addr < (sdram_wr_e_addr - wr_burst_len))//不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加sdram_wr_addr <= sdram_wr_addr + wr_burst_len;else //不使用乒乓操作,到达末地址,回到写起始地址sdram_wr_addr <= sdram_wr_b_addr; endalways@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)sdram_rd_addr <= 24'd0;else if(rd_rst)sdram_rd_addr <= sdram_rd_b_addr;else if(rd_ack_fall)beginif(sdram_rd_addr < (sdram_rd_e_addr - rd_burst_len))//不使用乒乓操作,一次突发写结束,更改写地址,未达到末地址,写地址累加sdram_rd_addr <= sdram_rd_addr + rd_burst_len;else //不使用乒乓操作,到达末地址,回到写起始地址sdram_rd_addr <= sdram_rd_b_addr; end always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)beginsdram_wr_req <= 1'b0;sdram_rd_req <= 1'b0;endelse if(init_end)beginif(wr_fifo_num >= wr_burst_len)beginsdram_wr_req <= 1'b1;sdram_rd_req <= 1'b0;endelse if(rd_fifo_num < rd_burst_len && read_valid)beginsdram_wr_req <= 1'b0;sdram_rd_req <= 1'b1;endelsebeginsdram_wr_req <= 1'b0;sdram_rd_req <= 1'b0;end endelsebeginsdram_wr_req <= 1'b0;sdram_rd_req <= 1'b0;end sdram_fifo sdram_wr_fifo_inst (.aclr ( ~sys_rst_n || wr_rst ),.data ( wr_fifo_data ),.rdclk ( sys_clk ),.rdreq ( sdram_wr_ack ),.wrclk ( wr_fifo_clk ),.wrreq ( wr_fifo_req ),.q ( sdram_data_in ),.rdusedw ( wr_fifo_num ),.wrusedw ( ));sdram_fifo sdram_rd_fifo_inst (.aclr ( ~sys_rst_n || rd_rst ),.data ( sdram_data_out ),.rdclk ( rd_fifo_clk ),.rdreq ( rd_fifo_req ),.wrclk ( sys_clk ),.wrreq ( sdram_rd_ack ),.q ( rd_fifo_rd_data ),.rdusedw ( ),.wrusedw ( rd_fifo_num ));endmodule
仿真文本
`timescale 1ns/1nsmodule tb_uart_sdram();//wire define
wire tx ;
wire sdram_clk ;
wire sdram_cke ;
wire sdram_cs_n ;
wire sdram_cas_n ;
wire sdram_ras_n ;
wire sdram_we_n ;
wire [1:0] sdram_ba ;
wire [12:0] sdram_addr ;
wire [1:0] sdram_dqm ;
wire [15:0] sdram_dq ;//reg define
reg sys_clk ;
reg sys_rst_n ;
reg rx ;
reg [7:0] data_mem [30:0] ; //data_mem是一个存储器,相当于一个ram//读取sim文件夹下面的data.txt文件,并把读出的数据定义为data_mem
initial$readmemh("E:/intelFPGA/data/uart_sdram/sim/test_data.txt",data_mem);//时钟、复位信号
initialbeginsys_clk = 1'b1 ;sys_rst_n <= 1'b0 ;#200sys_rst_n <= 1'b1 ;endalways #10 sys_clk = ~sys_clk;initialbeginrx <= 1'b1;#200rx_byte();endtask rx_byte();integer j;for(j=0;j<30;j=j+1)rx_bit(data_mem[j]);
endtasktask rx_bit(input[7:0] data); //data是data_mem[j]的值。integer i;for(i=0;i<10;i=i+1)begincase(i)0: rx <= 1'b0 ; //起始位1: rx <= data[0];2: rx <= data[1];3: rx <= data[2];4: rx <= data[3];5: rx <= data[4];6: rx <= data[5];7: rx <= data[6];8: rx <= data[7]; //上面8个发送的是数据位9: rx <= 1'b1 ; //停止位endcase#(52*20); //一个波特时间=ssys_clk周期*波特计数器end
endtask//重定义defparam,用于修改参数,缩短仿真时间
//defparam uart_sdram_inst.uart_rx_inst.BAUD_CNT_END = 52;
//defparam uart_sdram_inst.uart_rx_inst.BAUD_CNT_END_HALF = 26;
//defparam uart_sdram_inst.uart_tx_inst.BAUD_CNT_END = 52;
defparam uart_sdram_inst.CLK_FREQ = 50_000_0;
defparam uart_sdram_inst.fifo_read_inst.BAUD_CNT = 52;
defparam uart_sdram_inst.fifo_read_inst.BAUD_HALF = 26;
defparam sdram_model_plus_inst.addr_bits = 13;
defparam sdram_model_plus_inst.data_bits = 16;
defparam sdram_model_plus_inst.col_bits = 9;
defparam sdram_model_plus_inst.mem_sizes = 2*1024*1024;//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************////-------------uart_sdram_inst-------------
uart_sdram uart_sdram_inst(.sys_clk (sys_clk ),.sys_rst_n (sys_rst_n ),.rx (rx ),.tx (tx ),.sdram_clk (sdram_clk ),.sdram_cke (sdram_cke ),.sdram_cs_n (sdram_cs_n ),.sdram_cas_n (sdram_cas_n ),.sdram_ras_n (sdram_ras_n ),.sdram_we_n (sdram_we_n ),.sdram_ba (sdram_ba ),.sdram_addr (sdram_addr ),.sdram_dqm (sdram_dqm ),.sdram_dq (sdram_dq ));//-------------sdram_model_plus_inst-------------
sdram_model_plus sdram_model_plus_inst(.Dq (sdram_dq ),.Addr (sdram_addr ),.Ba (sdram_ba ),.Clk (sdram_clk ),.Cke (sdram_cke ),.Cs_n (sdram_cs_n ),.Ras_n (sdram_ras_n ),.Cas_n (sdram_cas_n ),.We_n (sdram_we_n ),.Dqm (sdram_dqm ),.Debug (1'b1 )
);endmodule



把采集数据写入写FIFO中,等待SDRAM控制模块SDRAM写数据有效信号拉高时,开始从写FIFO读出传到SDRAM。当SDRAM读数据有效信号拉高时,开始从SDRAM读出数据写入到读FIFO中,上位机再从读FIFO中读出数据。以上为学习内容,具体可参考野火教程。















