module axi_vfifo_enc #
(
    // Width of input segment
    parameter SEG_WIDTH = 32,
    // Segment count
    parameter SEG_CNT = 2,
    // Width of AXI stream interfaces in bits
    parameter AXIS_DATA_WIDTH = SEG_WIDTH*SEG_CNT/2,
    // Use AXI stream tkeep signal
    parameter AXIS_KEEP_ENABLE = (AXIS_DATA_WIDTH>8),
    // AXI stream tkeep signal width (words per cycle)
    parameter AXIS_KEEP_WIDTH = (AXIS_DATA_WIDTH/8),
    // Use AXI stream tlast signal
    parameter AXIS_LAST_ENABLE = 1,
    // Propagate AXI stream tid signal
    parameter AXIS_ID_ENABLE = 0,
    // AXI stream tid signal width
    parameter AXIS_ID_WIDTH = 8,
    // Propagate AXI stream tdest signal
    parameter AXIS_DEST_ENABLE = 0,
    // AXI stream tdest signal width
    parameter AXIS_DEST_WIDTH = 8,
    // Propagate AXI stream tuser signal
    parameter AXIS_USER_ENABLE = 1,
    // AXI stream tuser signal width
    parameter AXIS_USER_WIDTH = 1
)
(
    input  wire                          clk,
    input  wire                          rst,

    /*
     * AXI stream data input
     */
    input  wire [AXIS_DATA_WIDTH-1:0]    s_axis_tdata,
    input  wire [AXIS_KEEP_WIDTH-1:0]    s_axis_tkeep,
    input  wire                          s_axis_tvalid,
    output wire                          s_axis_tready,
    input  wire                          s_axis_tlast,
    input  wire [AXIS_ID_WIDTH-1:0]      s_axis_tid,
    input  wire [AXIS_DEST_WIDTH-1:0]    s_axis_tdest,
    input  wire [AXIS_USER_WIDTH-1:0]    s_axis_tuser,

    /*
     * Segmented data output (to virtual FIFO channel)
     */
    input  wire                          fifo_rst_in,
    output wire [SEG_CNT*SEG_WIDTH-1:0]  output_data,
    output wire [SEG_CNT-1:0]            output_valid,
    input  wire                          fifo_watermark_in
);

parameter AXIS_KEEP_WIDTH_INT = AXIS_KEEP_ENABLE ? AXIS_KEEP_WIDTH : 1;
parameter AXIS_BYTE_LANES = AXIS_KEEP_WIDTH_INT;
parameter AXIS_BYTE_SIZE = AXIS_DATA_WIDTH/AXIS_BYTE_LANES;
parameter CL_AXIS_BYTE_LANES = $clog2(AXIS_BYTE_LANES);

parameter BYTE_SIZE = AXIS_BYTE_SIZE;

parameter SEG_BYTE_LANES = SEG_WIDTH / BYTE_SIZE;

parameter EXPAND_OUTPUT = SEG_CNT < 2;

parameter SEG_CNT_INT = EXPAND_OUTPUT ? SEG_CNT*2 : SEG_CNT;

parameter SEG_IDX_WIDTH = $clog2(SEG_CNT_INT);
parameter SEG_BYTE_IDX_WIDTH = $clog2(SEG_BYTE_LANES);

parameter AXIS_SEG_CNT = (AXIS_DATA_WIDTH + SEG_WIDTH-1) / SEG_WIDTH;
parameter AXIS_SEG_IDX_WIDTH = AXIS_SEG_CNT > 1 ? $clog2(AXIS_SEG_CNT) : 1;
parameter AXIS_LEN_MASK = AXIS_BYTE_LANES-1;

parameter IN_OFFS_WIDTH = AXIS_SEG_IDX_WIDTH;

parameter META_ID_OFFSET = 0;
parameter META_DEST_OFFSET = META_ID_OFFSET + (AXIS_ID_ENABLE ? AXIS_ID_WIDTH : 0);
parameter META_USER_OFFSET = META_DEST_OFFSET + (AXIS_DEST_ENABLE ? AXIS_DEST_WIDTH : 0);
parameter META_WIDTH = META_USER_OFFSET + (AXIS_USER_ENABLE ? AXIS_USER_WIDTH : 0);
parameter HDR_SIZE = (16 + META_WIDTH + BYTE_SIZE-1) / BYTE_SIZE;
parameter HDR_WIDTH = HDR_SIZE * BYTE_SIZE;

parameter HDR_LEN_WIDTH = 12;
parameter HDR_SEG_LEN_WIDTH = HDR_LEN_WIDTH-SEG_BYTE_IDX_WIDTH;

