DDR读写控制模块实现介绍
由于该模块接口数量较多,为了详细说明模块实现,采用文字流程进行介绍
- 上级模块传输数据到来
- 捕捉数据有效上升沿
- 传输写指令,写有效,写指令成功被下一级模块缓存,进行写地址+一次读写长度(bit为单位)
- 传递写数据,每次写完1k数据,进行size+1,记录最终的KB大小(以1K为单位),提供给读取内存模块,指示可读取范围
- 接收到地址清除信号,地址回读信号,将读写输出地址置0,地址清除信号代表上位机要重写文件,将地址置0,即可表示擦除DDR数据,即将有效地址清零,标记为无效地址块;地址回读信号,即读取内存模块成功完成一次文件读取,回到0地址,进行第二次数据读取。所谓0地址,可根据所选用的DDR起始地址设定,不一定全为0,只要是数据块的起始写入位置即可,要求固定。
- 接收到读取指令,转换为op指令,进行数据读取
DDR读写控制模块代码编写
通过文字流程描述,逻辑梳理可以将代码清晰的进行写出
c
module ddr_rw_control(
input i_ui_clk ,
input i_ui_rst ,
/*ASYNC_BUF_DDR*/
input [31:0] i_send_data ,
input i_send_valid ,
input i_read_cmd ,
input i_raddr_clear ,
input i_read_back ,
output [15:0] o_store_size ,
/*op-->axi*/
output [1 :0] o_op_cmd ,
output [29:0] o_op_waddr ,
output [29:0] o_op_raddr ,
output o_op_valid ,
input i_op_ready ,
output [31:0] o_write_data ,
output o_write_valid ,
input [31:0] i_read_data ,
input i_read_valid
);
localparam P_ADDR = 4*256 ;
reg [31:0] ri_send_data ;
reg ri_send_valid ;
reg [31:0] ri_send_data_1d ;
reg ri_send_valid_1d;
reg ri_read_cmd ;
reg [1 :0] ro_op_cmd ;
reg [29:0] ro_op_waddr ;
reg [29:0] ro_op_raddr ;
reg ro_op_valid ;
reg ri_op_ready ;
reg [31:0] ri_read_data ;
reg ri_read_valid ;
reg [15:0] ro_store_size ;
(*mark_debug = "true"*)wire w_send_pos ;
(*mark_debug = "true"*)wire w_read_pos ;
wire w_send_neg ;
assign w_send_pos = ~ri_send_valid & i_send_valid ;
assign w_send_neg = ~ri_send_valid & ri_send_valid_1d;
assign w_read_pos = ~ri_read_cmd & i_read_cmd ;
assign o_op_cmd = ro_op_cmd ;
assign o_op_waddr = ro_op_waddr ;
assign o_op_raddr = ro_op_raddr ;
assign o_op_valid = ro_op_valid ;
assign o_write_data = ri_send_data ;
assign o_write_valid = ri_send_valid ;
assign o_store_size = ro_store_size ;
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ri_op_ready <= 1'b0;
else
ri_op_ready <= i_op_ready;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst) begin
ri_send_data <= 32'd0;
ri_send_valid <= 1'b0 ;
ri_send_data_1d <= 32'd0;
ri_send_valid_1d <= 1'b0 ;
end
else begin
ri_send_data <= i_send_data ;
ri_send_valid <= i_send_valid ;
ri_send_data_1d <= ri_send_data ;
ri_send_valid_1d <= ri_send_valid;
end
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_store_size <= 16'h0;
else if(i_raddr_clear)
ro_store_size <= 16'h0;
else if(w_send_neg)
ro_store_size <= ro_store_size + 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ri_read_cmd <= 1'b0;
else
ri_read_cmd <= i_read_cmd;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_op_cmd <= 2'b00;
else if(w_send_pos)
ro_op_cmd <= 2'b01;
else if(w_read_pos)
ro_op_cmd <= 2'b10;
else if(ro_op_valid && i_op_ready)
ro_op_cmd <= 2'b00;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_op_valid <= 1'b0;
else if(ro_op_valid && i_op_ready)
ro_op_valid <= 1'b0;
else if(~ro_op_valid && w_send_pos)
ro_op_valid <= 1'b1;
else if(~ro_op_valid && w_read_pos)
ro_op_valid <= 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_op_waddr <= 'd0;
else if(i_raddr_clear || i_read_back)
ro_op_waddr <= 'd0;
else if(ro_op_valid && i_op_ready && ro_op_cmd == 2'b01)
ro_op_waddr <= ro_op_waddr + P_ADDR;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_op_raddr <= 'd0;
else if(i_raddr_clear || i_read_back)
ro_op_raddr <= 'd0;
else if(ro_op_valid && i_op_ready && ro_op_cmd == 2'b10)
ro_op_raddr <= ro_op_raddr + P_ADDR;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst) begin
ri_read_data <= 32'd0;
ri_read_valid <= 1'b0 ;
end
else begin
ri_read_data <= i_read_data ;
ri_read_valid <= i_read_valid;
end
end
endmodule
接下来对代码中几个比较关键的讲解
首先是ro_op_waddr 和ro_op_raddr ,为什么其要在op总线信号握手成功后,再进行地址递增操作
这是参考了AXIS的涉及,ro_op_valid信号指示读写地址、指令等信息的有效,i_op_ready指示下一级模块成功接收到这些信息,如果地址与op_valid同步处理,会造成基础地址,不会被写入数据,直接是基础地址+2000了,所以要在每次握手成功后,进行地址的加操作
cmd为1表示写操作,cmd为2表示读操作
同样对于ro_store_size 这个信号,其表示缓存了多少KB,则在每次传入有效信号的下降沿进行+1,则可以表示写入了DDR多少KB,若是在上升沿操作,其理论上也不会出现问题。
关于代码的说明便介绍到这里,接下来进行仿真实验。
DDR读写控制模块仿真
即在之前章节的基础上,添加对该模块的例化即可,另外,考虑到内存块读取模块尚未实现,对于输入的read_cmd指令,笔者便先通过仿真给信号实现。完整的tb文件代码如下
c
module tb_module(
);
reg i_udp_clk = 1'b0;
reg i_udp_rst = 1'b0;
reg i_ui_clk = 1'b0;
reg i_ui_rst = 1'b0;
reg i_sfp_clk = 1'b0;
reg i_sfp_rst = 1'b0;
reg [7 :0] i_udp_data = 8'd0;
reg i_udp_valid = 1'd0;
reg i_rcmd = 1'b0;
wire [7 :0] w_store_udp_data ;
wire w_store_udp_valid ;
wire w_store_done ;
wire w_raddr_clear ;
wire w_sync_clear ;
wire w_read_cmd ;
reg [31:0] r_read_data = 32'd0;
reg r_read_valid = 1'd0 ;
wire [31:0] w_send_data ;
wire w_send_valid ;
wire [1 :0] w_op_cmd ;
wire [29:0] w_op_waddr ;
wire [29:0] w_op_raddr ;
wire w_op_valid ;
wire [31:0] w_write_data ;
wire w_write_valid ;
wire [15:0] w_store_size ;
integer i = 0;
integer j = 0;
always #4 i_udp_clk = ~i_udp_clk;
always #2.5 i_ui_clk = ~i_ui_clk ;
always #2 i_sfp_clk = ~i_sfp_clk;
initial begin
i_udp_rst = 1;
i_sfp_rst = 1;
i_ui_rst = 1;
#100
@(i_sfp_clk) begin
i_udp_rst <= 1'b0;
i_sfp_rst <= 1'b0;
i_ui_rst <= 1'b0;
end
#100
/*传输擦除指令*/
@(posedge i_udp_clk)
udp_cmd(64'HD5D5D5D5_FCFCFCFC);
/*传输256KB*/
@(posedge i_udp_clk)
for(i = 0;i < 256; i = i + 1) begin
@(posedge i_udp_clk)
udp_send(1024);
#500
@(posedge i_udp_clk);
end
/*传输完成指令*/
@(posedge i_udp_clk)
udp_cmd(64'HA5A5A5A5_BCBCBCBC);
#1000
@(posedge i_udp_clk)
i_rcmd <= 1'b1;
@(posedge i_udp_clk)
i_rcmd <= 1'b0;
#100
@(posedge i_udp_clk)
udp_cmd(64'HD5D5D5D5_FCFCFCFC);
end
/*指令监测,输出监测后数据*/
udp_cmd_check udp_cmd_check_u0(
.i_clk (i_udp_clk ),
.i_rst (i_udp_rst ),
.i_udp_data (i_udp_data ),
.i_udp_valid (i_udp_valid ),
.o_udp_data (w_store_udp_data ),
.o_udp_valid (w_store_udp_valid ),
.o_store_done (w_store_done ),
.o_raddr_clear (w_raddr_clear )
);
/*跨时钟域处理,1Byte-->4Bytes,udp-->ddr*/
ASYNC_BUF_DDR ASYNC_BUF_DDR_U0(
.i_udp_clk (i_udp_clk ),
.i_udp_rst (i_udp_rst ),
.i_ui_clk (i_ui_clk ),
.i_ui_rst (i_ui_rst ),
.i_udp_data (w_store_udp_data ),
.i_udp_valid (w_store_udp_valid ),
.o_send_data (w_send_data ),
.o_send_valid (w_send_valid )
);
/*读取地址清除信号跨时钟*/
sync_s2f sync_s2f_u0(
.i_clk_slow (i_udp_clk ),
.i_signal (w_raddr_clear ),
.i_clk_fast (i_ui_clk ),
.o_sync (w_sync_clear )
);
ddr_rw_control ddr_rw_control_u0(
.i_ui_clk (i_ui_clk ),
.i_ui_rst (i_ui_rst ),
.i_send_data (w_send_data ),
.i_send_valid (w_send_valid ),
.i_read_cmd (i_rcmd ),
.i_raddr_clear (w_sync_clear ),
.i_read_back (1'b0 ),
.o_store_size (w_store_size ),
.o_op_cmd (w_op_cmd ),
.o_op_waddr (w_op_waddr ),
.o_op_raddr (w_op_raddr ),
.o_op_valid (w_op_valid ),
.i_op_ready (1'b1 ),
.o_write_data (w_write_data ),
.o_write_valid (w_write_valid ),
.i_read_data (32'd0 ),
.i_read_valid (1'b0 )
);
task udp_send(input [15:0] byte_len);begin : data
integer i;
i_udp_data = 8'd0;
i_udp_valid = 1'd0;
@(posedge i_udp_clk);
for(i = 0;i < byte_len ;i = i + 1)
begin
i_udp_data <= i_udp_data + 1'b1;
i_udp_valid <= 1'b1;
@(posedge i_udp_clk);
end
i_udp_data <= 8'd0;
i_udp_valid <= 1'd0;
end
endtask
task udp_cmd(input [63:0] i_cmd);begin : cmd
integer i;
i_udp_data = 8'd0;
i_udp_valid = 1'd0;
@(posedge i_udp_clk);
for(i = 0;i < 8 ;i = i + 1)
begin
i_udp_data <= i_cmd[63:56];
i_cmd <= {i_cmd[55:0],8'h0};
i_udp_valid <= 1'b1;
@(posedge i_udp_clk);
end
i_udp_data <= 8'd0;
i_udp_valid <= 1'd0;
end
endtask
endmodule
模拟仿真的流程与前两节类似,只是在udp文件传输完成后,加入了一次读操作,观察指令输出情况,以及一次擦除指令下发,因为初始时地址为0,无法观察擦除命令是否成功执行,需要注意,此时还没有链接DDR_AXI模块,所以不会有数据读出,只是检验指令输出过程是否正确。
由下图所示,为第一帧数据传输进来,op指令进行相应转译,cmd为1,写地址为0,握手成功后,写地址+0x2000,
观察最后一帧数据传输完成,size记录为256KB,记录正确,waddr的下一次写地址为30'h0x00200000,即256X1024X8,正确,注意使用AXI控制器读写地址时,单位为bit。
观察read_cmd以及DDR擦除指令传输情况,若下图所示,成功进行了一次读取指令译码,以及当clear指令拉高时,可以看出size以及操作地址等变量成功清零。
经过上述仿真,可以看出该模块正常工作,对于该模块的编写是较为简单的,在开发中,是十分常用的模块,有必要进行掌握。关于本节代码的问题,以及优化意见,欢迎大家在评论区指出,如果想要对应工程进行学习,欢迎大家私信。