由于该模块接口数量较多,为了详细说明模块实现,采用文字流程进行介绍
通过文字流程以及状态机跳转图描述,逻辑梳理可以将代码清晰的进行写出
需要注意状态机运转在DDR时钟域下,故光纤数据一次传输完成的指示信号,需要进行跨时钟操作,同理BRAM中缓存了100KB数据的指示信号,也需要进行跨时钟处理。
module read_memory_ctrl(
input i_ui_clk ,
input i_ui_rst ,
input i_sfp_clk ,
input i_sfp_rst ,
input i_store_done ,
input [15:0] i_store_size ,
input i_raddr_clear ,
output o_read_back ,
output o_read_cmd ,
input [31:0] i_read_data ,
input i_read_valid ,
output [31:0] o_sfp_data ,
output o_sfp_valid
);
localparam P_IDLE = 0 ,
P_READ_GAP = 1 ,
P_READ = 2 ,
P_TRANS = 3 ,
P_READ_DONE = 4 ;
localparam P_AXI_READ_CNT = 100; /*单次突发读取长度256,连续100次突发*/
localparam P_READ_ADDR_LEN = 25600 - 1;/*BRAM读写长度*/
reg [7 :0] r_st_current ;
reg [7 :0] r_st_next ;
reg [7 :0] r_st_cnt ;
reg [31:0] ri_read_data ;
reg ri_read_valid ;
reg [31:0] ri_read_data_1d ;
reg ri_read_valid_1d;
reg ri_store_done ;
reg ro_read_cmd ;
reg ro_read_back ;
reg [15:0] ri_store_size ;
reg [14:0] r_addra ;
reg r_enb ;
reg [1 :0] r_enb_dly ;
reg [14:0] r_addrb ;
reg [9 :0] r_read_cnt ;
reg [3 :0] r_read_gap ;
wire w_store_done_pos;
wire [15:0] w_burst_cnt ;
reg [15:0] r_burst_cnt ;
reg r_read_done ;
wire w_sync_done ;
reg r_read_start ;
wire w_sync_start ;
wire w_read_valid_neg;
wire [31:0] w_doutb ;
reg [15:0] ro_read_cmd_cnt ;
assign w_store_done_pos = ~ri_store_done & i_store_done;
assign w_read_valid_neg = ri_read_valid_1d & ~ri_read_valid;
assign w_burst_cnt = ri_store_size / 100 ;
assign o_read_cmd = ro_read_cmd ;
assign o_sfp_data = w_doutb ;
assign o_sfp_valid = r_enb_dly[1] ;
assign o_read_back = ro_read_back ;
BRAM_W32D25K BRAM_W32D25K_U0 (
.clka (i_ui_clk ),
.ena (ri_read_valid ),
.wea (ri_read_valid ),
.addra (r_addra ),
.dina (ri_read_data ),
.clkb (i_sfp_clk ),
.enb (r_enb ),
.addrb (r_addrb ),
.doutb (w_doutb )
);
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 ;
ri_read_data_1d <= 32'd0;
ri_read_valid_1d <= 1'b0 ;
ri_store_size <= 16'd0;
end
else begin
ri_read_data <= i_read_data ;
ri_read_valid <= i_read_valid ;
ri_read_data_1d <= ri_read_data ;
ri_read_valid_1d <= ri_read_valid;
ri_store_size <= i_store_size ;
end
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_addra <= 15'd0;
else if(r_addra == P_READ_ADDR_LEN - 1)
r_addra <= 15'd0;
else if(ri_read_valid)
r_addra <= r_addra + 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_st_current <= P_IDLE;
else
r_st_current <= r_st_next;
end
always @(*) begin
case (r_st_current)
P_IDLE : r_st_next = w_store_done_pos ? P_READ_GAP : P_IDLE;
P_READ_GAP : r_st_next = i_raddr_clear ? P_IDLE : r_burst_cnt == w_burst_cnt ? P_READ_DONE : P_READ;
P_READ : r_st_next = i_raddr_clear ? P_IDLE : r_read_cnt == P_AXI_READ_CNT ? P_TRANS : P_READ;
P_TRANS : r_st_next = i_raddr_clear ? P_IDLE : w_sync_done ? P_READ_GAP : P_TRANS;
P_READ_DONE: r_st_next = i_raddr_clear ? P_IDLE : P_READ_GAP ;
default : r_st_next = P_IDLE ;
endcase
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_read_start <= 1'b0;
else if(r_st_current == P_READ && r_st_next == P_TRANS)
r_read_start <= 1'b1;
else
r_read_start <= 1'b0;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_read_back <= 1'b0;
else if(r_st_current == P_READ_DONE)
ro_read_back <= 1'b1;
else
ro_read_back <= 1'b0;
end
sync_s2f sync_s2f_u0(
.i_clk_slow (i_ui_clk ),
.i_signal (r_read_start ),
.i_clk_fast (i_sfp_clk ),
.o_sync (w_sync_start )
);
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ri_store_done <= 1'b0;
else
ri_store_done <= i_store_done;
end
always @(posedge i_sfp_clk,posedge i_sfp_rst) begin
if(i_sfp_rst)
r_enb <= 1'b0;
else if(r_st_current == P_IDLE)
r_enb <= 1'b0;
else if(w_sync_start)
r_enb <= 1'b1;
else if(r_addrb == P_READ_ADDR_LEN - 1)
r_enb <= 1'b0;
end
always @(posedge i_sfp_clk,posedge i_sfp_rst) begin
if(i_sfp_rst)
r_enb_dly <= 2'b00;
else
r_enb_dly <= {r_enb_dly[0],r_enb};
end
always @(posedge i_sfp_clk,posedge i_sfp_rst) begin
if(i_sfp_rst)
r_addrb <= 15'd0;
else if(r_st_current == P_IDLE)
r_addrb <= 15'd0;
else if(r_addrb == P_READ_ADDR_LEN - 1)
r_addrb <= 15'd0;
else if(r_enb)
r_addrb <= r_addrb + 1'b1;
end
always @(posedge i_sfp_clk,posedge i_sfp_rst) begin
if(i_sfp_rst)
r_read_done <= 1'b0;
else if(r_st_current == P_IDLE)
r_read_done <= 1'b0;
else if(r_addrb == P_READ_ADDR_LEN - 1)
r_read_done <= 1'b1;
else
r_read_done <= 1'b0;
end
sync_f2s #(
.SYNC_MODE_HANDSHAKE (1'b0 ),
.SYNC_MODE_STRETCH (1'b1 ),
.STRETCH_CYCLES (4'd3 )
)sync_f2s_u0(
.i_clk_fast (i_sfp_clk ),
.i_signal (r_read_done ),
.i_clk_slow (i_ui_clk ),
.o_sync (w_sync_done )
);
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ri_store_size <= 16'h0;
else
ri_store_size <= i_store_size;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_read_gap <= 4'h0;
else if(r_st_current == P_IDLE)
r_read_gap <= 4'h0;
else if(r_st_current == P_READ)
r_read_gap <= r_read_gap + 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_burst_cnt <= 16'h0;
else if(r_st_current == P_IDLE)
r_burst_cnt <= 16'h0;
else if(r_st_current == P_READ_DONE)
r_burst_cnt <= 16'h0;
else if(r_st_current == P_READ && r_st_next == P_TRANS)
r_burst_cnt <= r_burst_cnt + 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
r_read_cnt <= 10'd0;
else if(r_st_current == P_IDLE)
r_read_cnt <= 10'd0;
else if(r_st_current == P_TRANS)
r_read_cnt <= 10'd0;
else if(r_st_current == P_READ && w_read_valid_neg)
// else if(r_st_current == P_READ && r_read_gap == 4'b1111)
r_read_cnt <= r_read_cnt + 1'b1;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_read_cmd <= 1'b0;
else if((r_st_next == P_READ && r_st_current == P_READ_GAP) || (w_read_valid_neg && r_st_current == P_READ))
ro_read_cmd <= 1'b1;
else
ro_read_cmd <= 1'b0;
end
always @(posedge i_ui_clk,posedge i_ui_rst) begin
if(i_ui_rst)
ro_read_cmd_cnt <= 8'd0;
else if(r_st_current == P_TRANS)
ro_read_cmd_cnt <= 8'd0;
else if(ro_read_cmd)
ro_read_cmd_cnt <= ro_read_cmd_cnt + 1'd1;
end
endmodule
由于尚未连接DDR模块,所以对输入数据做模拟,主要查看各个状态是否正常工作,在进行DDR的AXI控制器编写时,笔者也不打算进行读取仿真,因为DDR的仿真较为麻烦,需要在官方历程中进行代码移植,所以在验证AXI模块的正常工作后,笔者会进行上板测试,进行数据读写验证。
即在之前章节的基础上,添加对该模块的例化即可,假设文件大小为300KB,那么传入SIEZ即为300,验证读取指令的发送次数,以及是否正常回转即可。
仿真tb文件代码如下
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_sfp_data ;
wire w_sfp_valid ;
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);
/*传输200KB*/
@(posedge i_udp_clk)
for(i = 0;i < 300; 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;
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 )
);
always @(posedge i_ui_clk) begin
if (w_read_cmd) begin
ddr_data(256);
end
end
/*内存读取控制器*/
read_memory_ctrl read_memory_ctrl_u0(
.i_ui_clk (i_ui_clk ),
.i_ui_rst (i_ui_rst ),
.i_sfp_clk (i_sfp_clk ),
.i_sfp_rst (i_sfp_rst ),
.i_store_done (w_store_done ),
.i_store_size (w_store_size ),
.i_raddr_clear (w_sync_clear ),
.o_read_cmd (w_read_cmd ),
.i_read_data (r_read_data ),
.i_read_valid (r_read_valid ),
.o_sfp_data (w_sfp_data ),
.o_sfp_valid (w_sfp_valid )
);
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
task ddr_data(input [15:0] byte_len);begin : ddr
integer i;
r_read_data = 32'd0;
r_read_valid = 1 'd0;
@(posedge i_ui_clk);
for(i = 0;i < 256 ;i = i + 1)
begin
r_read_data <= r_read_data + 1'b1;
r_read_valid <= 1'b1;
@(posedge i_ui_clk);
end
r_read_data = 32'd0;
r_read_valid = 1 'd0;
end
endtask
endmodule
仿真结果如下图所示
可以看出,每当SFP传输3次数据后,read_back信号成功拉高,进行地址回读,而传输3次,也符合300KB的数据量,且实现循环读取300KB。
观察下面的两张图,状态机跳转变化,在检测到存储完成指令后(上图)状态机成功进行状态跳转,在传输完成300KB数据后(下图),状态机成功进行了跳转,一切符合预期条件。
经过上述仿真,可以看出该模块正常工作,对于该模块的编写是较为简单的,在开发中,使用状态机进行复杂逻辑的控制,十分有必要进行掌握。关于本节代码的问题,以及优化意见,欢迎大家在评论区指出,如果想要对应工程进行学习,欢迎大家私信。