parameter INPUT_FIFO_ADDR_WIDTH = 5;
parameter HDR_FIFO_ADDR_WIDTH = INPUT_FIFO_ADDR_WIDTH + SEG_IDX_WIDTH;

parameter INPUT_FIFO_PTR_WIDTH = INPUT_FIFO_ADDR_WIDTH + SEG_IDX_WIDTH;
parameter HDR_FIFO_PTR_WIDTH = HDR_FIFO_ADDR_WIDTH;

parameter INPUT_FIFO_SIZE = SEG_BYTE_LANES * SEG_CNT_INT * 2**INPUT_FIFO_ADDR_WIDTH;

parameter MAX_BLOCK_LEN = INPUT_FIFO_SIZE / 2 > 4096 ? 4096 : INPUT_FIFO_SIZE / 2;

// validate parameters
initial begin
    if (AXIS_BYTE_SIZE * AXIS_KEEP_WIDTH_INT != AXIS_DATA_WIDTH) begin
        $error("Error: AXI stream data width not evenly divisible (instance %m)");
        $finish;
    end

    if (AXIS_SEG_CNT * SEG_WIDTH != AXIS_DATA_WIDTH) begin
        $error("Error: AXI stream data width not evenly divisible into segments (instance %m)");
        $finish;
    end

    if (SEG_WIDTH < HDR_SIZE*BYTE_SIZE) begin
        $error("Error: Segment smaller than header (instance %m)");
        $finish;
    end
end

reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_wr_ptr_reg = 0, input_fifo_wr_ptr_next;
reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_rd_ptr_reg = 0, input_fifo_rd_ptr_next;
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_wr_ptr_reg = 0, hdr_fifo_wr_ptr_next;
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_rd_ptr_reg = 0, hdr_fifo_rd_ptr_next;

reg [SEG_CNT_INT-1:0] mem_rd_data_valid_reg = 0, mem_rd_data_valid_next;
reg hdr_mem_rd_data_valid_reg = 0, hdr_mem_rd_data_valid_next;

reg [AXIS_DATA_WIDTH-1:0] int_seg_data;
reg [AXIS_SEG_CNT-1:0] int_seg_valid;

reg [SEG_CNT_INT*SEG_WIDTH-1:0] seg_mem_wr_data;
reg [SEG_CNT_INT-1:0] seg_mem_wr_valid;
reg [SEG_CNT_INT*INPUT_FIFO_ADDR_WIDTH-1:0] seg_mem_wr_addr_reg = 0, seg_mem_wr_addr_next;
reg [SEG_CNT_INT-1:0] seg_mem_wr_en;
reg [SEG_CNT_INT*SEG_IDX_WIDTH-1:0] seg_mem_wr_sel;

wire [SEG_CNT_INT*SEG_WIDTH-1:0] seg_mem_rd_data;
reg [SEG_CNT_INT*INPUT_FIFO_ADDR_WIDTH-1:0] seg_mem_rd_addr_reg = 0, seg_mem_rd_addr_next;
reg [SEG_CNT_INT-1:0] seg_mem_rd_en;

reg [HDR_LEN_WIDTH-1:0] hdr_mem_wr_len;
reg hdr_mem_wr_last;
reg [META_WIDTH-1:0] hdr_mem_wr_meta;
reg [HDR_FIFO_ADDR_WIDTH-1:0] hdr_mem_wr_addr;
reg hdr_mem_wr_en;

wire [HDR_LEN_WIDTH-1:0] hdr_mem_rd_len;
wire hdr_mem_rd_last;
wire [META_WIDTH-1:0] hdr_mem_rd_meta;
reg [HDR_FIFO_ADDR_WIDTH-1:0] hdr_mem_rd_addr_reg = 0, hdr_mem_rd_addr_next;
reg hdr_mem_rd_en;

reg input_fifo_full_reg = 1'b0;
reg input_fifo_half_full_reg = 1'b0;
reg input_fifo_empty_reg = 1'b1;
reg [INPUT_FIFO_PTR_WIDTH+1-1:0] input_fifo_count_reg = 0;
reg hdr_fifo_full_reg = 1'b0;
reg hdr_fifo_half_full_reg = 1'b0;
reg hdr_fifo_empty_reg = 1'b1;
reg [HDR_FIFO_PTR_WIDTH+1-1:0] hdr_fifo_count_reg = 0;

reg [SEG_CNT*SEG_WIDTH-1:0] output_data_reg = 0, output_data_next;
reg [SEG_CNT-1:0] output_valid_reg = 0, output_valid_next;

assign s_axis_tready = !input_fifo_full_reg && !hdr_fifo_full_reg && !fifo_rst_in;

assign output_data = output_data_reg;
assign output_valid = output_valid_reg;

