module video_dnn_fmem
        #(
            parameter   TUSER_WIDTH       = 1,
            parameter   TDATA_WIDTH       = 24,
            parameter   STORE_TDATA_WIDTH = 8,
            
            parameter   DIV_X             = 2,
            parameter   DIV_Y             = 2,
            
            parameter   X_WIDTH           = 10,
            parameter   Y_WIDTH           = 9,
            parameter   MAX_X_NUM         = (1 << X_WIDTH),
            parameter   MAX_Y_NUM         = (1 << Y_WIDTH),
            parameter   RAM_TYPE          = "block"
        )
        (
            input   wire                            aresetn,
            input   wire                            aclk,
            
            input   wire    [TUSER_WIDTH-1:0]       s_axi4s_tuser,
            input   wire                            s_axi4s_tlast,
            input   wire    [TDATA_WIDTH-1:0]       s_axi4s_tdata,
            input   wire                            s_axi4s_tvalid,
            output  wire                            s_axi4s_tready,
            
            output  wire    [TUSER_WIDTH-1:0]       m_axi4s_tuser,
            output  wire                            m_axi4s_tlast,
            output  wire    [TDATA_WIDTH-1:0]       m_axi4s_tdata,
            output  wire    [STORE_TDATA_WIDTH-1:0] m_axi4s_tdata_store,
            output  wire                            m_axi4s_tvalid,
            input   wire                            m_axi4s_tready,
            
            
            input   wire                            s_axi4s_store_aresetn,
            input   wire                            s_axi4s_store_aclk,
            input   wire    [TUSER_WIDTH-1:0]       s_axi4s_store_tuser,
            input   wire                            s_axi4s_store_tlast,
            input   wire    [STORE_TDATA_WIDTH-1:0] s_axi4s_store_tdata,
            input   wire                            s_axi4s_store_tvalid
        );
    
    localparam  STORE_X_WIDTH = X_WIDTH - DIV_X;
    localparam  STORE_Y_WIDTH = Y_WIDTH - DIV_Y;
    localparam  STORE_X_NUM   = (MAX_X_NUM >> DIV_X);
    localparam  STORE_Y_NUM   = (MAX_Y_NUM >> DIV_Y);
    
    
    // memory
    wire                            wr_en;
    wire    [STORE_X_WIDTH-1:0]     wr_addr_x;
    wire    [STORE_Y_WIDTH-1:0]     wr_addr_y;
    wire    [STORE_TDATA_WIDTH-1:0] wr_din;
    
    wire                            rd_en;
    wire                            rd_regcke;
    wire    [STORE_X_WIDTH-1:0]     rd_addr_x;
    wire    [STORE_Y_WIDTH-1:0]     rd_addr_y;
    wire    [STORE_TDATA_WIDTH-1:0] rd_dout;
    
    jelly_ram_simple_dualport
            #(
                .ADDR_WIDTH     (STORE_Y_WIDTH + STORE_X_WIDTH),
                .DATA_WIDTH     (STORE_TDATA_WIDTH),
                .MEM_SIZE       (STORE_Y_NUM * (1 << STORE_X_WIDTH)),
                .RAM_TYPE       (RAM_TYPE),
                .DOUT_REGS      (1)
            )
        i_ram_simple_dualport
            (
                .wr_clk         (s_axi4s_store_aclk),
                .wr_en          (wr_en),
                .wr_addr        ({wr_addr_y, wr_addr_x}),
                .wr_din         (wr_din),
                
                .rd_clk         (aclk),
                .rd_en          (rd_en),
                .rd_regcke      (rd_regcke),
                .rd_addr        ({rd_addr_y, rd_addr_x}),
                .rd_dout        (rd_dout)
            );
    
    
    // write
    reg                             reg_wr_en;
    reg                             reg_wr_last;
    reg     [STORE_X_WIDTH-1:0]     reg_wr_addr_x;
    reg     [STORE_Y_WIDTH-1:0]     reg_wr_addr_y;
    reg     [STORE_TDATA_WIDTH-1:0] reg_wr_din;
    always @(posedge s_axi4s_store_aclk) begin
        if ( ~s_axi4s_store_aresetn ) begin
            reg_wr_en     <= 1'b0;
            reg_wr_addr_x <= {STORE_X_WIDTH{1'bx}};
            reg_wr_addr_y <= {STORE_Y_WIDTH{1'bx}};
            reg_wr_din    <= {STORE_TDATA_WIDTH{1'bx}};
        end
        else begin
            reg_wr_en     <= s_axi4s_store_tvalid;
            reg_wr_last   <= s_axi4s_store_tlast;
            reg_wr_din    <= s_axi4s_store_tdata;
            if ( s_axi4s_store_tvalid && s_axi4s_store_tuser[0] ) begin
                reg_wr_addr_x <= {STORE_X_WIDTH{1'b0}};
                reg_wr_addr_y <= {STORE_Y_WIDTH{1'b0}};
            end
            else if ( reg_wr_en ) begin
                reg_wr_addr_x <= reg_wr_addr_x + 1'b1;
                if ( reg_wr_last ) begin
                    reg_wr_addr_x <= {STORE_X_WIDTH{1'b0}};
                    reg_wr_addr_y <= reg_wr_addr_y + 1'b1;
                end
            end
        end
    end
    
    assign wr_en     = reg_wr_en;
    assign wr_addr_x = reg_wr_addr_x;
    assign wr_addr_y = reg_wr_addr_y;
    assign wr_din    = reg_wr_din;
    
    
    
    // read
    wire                            cke;
    
    reg     [X_WIDTH-1:0]           st0_rd_addr_x;
    reg     [Y_WIDTH-1:0]           st0_rd_addr_y;
    reg     [TUSER_WIDTH-1:0]       st0_tuser;
    reg                             st0_tlast;
    reg     [TDATA_WIDTH-1:0]       st0_tdata;
    reg                             st0_tvalid;
    
    reg     [TUSER_WIDTH-1:0]       st1_tuser;
    reg                             st1_tlast;
    reg     [TDATA_WIDTH-1:0]       st1_tdata;
    reg                             st1_tvalid;
    
    reg     [TUSER_WIDTH-1:0]       st2_tuser;
    reg                             st2_tlast;
    reg     [TDATA_WIDTH-1:0]       st2_tdata;
    reg                             st2_tvalid;
    
    always @(posedge aclk) begin
        if ( ~aresetn ) begin
            st0_rd_addr_x <= {X_WIDTH{1'bx}};
            st0_rd_addr_y <= {Y_WIDTH{1'bx}};
            st0_tuser     <= {TUSER_WIDTH{1'bx}};
            st0_tlast     <= 1'bx;
            st0_tdata     <= {TDATA_WIDTH{1'bx}};
            st0_tvalid    <= 1'b0;
            
            st1_tuser     <= {TUSER_WIDTH{1'bx}};
            st1_tlast     <= 1'bx;
            st1_tdata     <= {TDATA_WIDTH{1'bx}};
            st1_tvalid    <= 1'b0;
            
            st2_tuser     <= {TUSER_WIDTH{1'bx}};
            st2_tlast     <= 1'bx;
            st2_tdata     <= {TDATA_WIDTH{1'bx}};
            st2_tvalid    <= 1'b0;
        end
        else if ( cke ) begin
            // stage 0
            if ( s_axi4s_tvalid && s_axi4s_tuser[0] ) begin
                st0_rd_addr_x <= {STORE_X_WIDTH{1'b0}};
                st0_rd_addr_y <= {STORE_X_WIDTH{1'b0}};
            end
            else if ( st0_tvalid ) begin
                st0_rd_addr_x <= st0_rd_addr_x + 1'b1;
                if ( st0_tlast ) begin
                    st0_rd_addr_x <= {STORE_X_WIDTH{1'b0}};
                    st0_rd_addr_y <= st0_rd_addr_y + 1'b1;
                end
            end
            
            st0_tuser  <= s_axi4s_tuser;
            st0_tlast  <= s_axi4s_tlast;
            st0_tdata  <= s_axi4s_tdata;
            st0_tvalid <= s_axi4s_tvalid;
            
            
            // stage 1
            st1_tuser  <= st0_tuser;
            st1_tlast  <= st0_tlast;
            st1_tdata  <= st0_tdata;
            st1_tvalid <= st0_tvalid;
            
            
            // stage 2
            st2_tuser  <= st1_tuser;
            st2_tlast  <= st1_tlast;
            st2_tdata  <= st1_tdata;
            st2_tvalid <= st1_tvalid;
        end
    end
    
    assign cke = !m_axi4s_tvalid || m_axi4s_tready;
    
    assign rd_en     = cke;
    assign rd_regcke = cke;
    assign rd_addr_x = (st0_rd_addr_x >> DIV_X);
    assign rd_addr_y = (st0_rd_addr_y >> DIV_Y);
    
    
    assign s_axi4s_tready = cke;
    
    assign m_axi4s_tuser       = st2_tuser;
    assign m_axi4s_tlast       = st2_tlast;
    assign m_axi4s_tdata       = st2_tdata;
    assign m_axi4s_tdata_store = rd_dout;
    assign m_axi4s_tvalid      = st2_tvalid;
    
endmodule