module cam_srl #(
    // search data bus width
    parameter DATA_WIDTH = 64,
    // memory size in log2(words)
    parameter ADDR_WIDTH = 5,
    // width of data bus slices (4 for SRL16, 5 for SRL32)
    parameter SLICE_WIDTH = 4
)
(
    input  wire                     clk,
    input  wire                     rst,

    input  wire [ADDR_WIDTH-1:0]    write_addr,
    input  wire [DATA_WIDTH-1:0]    write_data,
    input  wire                     write_delete,
    input  wire                     write_enable,
    output wire                     write_busy,

    input  wire [DATA_WIDTH-1:0]    compare_data,
    output wire [2**ADDR_WIDTH-1:0] match_many,
    output wire [2**ADDR_WIDTH-1:0] match_single,
    output wire [ADDR_WIDTH-1:0]    match_addr,
    output wire                     match
);

// total number of slices (enough to cover DATA_WIDTH with address inputs)
localparam SLICE_COUNT = (DATA_WIDTH + SLICE_WIDTH - 1) / SLICE_WIDTH;
// depth of RAMs
localparam RAM_DEPTH = 2**ADDR_WIDTH;

localparam [1:0]
    STATE_INIT = 2'd0,
    STATE_IDLE = 2'd1,
    STATE_WRITE = 2'd2,
    STATE_DELETE = 2'd3;

reg [1:0] state_reg = STATE_INIT, state_next;

wire [SLICE_COUNT*SLICE_WIDTH-1:0] compare_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, compare_data};
wire [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, write_data};

reg [SLICE_WIDTH-1:0] count_reg = {SLICE_WIDTH{1'b1}}, count_next;

reg [SLICE_COUNT-1:0] shift_data;
reg [RAM_DEPTH-1:0] shift_en;

reg [ADDR_WIDTH-1:0] write_addr_reg = {ADDR_WIDTH{1'b0}}, write_addr_next;
reg [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded_reg = {SLICE_COUNT*SLICE_WIDTH{1'b0}}, write_data_padded_next;

reg write_busy_reg = 1'b1;

assign write_busy = write_busy_reg;

reg [RAM_DEPTH-1:0] match_raw_out[SLICE_COUNT-1:0];
reg [RAM_DEPTH-1:0] match_many_raw;
reg [RAM_DEPTH-1:0] match_many_reg = {RAM_DEPTH{1'b0}};

assign match_many = match_many_reg;

integer k;

always @* begin
    match_many_raw = ~shift_en;
    for (k = 0; k < SLICE_COUNT; k = k + 1) begin
        match_many_raw = match_many_raw & match_raw_out[k];
    end
end

cam_priority_encoder #(
    .WIDTH(RAM_DEPTH),
    .LSB_PRIORITY("HIGH")
)
priority_encoder_inst (
    .input_unencoded(match_many_reg),
    .output_valid(match),
    .output_encoded(match_addr),
    .output_unencoded(match_single)
);

integer i;

// SRLs
genvar row_ind, slice_ind;
generate
    for (row_ind = 0; row_ind < RAM_DEPTH; row_ind = row_ind + 1) begin : row
        for (slice_ind = 0; slice_ind < SLICE_COUNT; slice_ind = slice_ind + 1) begin : slice
            reg [2**SLICE_WIDTH-1:0] srl_mem = {2**SLICE_WIDTH{1'b0}};

            // match
            always @* begin
                match_raw_out[slice_ind][row_ind] = srl_mem[compare_data_padded[SLICE_WIDTH * slice_ind +: SLICE_WIDTH]];
            end

            // write
            always @(posedge clk) begin
                if (shift_en[row_ind]) begin
                    srl_mem <= {srl_mem[2**SLICE_WIDTH-2:0], shift_data[slice_ind]};
                end
            end
        end
    end
endgenerate

// match
always @(posedge clk) begin
    match_many_reg <= match_many_raw;
end

// write
always @* begin
    state_next = STATE_IDLE;

    count_next = count_reg;
    shift_data = {SLICE_COUNT{1'b0}};
    shift_en = {RAM_DEPTH{1'b0}};

    write_addr_next = write_addr_reg;
    write_data_padded_next = write_data_padded_reg;

    case (state_reg)
        STATE_INIT: begin
            // zero out shift registers
            shift_en = {RAM_DEPTH{1'b1}};
            shift_data = {SLICE_COUNT{1'b0}};

            if (count_reg == 0) begin
                state_next = STATE_IDLE;
            end else begin
                count_next = count_reg - 1;
                state_next = STATE_INIT;
            end
        end
        STATE_IDLE: begin
            if (write_enable) begin
                write_addr_next = write_addr;
                write_data_padded_next = write_data_padded;
                count_next = {SLICE_WIDTH{1'b1}};
                if (write_delete) begin
                    state_next = STATE_DELETE;
                end else begin
                    state_next = STATE_WRITE;
                end
            end else begin
                state_next = STATE_IDLE;
            end
        end
        STATE_WRITE: begin
            // write entry
            shift_en = 1'b1 << write_addr;

            for (i = 0; i < SLICE_COUNT; i = i + 1) begin
                shift_data[i] = count_reg == write_data_padded_reg[SLICE_WIDTH * i +: SLICE_WIDTH];
            end

            if (count_reg == 0) begin
                state_next = STATE_IDLE;
            end else begin
                count_next = count_reg - 1;
                state_next = STATE_WRITE;
            end
        end
        STATE_DELETE: begin
            // delete entry
            shift_en = 1'b1 << write_addr;
            shift_data = {SLICE_COUNT{1'b0}};

            if (count_reg == 0) begin
                state_next = STATE_IDLE;
            end else begin
                count_next = count_reg - 1;
                state_next = STATE_DELETE;
            end
        end
    endcase
end

always @(posedge clk) begin
    if (rst) begin
        state_reg <= STATE_INIT;
        count_reg <= {SLICE_WIDTH{1'b1}};
        write_busy_reg <= 1'b1;
    end else begin
        state_reg <= state_next;
        count_reg <= count_next;
        write_busy_reg <= state_next != STATE_IDLE;
    end

    write_addr_reg <= write_addr_next;
    write_data_padded_reg <= write_data_padded_next;
end

endmodule