generate

genvar n;

for (n = 0; n < SEG_CNT_INT; n = n + 1) begin : seg_ram

    (* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
    reg [SEG_WIDTH-1:0] seg_mem_data[2**INPUT_FIFO_ADDR_WIDTH-1:0];

    wire wr_en = seg_mem_wr_en[n];
    wire [INPUT_FIFO_ADDR_WIDTH-1:0] wr_addr = seg_mem_wr_addr_reg[n*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH];
    wire [SEG_WIDTH-1:0] wr_data = seg_mem_wr_data[n*SEG_WIDTH +: SEG_WIDTH];

    wire rd_en = seg_mem_rd_en[n];
    wire [INPUT_FIFO_ADDR_WIDTH-1:0] rd_addr = seg_mem_rd_addr_reg[n*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH];
    reg [SEG_WIDTH-1:0] rd_data_reg = 0;

    assign seg_mem_rd_data[n*SEG_WIDTH +: SEG_WIDTH] = rd_data_reg;

    always @(posedge clk) begin
        if (wr_en) begin
            seg_mem_data[wr_addr] <= wr_data;
        end
    end

    always @(posedge clk) begin
        if (rd_en) begin
            rd_data_reg <= seg_mem_data[rd_addr];
        end
    end

end

endgenerate

(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
reg [HDR_LEN_WIDTH-1:0] hdr_mem_len[2**HDR_FIFO_ADDR_WIDTH-1:0];
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
reg hdr_mem_last[2**HDR_FIFO_ADDR_WIDTH-1:0];
(* ram_style = "distributed", ramstyle = "no_rw_check, mlab" *)
reg [META_WIDTH-1:0] hdr_mem_meta[2**HDR_FIFO_ADDR_WIDTH-1:0];

reg [HDR_LEN_WIDTH-1:0] hdr_mem_rd_len_reg = 0;
reg hdr_mem_rd_last_reg = 1'b0;
reg [META_WIDTH-1:0] hdr_mem_rd_meta_reg = 0;

assign hdr_mem_rd_len = hdr_mem_rd_len_reg;
assign hdr_mem_rd_last = hdr_mem_rd_last_reg;
assign hdr_mem_rd_meta = hdr_mem_rd_meta_reg;

always @(posedge clk) begin
    if (hdr_mem_wr_en) begin
        hdr_mem_len[hdr_mem_wr_addr] <= hdr_mem_wr_len;
        hdr_mem_last[hdr_mem_wr_addr] <= hdr_mem_wr_last;
        hdr_mem_meta[hdr_mem_wr_addr] <= hdr_mem_wr_meta;
    end
end

always @(posedge clk) begin
    if (hdr_mem_rd_en) begin
        hdr_mem_rd_len_reg <= hdr_mem_len[hdr_mem_rd_addr_reg];
        hdr_mem_rd_last_reg <= hdr_mem_last[hdr_mem_rd_addr_reg];
        hdr_mem_rd_meta_reg <= hdr_mem_meta[hdr_mem_rd_addr_reg];
    end
end

// limits
always @(posedge clk) begin
    input_fifo_full_reg <= $unsigned(input_fifo_wr_ptr_reg - input_fifo_rd_ptr_reg) >= (2**INPUT_FIFO_ADDR_WIDTH*SEG_CNT_INT)-SEG_CNT_INT*2;
    input_fifo_half_full_reg <= $unsigned(input_fifo_wr_ptr_reg - input_fifo_rd_ptr_reg) >= (2**INPUT_FIFO_ADDR_WIDTH*SEG_CNT_INT)/2;
    hdr_fifo_full_reg <= $unsigned(hdr_fifo_wr_ptr_reg - hdr_fifo_rd_ptr_reg) >= 2**HDR_FIFO_ADDR_WIDTH-4;
    hdr_fifo_half_full_reg <= $unsigned(hdr_fifo_wr_ptr_reg - hdr_fifo_rd_ptr_reg) >= 2**HDR_FIFO_ADDR_WIDTH/2;

    if (rst) begin
        input_fifo_full_reg <= 1'b0;
        input_fifo_half_full_reg <= 1'b0;
        hdr_fifo_full_reg <= 1'b0;
        hdr_fifo_half_full_reg <= 1'b0;
    end
end

// Split input segments
integer si;

always @* begin
    int_seg_data = s_axis_tdata;
    int_seg_valid = 0;

    if (s_axis_tvalid) begin
        if (s_axis_tlast) begin
            for (si = 0; si < AXIS_SEG_CNT; si = si + 1) begin
                int_seg_valid[si] = s_axis_tkeep[SEG_BYTE_LANES*si +: SEG_BYTE_LANES] != 0;
            end
        end else begin
            int_seg_valid = {AXIS_SEG_CNT{1'b1}};
        end
    end else begin
        int_seg_valid = 0;
    end
end

// Write logic
integer seg, k;
reg [SEG_IDX_WIDTH+1-1:0] seg_count;
reg [SEG_IDX_WIDTH-1:0] cur_seg;

reg frame_reg = 1'b0, frame_next;
reg [HDR_LEN_WIDTH-1:0] len_reg = 0, len_next;

reg cycle_valid_reg = 1'b0, cycle_valid_next;
reg cycle_last_reg = 1'b0, cycle_last_next;
reg [CL_AXIS_BYTE_LANES+1-1:0] cycle_len_reg = 0, cycle_len_next;
reg [META_WIDTH-1:0] cycle_meta_reg = 0, cycle_meta_next;

reg [CL_AXIS_BYTE_LANES+1-1:0] cycle_len;

reg [HDR_LEN_WIDTH-1:0] hdr_len_reg = 0, hdr_len_next;
reg [META_WIDTH-1:0] hdr_meta_reg = 0, hdr_meta_next;
reg hdr_last_reg = 0, hdr_last_next;
reg hdr_commit_reg = 0, hdr_commit_next;
reg hdr_commit_prev_reg = 0, hdr_commit_prev_next;
reg hdr_valid_reg = 0, hdr_valid_next;

wire [META_WIDTH-1:0] s_axis_meta;

generate

if (AXIS_ID_ENABLE) assign s_axis_meta[META_ID_OFFSET +: AXIS_ID_WIDTH] = s_axis_tid;
if (AXIS_DEST_ENABLE) assign s_axis_meta[META_DEST_OFFSET +: AXIS_DEST_WIDTH] = s_axis_tdest;
if (AXIS_USER_ENABLE) assign s_axis_meta[META_USER_OFFSET +: AXIS_USER_WIDTH] = s_axis_tuser;

endgenerate

always @* begin
    input_fifo_wr_ptr_next = input_fifo_wr_ptr_reg;
    hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg;

    if (AXIS_KEEP_ENABLE) begin
        cycle_len = 0;
        for (k = 0; k < AXIS_BYTE_LANES; k = k + 1) begin
            cycle_len = cycle_len + s_axis_tkeep[k];
        end
    end else begin
        cycle_len = AXIS_BYTE_LANES;
    end

    // pack segments
    seg_mem_wr_valid = 0;
    seg_mem_wr_sel = 0;
    cur_seg = input_fifo_wr_ptr_reg[SEG_IDX_WIDTH-1:0];
    seg_count = 0;
    for (seg = 0; seg < AXIS_SEG_CNT; seg = seg + 1) begin
        if (int_seg_valid[seg]) begin
            seg_mem_wr_valid[cur_seg +: 1] = 1'b1;
            seg_mem_wr_sel[cur_seg*SEG_IDX_WIDTH +: SEG_IDX_WIDTH] = seg;
            cur_seg = cur_seg + 1;
            seg_count = seg_count + 1;
        end
    end

    for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
        seg_mem_wr_data[seg*SEG_WIDTH +: SEG_WIDTH] = int_seg_data[seg_mem_wr_sel[seg*SEG_IDX_WIDTH +: SEG_IDX_WIDTH]*SEG_WIDTH +: SEG_WIDTH];
    end

    seg_mem_wr_addr_next = seg_mem_wr_addr_reg;
    seg_mem_wr_en = 0;

    hdr_mem_wr_len = hdr_len_reg;
    hdr_mem_wr_last = hdr_last_reg;
    hdr_mem_wr_meta = hdr_meta_reg;
    hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
    hdr_mem_wr_en = 1'b0;

    frame_next = frame_reg;
    len_next = len_reg;

    cycle_valid_next = 1'b0;
    cycle_last_next = cycle_last_reg;
    cycle_len_next = cycle_len_reg;
    cycle_meta_next = cycle_meta_reg;

    hdr_len_next = len_reg;
    hdr_meta_next = cycle_meta_reg;
    hdr_last_next = cycle_last_reg;
    hdr_commit_next = 1'b0;
    hdr_commit_prev_next = 1'b0;
    hdr_valid_next = 1'b0;

    if (s_axis_tvalid && s_axis_tready) begin
        // transfer data
        seg_mem_wr_en = seg_mem_wr_valid;
        input_fifo_wr_ptr_next = input_fifo_wr_ptr_reg + seg_count;
        for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
            seg_mem_wr_addr_next[seg*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH] = (input_fifo_wr_ptr_next + (SEG_CNT_INT-1 - seg)) >> SEG_IDX_WIDTH;
        end

        cycle_valid_next = 1'b1;
        cycle_last_next = s_axis_tlast;
        cycle_len_next = cycle_len;
        cycle_meta_next = s_axis_meta;
    end

    if (cycle_valid_reg) begin
        // process packets
        if (!frame_reg) begin
            frame_next = 1'b1;

            if (cycle_last_reg) begin
                len_next = cycle_len_reg;
            end else begin
                len_next = AXIS_BYTE_LANES;
            end

            hdr_len_next = len_next-1;
            hdr_meta_next = cycle_meta_reg;
            hdr_last_next = cycle_last_reg;
            hdr_valid_next = 1'b1;

            if (cycle_last_reg) begin
                // end of frame

                hdr_commit_next = 1'b1;

                frame_next = 1'b0;
            end
        end else begin
            if (cycle_meta_reg != hdr_meta_reg) begin
                if (cycle_last_reg) begin
                    len_next = cycle_len_reg;
                end else begin
                    len_next = AXIS_BYTE_LANES;
                end
            end else begin
                if (cycle_last_reg) begin
                    len_next = len_reg + cycle_len_reg;
                end else begin
                    len_next = len_reg + AXIS_BYTE_LANES;
                end
            end

            hdr_len_next = len_next-1;
            hdr_meta_next = cycle_meta_reg;
            hdr_last_next = cycle_last_reg;
            hdr_valid_next = 1'b1;

            if (cycle_meta_reg != hdr_meta_reg) begin
                // meta changed

                hdr_commit_prev_next = 1'b1;

                if (cycle_last_reg) begin
                    hdr_commit_next = 1'b1;
                    frame_next = 1'b0;
                end
            end else if (cycle_last_reg || len_next >= MAX_BLOCK_LEN) begin
                // end of frame or block is full

                hdr_commit_next = 1'b1;

                frame_next = 1'b0;
            end
        end
    end

    if (hdr_valid_reg) begin
        hdr_mem_wr_len = hdr_len_reg;
        hdr_mem_wr_last = hdr_last_reg;
        hdr_mem_wr_meta = hdr_meta_reg;
        hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
        hdr_mem_wr_en = 1'b1;

        if (hdr_commit_prev_reg) begin
            if (hdr_commit_reg) begin
                hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 2;
                hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg + 1;
            end else begin
                hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 1;
                hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg + 1;
            end
        end else begin
            if (hdr_commit_reg) begin
                hdr_fifo_wr_ptr_next = hdr_fifo_wr_ptr_reg + 1;
                hdr_mem_wr_addr = hdr_fifo_wr_ptr_reg;
            end
        end
    end
end

always @(posedge clk) begin
    input_fifo_wr_ptr_reg <= input_fifo_wr_ptr_next;
    hdr_fifo_wr_ptr_reg <= hdr_fifo_wr_ptr_next;

    seg_mem_wr_addr_reg <= seg_mem_wr_addr_next;

    frame_reg <= frame_next;
    len_reg <= len_next;

    cycle_valid_reg <= cycle_valid_next;
    cycle_last_reg <= cycle_last_next;
    cycle_len_reg <= cycle_len_next;
    cycle_meta_reg <= cycle_meta_next;

    hdr_len_reg <= hdr_len_next;
    hdr_meta_reg <= hdr_meta_next;
    hdr_last_reg <= hdr_last_next;
    hdr_commit_reg <= hdr_commit_next;
    hdr_commit_prev_reg <= hdr_commit_prev_next;
    hdr_valid_reg <= hdr_valid_next;

    if (rst || fifo_rst_in) begin
        input_fifo_wr_ptr_reg <= 0;
        hdr_fifo_wr_ptr_reg <= 0;

        seg_mem_wr_addr_reg <= 0;

        frame_reg <= 1'b0;

        cycle_valid_reg <= 1'b0;
        hdr_valid_reg <= 1'b0;
    end
end

// Read logic
integer rd_seg;
reg [SEG_IDX_WIDTH-1:0] cur_rd_seg;
reg rd_valid;

reg out_frame_reg = 1'b0, out_frame_next;
reg [HDR_LEN_WIDTH-1:0] out_len_reg = 0, out_len_next;
reg out_split1_reg = 1'b0, out_split1_next;
reg [HDR_SEG_LEN_WIDTH-1:0] out_seg_cnt_in_reg = 0, out_seg_cnt_in_next;
reg out_seg_last_straddle_reg = 1'b0, out_seg_last_straddle_next;
reg [SEG_IDX_WIDTH-1:0] out_seg_offset_reg = 0, out_seg_offset_next;
reg [SEG_IDX_WIDTH-1:0] out_seg_fifo_offset_reg = 0, out_seg_fifo_offset_next;
reg [SEG_IDX_WIDTH+1-1:0] out_seg_count_reg = 0, out_seg_count_next;

reg [HDR_WIDTH-1:0] out_hdr_reg = 0, out_hdr_next;

reg [SEG_CNT_INT-1:0] out_ctl_seg_hdr_reg = 0, out_ctl_seg_hdr_next, out_ctl_seg_hdr_raw;
reg [SEG_CNT_INT-1:0] out_ctl_seg_split1_reg = 0, out_ctl_seg_split1_next, out_ctl_seg_split1_raw;
reg [SEG_CNT_INT-1:0] out_ctl_seg_en_reg = 0, out_ctl_seg_en_next, out_ctl_seg_en_raw;
reg [SEG_IDX_WIDTH-1:0] out_ctl_seg_idx_reg[SEG_CNT_INT-1:0], out_ctl_seg_idx_next[SEG_CNT_INT-1:0];
reg [SEG_IDX_WIDTH-1:0] out_ctl_seg_offset_reg = 0, out_ctl_seg_offset_next;

reg [HDR_WIDTH-1:0] out_shift_reg = 0, out_shift_next;

reg [7:0] block_timeout_count_reg = 0, block_timeout_count_next;
reg block_timeout_reg = 0, block_timeout_next;

always @* begin
    input_fifo_rd_ptr_next = input_fifo_rd_ptr_reg;
    hdr_fifo_rd_ptr_next = hdr_fifo_rd_ptr_reg;

    mem_rd_data_valid_next = mem_rd_data_valid_reg;
    hdr_mem_rd_data_valid_next = hdr_mem_rd_data_valid_reg;

    output_data_next = output_data_reg;
    output_valid_next = 0;

    seg_mem_rd_addr_next = seg_mem_rd_addr_reg;
    seg_mem_rd_en = 0;

    hdr_mem_rd_addr_next = hdr_mem_rd_addr_reg;
    hdr_mem_rd_en = 0;

    out_frame_next = out_frame_reg;
    out_len_next = out_len_reg;
    out_split1_next = out_split1_reg;
    out_seg_cnt_in_next = out_seg_cnt_in_reg;
    out_seg_last_straddle_next = out_seg_last_straddle_reg;
    out_seg_offset_next = out_seg_offset_reg;
    out_seg_fifo_offset_next = out_seg_fifo_offset_reg;

    out_hdr_next = out_hdr_reg;

    out_ctl_seg_hdr_raw = 0;
    out_ctl_seg_hdr_next = 0;
    out_ctl_seg_split1_raw = 0;
    out_ctl_seg_split1_next = 0;
    out_ctl_seg_en_raw = 0;
    out_ctl_seg_en_next = 0;
    out_ctl_seg_offset_next = out_seg_offset_reg;

    for (seg = 0; seg < SEG_CNT_INT; seg = seg + 1) begin
        out_ctl_seg_idx_next[seg] = out_seg_fifo_offset_reg - out_seg_offset_reg + seg;
    end

    // partial block timeout handling
    block_timeout_count_next = block_timeout_count_reg;
    block_timeout_next = block_timeout_count_reg == 0;
    if (output_valid || out_seg_offset_reg == 0) begin
        block_timeout_count_next = 8'hff;
        block_timeout_next = 1'b0;
    end else if (block_timeout_count_reg > 0) begin
        block_timeout_count_next = block_timeout_count_reg - 1;
    end

    // process headers and generate output commands
    if (!fifo_watermark_in) begin
        if (out_frame_reg) begin
            if (out_seg_cnt_in_next >= SEG_CNT_INT) begin
                out_frame_next = out_seg_last_straddle_next || out_seg_cnt_in_next > SEG_CNT_INT;
                out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
                out_seg_offset_next = out_seg_offset_reg + SEG_CNT_INT;
                out_seg_fifo_offset_next = out_seg_fifo_offset_reg + SEG_CNT_INT;
            end else begin
                out_frame_next = 1'b0;
                if (out_seg_last_straddle_next) begin
                    out_ctl_seg_split1_raw = 1 << out_seg_cnt_in_next;
                    if (out_seg_cnt_in_next == SEG_CNT_INT-1) begin
                        out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
                    end else begin
                        out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - (out_seg_cnt_in_next+1));
                    end
                    out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next+1;
                end else begin
                    out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - out_seg_cnt_in_next);
                    out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next;
                end
                out_seg_fifo_offset_next = out_seg_fifo_offset_reg + out_seg_cnt_in_next;
            end

            out_seg_cnt_in_next = out_seg_cnt_in_next - SEG_CNT_INT;
        end else begin
            out_len_next = hdr_mem_rd_len;
            out_seg_cnt_in_next = (hdr_mem_rd_len + SEG_BYTE_LANES) >> SEG_BYTE_IDX_WIDTH;
            out_seg_last_straddle_next = ((hdr_mem_rd_len & (SEG_BYTE_LANES-1)) + HDR_SIZE) >> SEG_BYTE_IDX_WIDTH != 0;
            out_hdr_next = 0;
            out_hdr_next[0] = 1'b1;
            out_hdr_next[1] = hdr_mem_rd_last;
            out_hdr_next[2] = !hdr_mem_rd_last;
            out_hdr_next[15:4] = hdr_mem_rd_len;
            out_hdr_next[3] = ^hdr_mem_rd_len;
            if (META_WIDTH > 0) begin
                out_hdr_next[16 +: META_WIDTH] = hdr_mem_rd_meta;
            end

            out_ctl_seg_hdr_raw = 1;

            if (hdr_mem_rd_data_valid_reg) begin
                if (out_seg_cnt_in_next >= SEG_CNT_INT) begin
                    out_frame_next = out_seg_last_straddle_next || out_seg_cnt_in_next > SEG_CNT_INT;
                    out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
                    out_seg_offset_next = out_seg_offset_reg + SEG_CNT_INT;
                    out_seg_fifo_offset_next = out_seg_fifo_offset_reg + SEG_CNT_INT;
                end else begin
                    out_frame_next = 1'b0;
                    if (out_seg_last_straddle_next) begin
                        out_ctl_seg_split1_raw = 1 << out_seg_cnt_in_next;
                        if (out_seg_cnt_in_next == SEG_CNT_INT-1) begin
                            out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}};
                        end else begin
                            out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - (out_seg_cnt_in_next+1));
                        end
                        out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next+1;
                    end else begin
                        out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> (SEG_CNT_INT - out_seg_cnt_in_next);
                        out_seg_offset_next = out_seg_offset_reg + out_seg_cnt_in_next;
                    end
                    out_seg_fifo_offset_next = out_seg_fifo_offset_reg + out_seg_cnt_in_next;
                end

                out_seg_cnt_in_next = out_seg_cnt_in_next - SEG_CNT_INT;

                hdr_mem_rd_data_valid_next = 1'b0;
            end else if (block_timeout_reg && out_seg_offset_reg) begin
                // insert padding
                out_hdr_next[15:0] = 0;

                out_ctl_seg_en_raw = {SEG_CNT_INT{1'b1}} >> out_seg_offset_reg;
                out_ctl_seg_hdr_raw = {SEG_CNT_INT{1'b1}};
                out_ctl_seg_split1_raw = {SEG_CNT_INT{1'b1}};

                out_seg_offset_next = 0;
            end
        end
    end

    out_ctl_seg_hdr_next = {2{out_ctl_seg_hdr_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);
    out_ctl_seg_split1_next = {2{out_ctl_seg_split1_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);
    out_ctl_seg_en_next = {2{out_ctl_seg_en_raw}} >> (SEG_CNT_INT - out_seg_offset_reg);

    out_shift_next = out_shift_reg;

    // mux segments
    cur_rd_seg = out_ctl_seg_offset_reg;
    for (rd_seg = 0; rd_seg < SEG_CNT_INT; rd_seg = rd_seg + 1) begin
        output_data_next[cur_rd_seg*SEG_WIDTH +: SEG_WIDTH] = out_shift_next;
        output_data_next[cur_rd_seg*SEG_WIDTH+HDR_WIDTH +: SEG_WIDTH-HDR_WIDTH] = seg_mem_rd_data[out_ctl_seg_idx_reg[cur_rd_seg]*SEG_WIDTH +: SEG_WIDTH-HDR_WIDTH];

        if (out_ctl_seg_hdr_reg[cur_rd_seg]) begin
            output_data_next[cur_rd_seg*SEG_WIDTH +: HDR_WIDTH] = out_hdr_reg;
        end

        output_valid_next[cur_rd_seg] = out_ctl_seg_en_reg[cur_rd_seg];

        if (out_ctl_seg_en_reg[cur_rd_seg] && !out_ctl_seg_split1_reg[cur_rd_seg]) begin
            mem_rd_data_valid_next[out_ctl_seg_idx_reg[cur_rd_seg]] = 1'b0;
        end

        if (out_ctl_seg_en_reg[cur_rd_seg]) begin
            out_shift_next = seg_mem_rd_data[(out_ctl_seg_idx_reg[cur_rd_seg]+1)*SEG_WIDTH-HDR_WIDTH +: HDR_WIDTH];
        end

        cur_rd_seg = cur_rd_seg + 1;
    end

    // read segments
    cur_rd_seg = input_fifo_rd_ptr_reg[SEG_IDX_WIDTH-1:0];
    rd_valid = 1;
    for (rd_seg = 0; rd_seg < SEG_CNT_INT; rd_seg = rd_seg + 1) begin
        if (!mem_rd_data_valid_next[cur_rd_seg] && input_fifo_count_reg > rd_seg && rd_valid) begin
            input_fifo_rd_ptr_next = input_fifo_rd_ptr_reg + rd_seg+1;
            seg_mem_rd_en[cur_rd_seg] = 1'b1;
            seg_mem_rd_addr_next[cur_rd_seg*INPUT_FIFO_ADDR_WIDTH +: INPUT_FIFO_ADDR_WIDTH] = ((input_fifo_rd_ptr_reg + rd_seg) >> SEG_IDX_WIDTH) + 1;
            mem_rd_data_valid_next[cur_rd_seg] = 1'b1;
        end else begin
            rd_valid = 0;
        end
        cur_rd_seg = cur_rd_seg + 1;
    end

    // read header
    if (!hdr_mem_rd_data_valid_next && !hdr_fifo_empty_reg) begin
        hdr_fifo_rd_ptr_next = hdr_fifo_rd_ptr_reg + 1;
        hdr_mem_rd_en = 1'b1;
        hdr_mem_rd_addr_next = hdr_fifo_rd_ptr_next;
        hdr_mem_rd_data_valid_next = 1'b1;
    end
end

integer i;

always @(posedge clk) begin
    input_fifo_rd_ptr_reg <= input_fifo_rd_ptr_next;
    input_fifo_count_reg <= input_fifo_wr_ptr_next - input_fifo_rd_ptr_next;
    input_fifo_empty_reg <= input_fifo_wr_ptr_next == input_fifo_rd_ptr_next;
    hdr_fifo_rd_ptr_reg <= hdr_fifo_rd_ptr_next;
    hdr_fifo_count_reg <= hdr_fifo_wr_ptr_next - hdr_fifo_rd_ptr_next;
    hdr_fifo_empty_reg <= hdr_fifo_wr_ptr_next == hdr_fifo_rd_ptr_next;

    seg_mem_rd_addr_reg <= seg_mem_rd_addr_next;
    hdr_mem_rd_addr_reg <= hdr_mem_rd_addr_next;

    mem_rd_data_valid_reg <= mem_rd_data_valid_next;
    hdr_mem_rd_data_valid_reg <= hdr_mem_rd_data_valid_next;

    output_data_reg <= output_data_next;
    output_valid_reg <= output_valid_next;

    out_frame_reg <= out_frame_next;
    out_len_reg <= out_len_next;
    out_split1_reg <= out_split1_next;
    out_seg_cnt_in_reg <= out_seg_cnt_in_next;
    out_seg_last_straddle_reg <= out_seg_last_straddle_next;
    out_seg_offset_reg <= out_seg_offset_next;
    out_seg_fifo_offset_reg <= out_seg_fifo_offset_next;

    out_hdr_reg <= out_hdr_next;

    out_ctl_seg_hdr_reg <= out_ctl_seg_hdr_next;
    out_ctl_seg_split1_reg <= out_ctl_seg_split1_next;
    out_ctl_seg_en_reg <= out_ctl_seg_en_next;
    for (i = 0; i < SEG_CNT_INT; i = i + 1) begin
        out_ctl_seg_idx_reg[i] <= out_ctl_seg_idx_next[i];
    end
    out_ctl_seg_offset_reg <= out_ctl_seg_offset_next;

    out_shift_reg <= out_shift_next;

    block_timeout_count_reg <= block_timeout_count_next;
    block_timeout_reg <= block_timeout_next;

    if (rst || fifo_rst_in) begin
        input_fifo_rd_ptr_reg <= 0;
        input_fifo_count_reg <= 0;
        input_fifo_empty_reg <= 1'b1;
        hdr_fifo_rd_ptr_reg <= 0;
        hdr_fifo_count_reg <= 0;
        hdr_fifo_empty_reg <= 1'b1;

        seg_mem_rd_addr_reg <= 0;
        hdr_mem_rd_addr_reg <= 0;

        mem_rd_data_valid_reg <= 0;
        hdr_mem_rd_data_valid_reg <= 0;

        out_frame_reg <= 1'b0;
        out_len_reg <= 0;
        out_split1_reg <= 0;
        out_seg_offset_reg <= 0;
        out_seg_fifo_offset_reg <= 0;
        out_seg_count_reg <= 0;
    end
end

endmodule