1、概括
前文完成了xilinx DDR3 MIG IP的仿真和上板测试,对MIG IP的读、写需要去通过使能信号和应答信号进行握手。这对于图像处理、AD采集等大量数据的存储不太方便,常见的使用方式是把MIG IP的用户接口封装成FIFO的接口。
如下图所示,如果要存储大量数据,只需要在开始时指定存储数据的起始地址和终止地址,并且确定每次写入数据的个数,即可向wr_fifo中写入数据。当wr_fifo中的数据个数大于一次突发写入长度时,ddr3_rw模块读取wr_fifo中的数据,以MIG用户接口写入数据的格式,将数据写到DDR3中。
对于读数据也是同样的道理,需要确定起始读数据的起止地址和每次突发读数据的长度。当ddr3_rw检测到rd_fifo中的数据少于一次突发传输的数据时,通过MIG从DDR3中读出一次突发长度的数据到rd_fifo中。用户只需要通过拉高rd_fifo的读使能信号,即可获取数据。
上述两个fifo主要是存储读写的数据,同时处理数据跨时钟域的问题。读写端口均支持复位功能,当复位有效时,会将各自fifo复位,并且把ddr3_rw中相应的地址复位。
2、ddr3_rw设计
该模块主要负责将写FIFO中的数据写入DDR3中,从DDR3中指定地址读出数据存储到读FIFO中,处理复位相关问题。
在实际使用的过程中,比如图像传输,可能导致读写的地址范围出现在DDR3的同一段地址区域,整张画面造成上半部分界面已经刷新,下半部分还没刷新的尴尬局面,这种现象在后文验证模块的时候会有体现。所以本模块增加一个乒乓模式,该模式将DDR3的读、写放在两段不同的地址,读取的数据始终是更新完成的数据,就不会出现上述现象。
本模块以状态机作为主体,下图为状态转换图(由于文字较多,所以看起来不咋清晰)。
end_bust_cnt表示一次突发读或写完成,rfifo_rd_rst表示读FIFO复位,wfifo_wr_rst表示写FIFO复位,rfifo_wdata_count表示读FIFO以写侧数据位宽和时钟查看的数据个数,app_rd_bust_len表示一次读突发传输的数据个数,rfifo_wr_rst_busy高电平表示读FIFO处于复位状态,写FIFO对应的信号含义一致。
读侧多一个信号ddr3_read_valid,思考一个问题,当读FIFO中数据不足一次突发传输时,就会从DDR3中读取数据,最开始读FIFO中没有数据,那么会立即从DDR3中读取数据,但是刚上电时DDR3中也没有有效的数据,所以此时不能从DDR3中读取数据。
本设计增加了一个ddr3_read_valid信号,当对DDR3起、止地址之间的空间全部写入数据后,将ddr3_read_valid拉高,此时才能从DDR3中读取数据填充到读FIFO中,以保证数据的正确性。
app_rd_bust_len减2是因为FIFO使用的超前模式,FIFO会在输出总线上输出一个数据,当读使能有效时,对应的数据就是有效的,读数据与读使能对齐,由于MIG IP的写需要应答,所以FIFO使用此模式会方便很多。
设计思路比较简单,下面依次解析下代码,如下所示,MIG IP输出的复位信号是高电平有效的,将其取反作为复位。MIG IP的命令使能和数据使能信号都需要应答,所以将应答信号均为高电平时才拉高对应的使能信号,确保命令和数据个数保持一致。
assign rst_n = ~ui_clk_sync_rst;//将MIG IP输出的复位信号取反作为复位信号;//状态机在写状态MIG空闲且写有效,或者状态机在读状态MIG空闲时加1,其余时间为低电平;assign app_en = ((state_c == WRITE && app_rdy && app_wdf_rdy) || (state_c == READ && app_rdy));assign app_wdf_wren = (state_c == WRITE && app_rdy && app_wdf_rdy);//状态机在写状态且写入数据有效时拉高;assign app_wdf_end = app_wdf_wren;//由于DDR3芯片时钟和用户时钟的频率4:1,突发长度为8,故两个信号相同;assign app_cmd = (state_c == READ) ? 3'd1 :3'd0;//处于读的时候命令值为1,其他时候命令值为0;assign wfifo_rd_en = app_wdf_wren;//写FIFO读使能信号,读出数据与读使能对齐。assign app_wdf_data = wfifo_rdata;//将写FIFO读出的数据传输给MIG IP的写数据;assign rfifo_wr_en = app_rd_data_valid;//将MIG IP输出数据有效指示信号作为读FIFO的写使能信号;assign rfifo_wdata = app_rd_data;//将从MIG IP读出的数据作为读FIFO的写数据;
之所以使用组合逻辑,是因为app_en和app_wdf_en等信号均需要应答,如果使用时序逻辑,数据会延迟一个时钟周期,且整体逻辑(包含计数器)会麻烦很多。
下面是状态机的跳转,与状态转换图对应。
//状态机次态到现态的跳转;always@(posedge ui_clk or negedge rst_n)beginif(!rst_n)begin//初始为空闲状态;state_c <= IDLE;endelse beginstate_c <= state_n;endend//状态机次态的跳转;always@(*)begincase(state_c)IDLE : beginif(init_calib_complete)begin//如果DDR3初始化完成,跳转到DDR3初始化完成状态;state_n = DONE;endelse beginstate_n = state_c;endendDONE : begin//如果写FIFO中数据多于一次写突发的长度且写FIFO不处于复位状态时,跳转到写状态;if((wfifo_rdata_count >= app_wr_bust_len - 2) && (~wfifo_rd_rst_busy))beginstate_n = WRITE;end//如果读FIFO中的数据少于一次读突发的长度且读FIFO不处于复位状态时,开始读出数据;else if((rfifo_wdata_count <= app_rd_bust_len - 2) && ddr3_read_valid && (~rfifo_wr_rst_busy))beginstate_n = READ;endelse beginstate_n = state_c;endendWRITE : beginif(end_bust_cnt || wfifo_wr_rst)begin//写入指定个数的数据回到完成状态或者写复位信号有效;state_n = DONE;endelse beginstate_n = state_c;endendREAD : beginif(end_bust_cnt || rfifo_rd_rst)begin//读出指定个数的数据回到完成状态或者读复位信号有效;state_n = DONE;endelse beginstate_n = state_c;endenddefault:beginstate_n = IDLE;endendcaseend
下面是一个突发计数器,当状态机在读状态或者写状态,发出对应的指令信号时加1,当计数到指定突发长度时清零。由于读、写突发的长度可能不同,所以需要一个信号来记录本次计数器的最大值。
//突发读写个数计数器bust_cnt,用于记录突发读写的数据个数;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;bust_cnt <= 0;endelse if(state_c == DONE)begin//状态机位于初始化完成状态时清零;bust_cnt <= 0;endelse if(add_bust_cnt)beginif(end_bust_cnt)bust_cnt <= 0;elsebust_cnt <= bust_cnt + 1;endend//状态机在写状态MIG空闲且写有效且写FIFO中有数据,或者状态机在读状态MIG空闲且读FIFO未满时加1,其余时间为低电平;assign add_bust_cnt = ((state_c == WRITE && app_rdy && app_wdf_rdy) || (state_c == READ && app_rdy));assign end_bust_cnt = add_bust_cnt && bust_cnt == bust_cnt_num;//读写的突发长度可能不同,所以需要根据状态机的状态判断读写状态最大值;//用于存储突发的最大长度;always@(posedge ui_clk)beginif(state_c == READ)begin//如果状态机位于读状态,则计数器的最大值对应读突发的长度;bust_cnt_num <= app_rd_bust_len - 1;endelse begin//否则为写突发的长度;bust_cnt_num <= app_wr_bust_len - 1;endend
然后就是DDR3读、写地址的控制了,由于读写操作可能穿插进行,比如一次读突发完成,然后进行写突发,所以需要两个计数器来对地址进行计数。
开始时为初始地址,当复位信号有效时回到初始地址,状态机在写状态,写到设置的最大地址时回到初始地址,否则每次写入数据后地址加8,MIG IP一次会向DDR3中8个地址写入数据(DDR3的8倍预取)。
//生成MIG IP的写地址,初始值为写入数据的最小地址。always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;app_addr_wr <= app_addr_wr_min;endelse if(wfifo_wr_rst)begin//复位时地址回到最小值;app_addr_wr <= app_addr_wr_min;end//当计数器加以条件有效且状态机处于写状态时,如果写入地址达到最大,则进行复位操作,否则加8;else if(add_bust_cnt && (state_c == WRITE))beginif(app_addr_wr >= app_addr_wr_max - 8)app_addr_wr <= app_addr_wr_min;else//否则,每次地址加8,因为DDR3每次突发会写入8次数据;app_addr_wr <= app_addr_wr + 8;endend//生成MIG IP的读地址,初始值为读出数据的最小地址。always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;app_addr_rd <= app_addr_rd_min;endelse if(rfifo_rd_rst)begin//复位时地址回到最小值;app_addr_rd <= app_addr_rd_min;endelse if(add_bust_cnt && (state_c == READ))beginif(app_addr_rd >= app_addr_rd_max - 8)beginapp_addr_rd <= app_addr_rd_min;endelseapp_addr_rd <= app_addr_rd + 8;endend
下面这段代码根据是否启用乒乓操作,综合出不同的电路,如果使用乒乓操作,那么需要使用一个读写页的控制信号,让读、写操作在不同的地址进行,始终读取完整数据的地址段。
//根据是否使用乒乓功能,综合成不同的电路;generateif(PINGPANG_EN)begin//如果使能乒乓操作,地址信号将执行下列信号;reg waddr_page ;reg raddr_page ;//相当于把bank地址进行调整,使得读写的地址空间不再同一个范围;always@(posedge ui_clk)beginif(rst_n==1'b0)beginwaddr_page <= 1'b1;raddr_page <= 1'b0;endelse if(add_bust_cnt)beginif((state_c == WRITE) && (app_addr_wr >= app_addr_wr_max - 8))waddr_page <= ~waddr_page;else if((state_c == READ) && (app_addr_rd >= app_addr_rd_max - 8))raddr_page <= ~waddr_page;endend//将数据读写地址赋给ddr地址always @(*) beginif(state_c == READ )app_addr <= {2'b0,raddr_page,app_addr_rd[25:0]};elseapp_addr <= {2'b0,waddr_page,app_addr_wr[25:0]};endendelse begin//如果没有使能乒乓操作,则综合以下代码;//将数据读写地址赋给ddr地址always @(*) beginif(state_c == READ )app_addr <= {3'b0,app_addr_rd[25:0]};elseapp_addr <= {3'b0,app_addr_wr[25:0]};endendendgenerate
当一段地址的数据被写入完后,将地址的某一位翻转,之后写入的数据就在另一段地址中,读数据始终从写入完整数据的地址段读取数据,保证输出数据流的完整,不会出现正在更新的数据。
上电初始化之后,当规定的起止地址之间全部写入一次数据后,将该信号拉高,之后从该段地址中读取数据进行输出。
//生成读使能信号,最开始的时候DDR3中并没有数据,必须向DDR3中写入数据后才能从DDR3中读取数据;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;ddr3_read_valid <= 1'b0;end//当状态机位于写状态写入一帧数据之后拉高,之后保持高电平不变。else if(app_addr_wr >= app_addr_wr_max - 8)beginddr3_read_valid <= 1'b1;endend
下面这段代码就是为复位服务的,复位分为写侧和读侧的复位,复位来自读写时钟域,与MIG IP的ui_clk为异步信号,首先需要通过两个触发器同步。
对于写需要在写入数据前对写FIFO进行复位,所以检测其上升沿,然后将复位信号持续多个时钟周期(xilinx的FIFO复位需要多个时钟周期),对写FIFO复位。
//后面考虑复位信号的处理,复位的时候应该对FIFO和写地址一起复位,复位FIFO需要复位信号持续多个时钟周期;//因此需要计数器,由于读写的复位是独立的,可能同时到达,因此计数器不能共用。//写复位到达时,如果状态机位于写数据状态,应该回到初始状态,等待清零完成后再进行跳转。//同步两个FIFO复位信号,并且检测上升沿,用于清零读写DDR的地址,由于状态机跳转会检测FIFO是否位于复位状态。always@(posedge ui_clk)beginwr_rst_r <= {wr_rst_r[0],wr_rst};//同步复位脉冲信号;rd_rst_r <= {rd_rst_r[0],rd_rst};//同步复位脉冲信号;end//生成写复位信号,由于需要对写FIFO进行复位,所以复位信号必须持续多个时钟周期;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;wfifo_wr_rst <= 1'b0;endelse if(wr_rst_r[0] && (~wr_rst_r[1]))begin//检测wfifo_wr_rst上升沿拉高复位信号;wfifo_wr_rst <= 1'b1;end//当写复位计数器全为高电平时拉低,目前是持续32个时钟周期,如果不够,修改wrst_cnt位宽即可。else if(&wr_rst_cnt)beginwfifo_wr_rst <= 1'b0;endend//写复位计数器,初始值为0,之后一直对写复位信号持续的时钟个数进行计数;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;wr_rst_cnt <= 0;endelse if(wfifo_wr_rst)beginwr_rst_cnt <= wr_rst_cnt + 1;end
而对于读复位,一般是在读取一帧或者读完起止地址之间的数据后,对读FIFO进行复位,清除FIFO中的残留数据,从DDR3起始地址读取数据进行数据,防止上一帧数据的错误影响下一帧数据,与写复位原理一致。
//写复位信号,初始值为0,当读FIFO读复位上升沿到达时有效,当计数器计数结束时清零;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;rfifo_rd_rst <= 1'b0;endelse if(rd_rst_r[0] && (~rd_rst_r[1]))beginrfifo_rd_rst <= 1'b1;endelse if(&rd_rst_cnt)beginrfifo_rd_rst <= 1'b0;endend//读复位计数器,初始值为0,当读复位有效时进行计数;always@(posedge ui_clk)beginif(rst_n==1'b0)begin//初始值为0;rd_rst_cnt <= 0;endelse if(rfifo_rd_rst)beginrd_rst_cnt <= rd_rst_cnt + 1;endend
通过上述代码可知,读写DDR3数据的起、止地址,突发长度与ui_clk也是异步信号,但是并没有做异步处理。这是因为这些信号在使用时,可能很长时间(至少一帧数据的传输时间不变)不变,甚至可能是常数,所以没必要做异步处理。
整个模块的代码设计就完成了,比较简单,后面进行仿真和上板测试。
3、读、写FIFO
需要配置读、写FIFO IP,两个FIFO的读写数据信号位宽会有点区别,所以需要使用两个IP。当然可以使用一个IP,然后通过代码实现位宽的转变,这样代码会多一点,本文不采用。
对于写FIFO,一般输入数据的位宽为16位,输出数据作为MIG IP写数据,位宽为128位,输出采用超前模式,并且需要输出FIFO中的数据个数以及复位状态指示信号,相关配置如下图所示。
选择异步FIFO,如下图所示。
如下图所示,选择First Word Fall Through模式,在该模式下FIFO的读数据与读使能信号对齐。当读写控制模块需要数据时,就能立即得到数据,数据不会被延迟。
该FIFO输入数据位宽设置为16位,因为很多图像数据或者AD数据均为16位,当然后面也可以根据实际情况修改,不影响。
深度这里测试使用的1024,之后设置输出数据的位宽,因为MIG写数据的位宽为128位,因此设置为128,之后就会得到输出数据的深度。
因为读写数据的位宽不同,所以这个FIFO能够存储的数据个数对于读写数据来说也是不一样的。但是要保证输入数据位宽输入深度等于输出数据位宽输出深度。
另外注意FIFO工作在这种模式下其实就是输出数据线上提前输出了一个数据,当读使能有效后,下个时钟输出下一个数据。所以输出深度为128的FIFO实际能够存储129个数据。
注意复位信号的设置,采用高电平复位,并且将复位状态的指示信号输出,当FIFO不处于复位状态才对FIFO进行读、写操作。
通过下图设置,将指示FIFO中有多少个数据的信号输出,由于采用异步时钟信号,所以读、写时钟域都要输出一个信号。
写FIFO的相关设置就完成了,之后生成读FIFO,读FIFO与写FIFO只有读写数据位宽的设置不同,其余均一致。
由于MIG需要把从DDR3中读出的数据写入到读FIFO中,所以读FIFO写侧的数据位宽为128位。用户通过拉高读使能信号从读FIFO中获取数据,所以读FIFO读侧的数据位宽为16位。
要特别注意上图中读FIFO的实际深度为1016,后面要考,在这里栽了一次。。。
关于读、写FIFO IP的相关配置就完成了,主要关注数据的流向,就能清除的理解各个细节了。
4、顶层模块
顶层模块需要把MIG IP与ddr3读写控制模块和读、写FIFO相应接口连起来,并且把用户接口和DDR3芯片接口引出,对应的顶层模块RTL视图如下所示。
注意读、写FIFO的复位信号宽度必须大于一个ui_clk的宽度,且FIFO不处于复位状态才能写入或者读出数据。
顶层模块参考代码如下所示:
//例化写FIFO IP;wrfifo u_wrfifo (.rst ( wfifo_wr_rst ),//input wire rst;.wr_clk ( wfifo_wclk ),//input wire wr_clk;.rd_clk ( ui_clk ),//input wire rd_clk;.din ( wfifo_wdata ),//input wire [15 : 0] din;.wr_en ( wfifo_wren ),//input wire wr_en;.rd_en ( wfifo_rd_en ),//input wire rd_en;.dout ( wfifo_rdata ),//output wire [127 : 0] dout;.full ( wfifo_full ),//output wire full;.empty ( wfifo_empty ),//output wire empty;.rd_data_count ( wfifo_rcount ),//output wire [6 : 0] rd_data_count;.wr_data_count ( wfifo_wcount ),//output wire [9 : 0] wr_data_count;.wr_rst_busy ( wfifo_wrst_busy ),//output wire wr_rst_busy;.rd_rst_busy ( wfifo_rd_rst_busy ) //output wire rd_rst_busy;);//rdfifo u_rdfifo (.rst ( rfifo_rd_rst ),//input wire rst;.wr_clk ( ui_clk ),//input wire wr_clk;.rd_clk ( rfifo_rclk ),//input wire rd_clk;.din ( rfifo_wdata ),//input wire [127 : 0] din;.wr_en ( rfifo_wr_en ),//input wire wr_en;.rd_en ( rfifo_rden ),//input wire rd_en;.dout ( rfifo_rdata ),//output wire [15 : 0] dout;.full ( rfifo_full ),//output wire full;.empty ( rfifo_empty ),//output wire empty;.rd_data_count ( rfifo_rcount ),//output wire [9 : 0] rd_data_count;.wr_data_count ( rfifo_wcount ),//output wire [6 : 0] wr_data_count;.wr_rst_busy ( rfifo_wrst_busy ),//output wire wr_rst_busy;.rd_rst_busy ( rfifo_rrst_busy ) //output wire rd_rst_busy;);//例化DDR3读写控制模块;ddr3_rw #(.PINGPANG_EN ( PINGPANG_EN ),//乒乓操作是否使能;.USE_ADDR_W ( USE_ADDR_W ),//用户需要写入数据的位宽;.USE_BUST_LEN_W ( USE_BUST_LEN_W ),//用户侧读写数据突发长度的位宽;.USE_DATA_W ( USE_DATA_W ),//用户侧读写数据的位宽;.DDR_ADDR_W ( DDR_ADDR_W ),//MIG IP读写数据地址位宽;.DDR_DATA_W ( DDR_DATA_W ) //MIG IP读写数据的位宽;)u_ddr3_rw (//MIG IP用户侧相关信号;.ui_clk ( ui_clk ),//;.ui_clk_sync_rst ( ui_clk_sync_rst ),//;.init_calib_complete ( ddr3_init_done ),//;.app_rdy ( app_rdy ),.app_wdf_rdy ( app_wdf_rdy ),.app_rd_data ( app_rd_data ),.app_rd_data_valid ( app_rd_data_valid ),.app_en ( app_en ),.app_cmd ( app_cmd ),.app_addr ( app_addr ),.app_wdf_wren ( app_wdf_wren ),.app_wdf_end ( app_wdf_end ),.app_wdf_data ( app_wdf_data ),//用户设置接口.app_addr_wr_min ( app_addr_wr_min ),.app_addr_wr_max ( app_addr_wr_max ),.app_wr_bust_len ( app_wr_bust_len ),.app_addr_rd_min ( app_addr_rd_min ),.app_addr_rd_max ( app_addr_rd_max ),.app_rd_bust_len ( app_rd_bust_len ),.wr_rst ( wr_rst ),.rd_rst ( rd_rst ),//写FIFO读侧信号.wfifo_wr_rst ( wfifo_wr_rst ),.wfifo_empty ( wfifo_empty ),.wfifo_rd_rst_busy ( wfifo_rd_rst_busy ),.wfifo_rd_en ( wfifo_rd_en ),.wfifo_rdata ( wfifo_rdata ),.wfifo_rdata_count ( wfifo_rcount ),//读FIFO写侧信号.rfifo_rd_rst ( rfifo_rd_rst ),.rfifo_full ( rfifo_full ),.rfifo_wr_rst_busy ( rfifo_wrst_busy ),.rfifo_wdata_count ( rfifo_wcount ),.rfifo_wr_en ( rfifo_wr_en ),.rfifo_wdata ( rfifo_wdata ));//例化MIG IPmig_7series_0 u_mig_7series_0 (// Memory interface ports.ddr3_addr ( ddr3_addr ),//output [14:0] ddr3_addr.ddr3_ba ( ddr3_ba ),//output [2:0] ddr3_ba.ddr3_cas_n ( ddr3_cas_n ),//output ddr3_cas_n.ddr3_ck_n ( ddr3_ck_n ),//output [0:0] ddr3_ck_n.ddr3_ck_p ( ddr3_ck_p ),//output [0:0] ddr3_ck_p.ddr3_cke ( ddr3_cke ),//output [0:0] ddr3_cke.ddr3_ras_n ( ddr3_ras_n ),//output ddr3_ras_n.ddr3_reset_n ( ddr3_reset_n ),//output ddr3_reset_n.ddr3_we_n ( ddr3_we_n ),//output ddr3_we_n.ddr3_dq ( ddr3_dq ),//inout [15:0] ddr3_dq.ddr3_dqs_n ( ddr3_dqs_n ),//inout [1:0] ddr3_dqs_n.ddr3_dqs_p ( ddr3_dqs_p ),//inout [1:0] ddr3_dqs_p.init_calib_complete ( ddr3_init_done ),//output init_calib_complete.ddr3_cs_n ( ddr3_cs_n ),//output [0:0] ddr3_cs_n.ddr3_dm ( ddr3_dm ),//output [1:0] ddr3_dm.ddr3_odt ( ddr3_odt ),//output [0:0] ddr3_odt// Application interface ports.app_addr ( app_addr ),//input [28:0] app_addr.app_cmd ( app_cmd ),//input [2:0] app_cmd.app_en ( app_en ),//input app_en.app_wdf_data ( app_wdf_data ),//input [127:0] app_wdf_data.app_wdf_end ( app_wdf_end ),//input app_wdf_end.app_wdf_wren ( app_wdf_wren ),//input app_wdf_wren.app_rd_data ( app_rd_data ),//output [127:0] app_rd_data.app_rd_data_end ( ),//output app_rd_data_end.app_rd_data_valid ( app_rd_data_valid ),//output app_rd_data_valid.app_rdy ( app_rdy ),//output app_rdy.app_wdf_rdy ( app_wdf_rdy ),//output app_wdf_rdy.app_sr_req ( 0 ),//input app_sr_req.app_ref_req ( 0 ),//input app_ref_req.app_zq_req ( 0 ),//input app_zq_req.app_sr_active ( ),//output app_sr_active.app_ref_ack ( ),//output app_ref_ack.app_zq_ack ( ),//output app_zq_ack.ui_clk ( ui_clk ),//output ui_clk.ui_clk_sync_rst ( ui_clk_sync_rst ),//output ui_clk_sync_rst.app_wdf_mask ( 16'd0 ),//input [15:0] app_wdf_mask// System Clock Ports.sys_clk_i ( sys_clk_i ),//系统输入200MHz时钟作为MIG参考和工作时钟;.sys_rst ( rst_n ) //input sys_rst);
5、仿真
由于该模块的仿真需要DDR3芯片的仿真模型,想要模拟DDR3芯片工作模式对于我们来说还是十分困难的。但是前文对MIG IP仿真时,官方提供了DDR3的仿真模型,可以从该工程中获取仿真模型,添加到本工程,然后仿真。
打开前文生成的官方测试例程的imports文件夹,如下图所示,将ddr3_model.sv和ddr3_model_parameters.vh文件复制,然后加入自己工程中。
ddr3_model.sv就是官方使用System Verilog编写的DDR3仿真模型,而ddr3_model_parameters.vh存放的是ddr3_model.sv需要的一些parameter常量。
加入工程后,如下图所示。
仿真的测试文件很简单,先向DDR3中0~4095地址空间写入数据,每次突发写入512个数据。之后在把写入的数据依次读出,对应的TestBench代码如下所示:
`timescale 1ns/1ns
module ddr3_top_tb;localparam DDR3_CYCLE = 5 ;//DDR3时钟周期;localparam WFIFO_CYCLE = 8 ;//写FIFO的写时钟周期;localparam RFIFO_CYCLE = 20 ;//读FIFO读时钟周期;localparam RST_TIME = 10 ;//系统复位持续时间,默认10个系统时钟周期;localparam STOP_TIME = 1000 ;//仿真运行时间,复位完成后运行1000个系统时钟后停止;reg sys_clk_i ;reg rst_n ;reg wr_rst ;reg rd_rst ;reg wrfifo_clk ;reg wfifo_wren ;reg [15 : 0] wfifo_wdata ;reg rdfifo_clk ;reg rfifo_rden ;wire [14 : 0] ddr3_addr ;wire [2 : 0] ddr3_ba ;wire ddr3_ras_n ;wire ddr3_cas_n ;wire ddr3_we_n ;wire ddr3_reset_n;wire ddr3_ck_p ;wire ddr3_ck_n ;wire ddr3_cke ;wire ddr3_cs_n ;wire [1 : 0] ddr3_dm ;wire ddr3_odt ;wire [9 : 0] wfifo_wcount;wire wfifo_full ;wire wfifo_wrst_busy ;wire [15 : 0] rfifo_rdata ;wire [9 : 0] rfifo_rcount;wire rfifo_empty ;wire rfifo_rrst_busy ;wire ddr3_init_done ;wire [15 : 0] ddr3_dq ;wire [1 : 0] ddr3_dqs_n ;wire [1 : 0] ddr3_dqs_p ;//生成DDR3参考时钟信号200MHz。initial beginsys_clk_i = 0;forever #(DDR3_CYCLE/2) sys_clk_i = ~sys_clk_i;end//生成写FIFO写时钟信号;initial beginwrfifo_clk = 0;forever #(WFIFO_CYCLE/2) wrfifo_clk = ~wrfifo_clk;end//生成读FIFO读时钟信号;initial beginrdfifo_clk = 0;forever #(RFIFO_CYCLE/2) rdfifo_clk = ~rdfifo_clk;endinitial beginrst_n = 1'b0;wfifo_wren = 1'b0;wfifo_wdata = 16'd0;rfifo_rden = 1'b0;wr_rst = 1'b0;rd_rst = 1'b0;#201;rst_n = 1'b1;@(posedge ddr3_init_done);wr_rst = 1'b1;repeat(4)@(posedge wrfifo_clk);wr_rst = 1'b0;repeat(50)@(posedge wrfifo_clk);repeat(4)beginwr_data(16'd100,16'd1024);#2000;endrd_rst = 1'b1;repeat(4)@(posedge rdfifo_clk);rd_rst = 1'b0;repeat(50)@(posedge rdfifo_clk);repeat(4)beginrd_data(16'd1024);#2000;end#1000;$stop;endtask wr_data;input [15:0] data_begin;input [15:0] wr_data_cnt;beginwfifo_wren <= 1'b0;wfifo_wdata <= data_begin;@(posedge wrfifo_clk);wfifo_wren <= 1'b1;repeat(wr_data_cnt)begin @(posedge wrfifo_clk);wfifo_wdata <= wfifo_wdata + 1'b1;endwfifo_wren <= 1'b0;endendtasktask rd_data;input [15:0] rd_data_cnt;beginrfifo_rden <= 1'b0;@(posedge rdfifo_clk);rfifo_rden <= 1'b1;repeat(rd_data_cnt)begin@(posedge rdfifo_clk);endrfifo_rden <= 1'b0;endendtask//例化DDR3顶层模块;ddr3_top #(.PINGPANG_EN ( 1'b0 ),.USE_ADDR_W ( 29 ),.USE_BUST_LEN_W ( 7 ),.USE_DATA_W ( 16 ),.DDR_ADDR_W ( 29 ),.DDR_DATA_W ( 128 ))u_ddr3_top (.sys_clk_i ( sys_clk_i ),.rst_n ( rst_n ),.app_addr_wr_min ( 0 ),.app_addr_wr_max ( 4096 ),.app_wr_bust_len ( 64 ),.app_addr_rd_min ( 0 ),.app_addr_rd_max ( 4096 ),.app_rd_bust_len ( 64 ),.wr_rst ( wr_rst ),.rd_rst ( rd_rst ),//写FIFO相关信号.wfifo_wclk ( wrfifo_clk ),.wfifo_wren ( wfifo_wren ),.wfifo_wdata ( wfifo_wdata ),.wfifo_wcount ( wfifo_wcount ),.wfifo_full ( wfifo_full ),.wfifo_wrst_busy ( wfifo_wrst_busy ),//读FIFO相关信号.rfifo_rclk ( rdfifo_clk ),.rfifo_rden ( rfifo_rden ),.rfifo_rdata ( rfifo_rdata ),.rfifo_rcount ( rfifo_rcount ),.rfifo_empty ( rfifo_empty ),.rfifo_rrst_busy ( rfifo_rrst_busy ),//DDR3端口.ddr3_addr ( ddr3_addr ),.ddr3_ba ( ddr3_ba ),.ddr3_ras_n ( ddr3_ras_n ),.ddr3_cas_n ( ddr3_cas_n ),.ddr3_we_n ( ddr3_we_n ),.ddr3_reset_n ( ddr3_reset_n ),.ddr3_ck_p ( ddr3_ck_p ),.ddr3_ck_n ( ddr3_ck_n ),.ddr3_cke ( ddr3_cke ),.ddr3_cs_n ( ddr3_cs_n ),.ddr3_dm ( ddr3_dm ),.ddr3_odt ( ddr3_odt ),.ddr3_init_done ( ddr3_init_done ),.ddr3_dq ( ddr3_dq ),.ddr3_dqs_n ( ddr3_dqs_n ),.ddr3_dqs_p ( ddr3_dqs_p ));//例化DDR3仿真模型;ddr3_model u_ddr3_model (.rst_n ( ddr3_reset_n ),.ck ( ddr3_ck_p ),.ck_n ( ddr3_ck_n ),.cke ( ddr3_cke ),.cs_n ( ddr3_cs_n ),.ras_n ( ddr3_ras_n ),.cas_n ( ddr3_cas_n ),.we_n ( ddr3_we_n ),.dm_tdqs ( ddr3_dm ),.ba ( ddr3_ba ),.addr ( ddr3_addr ),.dq ( ddr3_dq ),.dqs ( ddr3_dqs_p ),.dqs_n ( ddr3_dqs_n ),.tdqs_n ( ),.odt ( ddr3_odt ));endmodule
然后直接仿真即可,只不过DDR3因为需要初始化,且初始化流程比较复杂,所以需要等待较长时间。仿真结果如下所示。
如下图所示,TestBench设置一次读、写突发的长度对于MIG IP的128位数据来说是64,当写FIFO中的数据大于等于64-2时,从写FIFO中读出数据存入DDR3中,如下图所示。
由于内部设置,只有当DDR3的起止地址之间全部被写入一次数据之后,如果读FIFO中的数据少于一次突发读传输的数据(此处为64个128位数据),且读FIFO不处于复位状态,则从DDR3中读取数据存入读FIFO中,如下图所示。
之后用户就可以通过拉高读FIFO的读使能信号,从读FIFO中读出数据了,如下图所示。
上图表示读出的数据是从100开始累加的,下图是用户写入DDR3中的数据,也是从100开始累加的,所以写入和读出的开始数据正确。
第一帧写入的结束数据位1123,如下图所示。
第一帧读出的数据如下图所示,也是1123,读写数据一致,证明整个写入和读出的控制逻辑没有问题。
最后要注意一下xilinx的FIFO仿真,发现这个FIFO必须在复位之后才能进行读、写,不然无法写入数据。
以写FIFO举例,如下图所示,仿真开始后,写侧和读侧的复位指示信号一直位高电平,表示FIFO处于复位状态。此时将FIFO的复位信号拉高一段时间,之后将复位信号拉低,过一段时间后,写侧和读侧的复位指示信号才会拉低,此时才能向FIFO中写入数据,否则无法写入数据。
注意上图中复位信号拉低之后,满指示信号也会持续一段时间才会无效,但是持续的时间并没有复位指示信号长,所以依旧可以根据复位指示信号来判断复位是否结束。
这种现象只存在于仿真,实际上板时经过实测,即使不复位FIFO,也能写入数据,并且读出正确数据,可能这是Xilinx为了让我们养成复位的习惯在仿真时添加的限制吧,哈哈。
对于该模块的仿真就结束了,不在对状态机的跳转进行分析,因为逻辑比较简单。另外乒乓操作也没仿真,如果觉得有必要可以自己写TestBench进行仿真,乒乓操作就是地址的切换,其余逻辑均一致,所以不会出现问题,后续直接使用即可。
本工程不用于上板,只是进行仿真,后续使用该模块存储数据时,在上板观察使用结果。
如果需要本次工程,在公众号后台回复“MIG IP封装成FIFO”(不包括引号)即可。
如果对文章内容理解有疑惑或者对代码不理解,可以在评论区或者后台留言,看到后均会回复!
如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!您的支持是我更新的最大动力!将持续更新工程!