UDP--DDR--SFP,FPGA实现之内存读取控制模块

内存读取控制模块实现介绍

由于该模块接口数量较多,为了详细说明模块实现,采用文字流程进行介绍

  • 该模块的工作时钟域为DDR时钟和SFP时钟,即读取数据为DDR时钟域下工作,输出读取到的数据在SFP时钟域下工作
  • 接收到数据完成指令后,开始进行从DDR中读数据操作
  • 根据文件的KB大小,确定突发次数(以突发100次为单位),读到的数据存储在BRAM中
  • 每次读取完100KB,进行一次光纤数据传输,即100KB大小的单次传输量
  • 每次传输完成100KB数据量后,进行检测,是否读取完一次文件
  • 当读取完成一次文件,地址回读,进行第二次文件读取
  • 之所以需要使用BRAM缓存,原因是因为,DDR的AXI接口最大突发256,每次256个数据之间不是连续的,为了保证一次连续传输100KB的数据,故需要进行缓存
  • 由于上述所述流程需要重复进行的,所以使用状态机控制逻辑跳转,会使代码的逻辑严谨,可维护性也高,另外,当接收到地址擦除信号,则是代表要重新写入文件,则无论状态机处于何种状态,都应该回转至空闲状态,如下所示为状态机跳转示意图
    UDP--DDR--SFP,FPGA实现之内存读取控制模块_第1张图片

内存读取控制模块代码编写

通过文字流程以及状态机跳转图描述,逻辑梳理可以将代码清晰的进行写出
需要注意状态机运转在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。
UDP--DDR--SFP,FPGA实现之内存读取控制模块_第2张图片
观察下面的两张图,状态机跳转变化,在检测到存储完成指令后(上图)状态机成功进行状态跳转,在传输完成300KB数据后(下图),状态机成功进行了跳转,一切符合预期条件。
UDP--DDR--SFP,FPGA实现之内存读取控制模块_第3张图片
经过上述仿真,可以看出该模块正常工作,对于该模块的编写是较为简单的,在开发中,使用状态机进行复杂逻辑的控制,十分有必要进行掌握。关于本节代码的问题,以及优化意见,欢迎大家在评论区指出,如果想要对应工程进行学习,欢迎大家私信。

你可能感兴趣的:(fpga开发,状态机,udp,ddr,sfp)