`timescale 1ns / 1ps

module tb_tcam8_lookup;
    reg clk;
    reg rst;
    reg [7:0] lookup_key;
    reg wr_en;
    reg [2:0] wr_addr;
    reg wr_clear;
    reg [7:0] wr_key;
    reg [7:0] wr_mask;
    reg [7:0] wr_value;

    wire match;
    wire [2:0] match_idx;
    wire [7:0] match_value;

    integer errors = 0;
    integer checks_run = 0;
    integer cycle_count = 0;

    tcam8_lookup uut (
        .clk(clk),
        .rst(rst),
        .lookup_key(lookup_key),
        .wr_en(wr_en),
        .wr_addr(wr_addr),
        .wr_clear(wr_clear),
        .wr_key(wr_key),
        .wr_mask(wr_mask),
        .wr_value(wr_value),
        .match(match),
        .match_idx(match_idx),
        .match_value(match_value)
    );

    initial begin
        clk = 1'b0;
        forever #5 clk = ~clk;
    end

    task check_outputs;
        input [255:0] tag;
        input [15:0] phase;
        input expected_match;
        input [2:0] expected_idx;
        input [7:0] expected_value;
        begin
            checks_run = checks_run + 1;

            if (match !== expected_match ||
                match_idx !== expected_idx ||
                match_value !== expected_value) begin
                $display("ERROR [cycle %0d][%0s][%0s]: expected match=%b idx=%0d value=0x%02h, got match=%b idx=%0d value=0x%02h (rst=%b lookup_key=0x%02h wr_en=%b wr_addr=%0d wr_clear=%b wr_key=0x%02h wr_mask=0x%02h wr_value=0x%02h)",
                         cycle_count, tag, phase,
                         expected_match, expected_idx, expected_value,
                         match, match_idx, match_value,
                         rst, lookup_key, wr_en, wr_addr, wr_clear, wr_key, wr_mask, wr_value);
                errors = errors + 1;
            end
        end
    endtask

    task run_step;
        input [255:0] tag;
        input step_rst;
        input [7:0] step_lookup_key;
        input step_wr_en;
        input [2:0] step_wr_addr;
        input step_wr_clear;
        input [7:0] step_wr_key;
        input [7:0] step_wr_mask;
        input [7:0] step_wr_value;
        input expected_pre_match;
        input [2:0] expected_pre_idx;
        input [7:0] expected_pre_value;
        input expected_post_match;
        input [2:0] expected_post_idx;
        input [7:0] expected_post_value;
        begin
            rst = step_rst;
            lookup_key = step_lookup_key;
            wr_en = step_wr_en;
            wr_addr = step_wr_addr;
            wr_clear = step_wr_clear;
            wr_key = step_wr_key;
            wr_mask = step_wr_mask;
            wr_value = step_wr_value;

            #1;
            check_outputs(tag, "pre", expected_pre_match, expected_pre_idx, expected_pre_value);

            @(posedge clk);
            #1;

            cycle_count = cycle_count + 1;
            check_outputs(tag, "post", expected_post_match, expected_post_idx, expected_post_value);
        end
    endtask

    initial begin
        rst = 1'b0;
        lookup_key = 8'h00;
        wr_en = 1'b0;
        wr_addr = 3'd0;
        wr_clear = 1'b0;
        wr_key = 8'h00;
        wr_mask = 8'h00;
        wr_value = 8'h00;

        $display("===========================================");
        $display("      8-Entry TCAM Lookup Testbench");
        $display("===========================================");

        // Generated by generate_golden.py and copied here. Do not edit by hand.
        run_step("rst_idle0", 1'b1, 8'h00, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("rst_ign_write", 1'b1, 8'ha5, 1'b1, 3'd2, 1'b0, 8'ha5, 8'hff, 8'h11, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("empty_post_reset", 1'b0, 8'ha5, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("write_exact_s2", 1'b0, 8'ha5, 1'b1, 3'd2, 1'b0, 8'ha5, 8'hff, 8'h11, 1'b0, 3'd0, 8'h00, 1'b1, 3'd2, 8'h11);
        run_step("query_exact_hit", 1'b0, 8'ha5, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b1, 3'd2, 8'h11, 1'b1, 3'd2, 8'h11);
        run_step("query_exact_miss", 1'b0, 8'ha4, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("write_masked_s5", 1'b0, 8'hb7, 1'b1, 3'd5, 1'b0, 8'hb2, 8'hf0, 8'h22, 1'b0, 3'd0, 8'h00, 1'b1, 3'd5, 8'h22);
        run_step("query_masked_hit", 1'b0, 8'hbf, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b1, 3'd5, 8'h22, 1'b1, 3'd5, 8'h22);
        run_step("query_masked_miss", 1'b0, 8'ha2, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("write_wildcard_s1", 1'b0, 8'h5c, 1'b1, 3'd1, 1'b0, 8'h00, 8'h00, 8'h33, 1'b0, 3'd0, 8'h00, 1'b1, 3'd1, 8'h33);
        run_step("query_wildcard", 1'b0, 8'he1, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b1, 3'd1, 8'h33, 1'b1, 3'd1, 8'h33);
        run_step("write_priority_s0", 1'b0, 8'he7, 1'b1, 3'd0, 1'b0, 8'he0, 8'hf0, 8'h44, 1'b1, 3'd1, 8'h33, 1'b1, 3'd0, 8'h44);
        run_step("query_priority_s0", 1'b0, 8'he3, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b1, 3'd0, 8'h44, 1'b1, 3'd0, 8'h44);
        run_step("write_third_match_s3", 1'b0, 8'he6, 1'b1, 3'd3, 1'b0, 8'he5, 8'hf0, 8'h55, 1'b1, 3'd0, 8'h44, 1'b1, 3'd0, 8'h44);
        run_step("clear_priority_s0", 1'b0, 8'he1, 1'b1, 3'd0, 1'b1, 8'h00, 8'h00, 8'h00, 1'b1, 3'd0, 8'h44, 1'b1, 3'd1, 8'h33);
        run_step("overwrite_s1_exact", 1'b0, 8'h12, 1'b1, 3'd1, 1'b0, 8'h12, 8'hff, 8'h66, 1'b1, 3'd1, 8'h33, 1'b1, 3'd1, 8'h66);
        run_step("query_after_overwrite_miss", 1'b0, 8'h34, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("write_s6_exact", 1'b0, 8'h3c, 1'b1, 3'd6, 1'b0, 8'h3c, 8'hff, 8'h77, 1'b0, 3'd0, 8'h00, 1'b1, 3'd6, 8'h77);
        run_step("write_s4_masked", 1'b0, 8'h3b, 1'b1, 3'd4, 1'b0, 8'h30, 8'hf0, 8'h99, 1'b0, 3'd0, 8'h00, 1'b1, 3'd4, 8'h99);
        run_step("write_s7_masked", 1'b0, 8'h3b, 1'b1, 3'd7, 1'b0, 8'h38, 8'hfc, 8'h88, 1'b1, 3'd4, 8'h99, 1'b1, 3'd4, 8'h99);
        run_step("clear_s4_reveal_s7", 1'b0, 8'h3b, 1'b1, 3'd4, 1'b1, 8'h00, 8'h00, 8'h00, 1'b1, 3'd4, 8'h99, 1'b1, 3'd7, 8'h88);
        run_step("rst_midstream", 1'b1, 8'h12, 1'b1, 3'd2, 1'b0, 8'h5a, 8'hff, 8'hab, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("post_reset_empty", 1'b0, 8'h12, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b0, 3'd0, 8'h00, 1'b0, 3'd0, 8'h00);
        run_step("write_post_reset", 1'b0, 8'h5a, 1'b1, 3'd2, 1'b0, 8'h5a, 8'hff, 8'hab, 1'b0, 3'd0, 8'h00, 1'b1, 3'd2, 8'hab);
        run_step("query_post_reset_hit", 1'b0, 8'h5a, 1'b0, 3'd0, 1'b0, 8'h00, 8'h00, 8'h00, 1'b1, 3'd2, 8'hab, 1'b1, 3'd2, 8'hab);

        $display("");
        $display("===========================================");
        $display("  Checks Run: %0d", checks_run);
        $display("===========================================");

        if (errors == 0) begin
            $display("TEST_RESULT: PASS");
        end else begin
            $display("TEST_RESULT: FAIL (%0d errors)", errors);
        end

        $finish;
    end
endmodule
