`timescale 1ns / 1ps

module tb_runtime_reconfigurable_fir;
    reg clk;
    reg rst;
    reg [7:0] data_in;
    reg data_valid;
    reg coeff_wr_en;
    reg [2:0] coeff_addr;
    reg [15:0] coeff_data;

    wire signed [26:0] data_out;
    wire data_out_valid;

    integer errors = 0;
    integer tests_run = 0;
    integer cycle_count = 0;

    runtime_reconfigurable_fir uut (
        .clk(clk),
        .rst(rst),
        .data_in(data_in),
        .data_valid(data_valid),
        .coeff_wr_en(coeff_wr_en),
        .coeff_addr(coeff_addr),
        .coeff_data(coeff_data),
        .data_out(data_out),
        .data_out_valid(data_out_valid)
    );

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

    task run_step;
        input [255:0] tag;
        input next_rst;
        input next_data_valid;
        input [7:0] next_data_in;
        input next_coeff_wr_en;
        input [2:0] next_coeff_addr;
        input [15:0] next_coeff_data;
        input expected_data_out_valid;
        input [26:0] expected_data_out;
        begin
            @(negedge clk);
            rst = next_rst;
            data_valid = next_data_valid;
            data_in = next_data_in;
            coeff_wr_en = next_coeff_wr_en;
            coeff_addr = next_coeff_addr;
            coeff_data = next_coeff_data;

            @(posedge clk);
            #1;

            cycle_count = cycle_count + 1;
            tests_run = tests_run + 1;

            if (data_out_valid !== expected_data_out_valid) begin
                $display("ERROR [cycle %0d][%0s]: data_out_valid mismatch. expected=%b got=%b rst=%b data_valid=%b coeff_wr_en=%b coeff_addr=%0d data_in=%0d coeff_data=%0d",
                         cycle_count, tag, expected_data_out_valid, data_out_valid,
                         next_rst, next_data_valid, next_coeff_wr_en, next_coeff_addr,
                         $signed(next_data_in), $signed(next_coeff_data));
                errors = errors + 1;
            end

            if (data_out !== expected_data_out) begin
                $display("ERROR [cycle %0d][%0s]: data_out mismatch. expected=0x%07h (%0d) got=0x%07h (%0d)",
                         cycle_count, tag, expected_data_out, $signed(expected_data_out),
                         data_out, $signed(data_out));
                errors = errors + 1;
            end

            if (!expected_data_out_valid && ((data_out_valid !== 1'b0) || (data_out !== 27'd0))) begin
                $display("ERROR [cycle %0d][%0s]: idle cycle must drive data_out_valid=0 and data_out=0", cycle_count, tag);
                errors = errors + 1;
            end
        end
    endtask

    initial begin
        rst = 1'b1;
        data_in = 8'h00;
        data_valid = 1'b0;
        coeff_wr_en = 1'b0;
        coeff_addr = 3'd0;
        coeff_data = 16'h0000;

        $display("=================================================");
        $display("  Runtime-Reconfigurable 8-Tap FIR Testbench");
        $display("=================================================");

        // Prime the DUT so all synchronous state is initialized before checks.
        @(posedge clk);
        #1;

        // Generated offline by generate_golden.py and hardcoded here.
        run_step("rst_boot_0", 1'b1, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("rst_ignore_both", 1'b1, 1'b1, 8'h55, 1'b1, 3'd2, 16'h1234, 1'b0, 27'h0000000);
        run_step("post_reset_idle", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("pre_a0_temp", 1'b0, 1'b0, 8'h00, 1'b1, 3'd0, 16'h0005, 1'b0, 27'h0000000);
        run_step("pre_a0_final", 1'b0, 1'b0, 8'h00, 1'b1, 3'd0, 16'h0001, 1'b0, 27'h0000000);
        run_step("pre_a1_temp", 1'b0, 1'b0, 8'h00, 1'b1, 3'd1, 16'h000c, 1'b0, 27'h0000000);
        run_step("pre_a1_final", 1'b0, 1'b0, 8'h00, 1'b1, 3'd1, 16'h0000, 1'b0, 27'h0000000);
        run_step("pre_frame0_idle", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("f0_s0", 1'b0, 1'b1, 8'h05, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("f0_s1_wr_b0", 1'b0, 1'b1, 8'hfd, 1'b1, 3'd0, 16'h0002, 1'b1, 27'h0000005);
        run_step("f0_idle_wr_b1_temp", 1'b0, 1'b0, 8'h00, 1'b1, 3'd1, 16'h0009, 1'b1, 27'h7fffffd);
        run_step("f0_s2_wr_b1_final", 1'b0, 1'b1, 8'h07, 1'b1, 3'd1, 16'hffff, 1'b0, 27'h0000000);
        run_step("f0_s3", 1'b0, 1'b1, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0000007);
        run_step("f0_s4_wr_b2", 1'b0, 1'b1, 8'hff, 1'b1, 3'd2, 16'h0003, 1'b1, 27'h0000000);
        run_step("f0_idle_mid", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7ffffff);
        run_step("f0_s5", 1'b0, 1'b1, 8'h04, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("f0_s6", 1'b0, 1'b1, 8'hfb, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0000004);
        run_step("f0_s7", 1'b0, 1'b1, 8'h06, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7fffffb);
        run_step("f1_s0_wr_c2", 1'b0, 1'b1, 8'h02, 1'b1, 3'd2, 16'h0004, 1'b1, 27'h0000006);
        run_step("f1_s1", 1'b0, 1'b1, 8'h01, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7ffffef);
        run_step("f1_s2_wr_c0", 1'b0, 1'b1, 8'hfc, 1'b1, 3'd0, 16'hfffd, 1'b1, 27'h0000012);
        run_step("f1_idle_wr_c1_temp", 1'b0, 1'b0, 8'h00, 1'b1, 3'd1, 16'h0005, 1'b1, 27'h7fffffd);
        run_step("f1_s3_wr_c1_final", 1'b0, 1'b1, 8'h03, 1'b1, 3'd1, 16'hffff, 1'b0, 27'h0000000);
        run_step("f1_s4", 1'b0, 1'b1, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h000000d);
        run_step("f1_s5", 1'b0, 1'b1, 8'hfe, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7fffff1);
        run_step("f1_idle_mid", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0000005);
        run_step("f1_s6", 1'b0, 1'b1, 8'h05, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("f1_s7", 1'b0, 1'b1, 8'hff, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h000000c);
        run_step("f2_s0_wr_d0", 1'b0, 1'b1, 8'h07, 1'b1, 3'd0, 16'h0006, 1'b1, 27'h7fffff3);
        run_step("f2_s1", 1'b0, 1'b1, 8'hf8, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0000000);
        run_step("f2_s2_wr_d1", 1'b0, 1'b1, 8'h02, 1'b1, 3'd1, 16'h0002, 1'b1, 27'h000000d);
        run_step("f2_s3", 1'b0, 1'b1, 8'h04, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h000001e);
        run_step("f2_s4", 1'b0, 1'b1, 8'hfd, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7ffffd2);
        run_step("f2_s5", 1'b0, 1'b1, 8'h01, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h000000d);
        run_step("f2_s6", 1'b0, 1'b1, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0000010);
        run_step("f2_s7", 1'b0, 1'b1, 8'h06, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7fffff3);
        run_step("pre_frame3_wr_d2", 1'b0, 1'b0, 8'h00, 1'b1, 3'd2, 16'hfff9, 1'b1, 27'h7fffff2);
        run_step("rst_discard_d", 1'b1, 1'b1, 8'h33, 1'b1, 3'd4, 16'h2222, 1'b0, 27'h0000000);
        run_step("post_reset_idle_0", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("flush_candidate", 1'b0, 1'b1, 8'hf4, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("flush_reset", 1'b1, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("post_flush_idle", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("pre_e0", 1'b0, 1'b0, 8'h00, 1'b1, 3'd0, 16'h7fff, 1'b0, 27'h0000000);
        run_step("pre_e1", 1'b0, 1'b0, 8'h00, 1'b1, 3'd1, 16'h8000, 1'b0, 27'h0000000);
        run_step("pre_e2", 1'b0, 1'b0, 8'h00, 1'b1, 3'd2, 16'h4000, 1'b0, 27'h0000000);
        run_step("pre_e3", 1'b0, 1'b0, 8'h00, 1'b1, 3'd3, 16'hc000, 1'b0, 27'h0000000);
        run_step("pre_e4", 1'b0, 1'b0, 8'h00, 1'b1, 3'd4, 16'h0400, 1'b0, 27'h0000000);
        run_step("pre_e5", 1'b0, 1'b0, 8'h00, 1'b1, 3'd5, 16'hfc00, 1'b0, 27'h0000000);
        run_step("pre_e6", 1'b0, 1'b0, 8'h00, 1'b1, 3'd6, 16'h0003, 1'b0, 27'h0000000);
        run_step("pre_e7", 1'b0, 1'b0, 8'h00, 1'b1, 3'd7, 16'hfffd, 1'b0, 27'h0000000);
        run_step("ext_s0", 1'b0, 1'b1, 8'h7f, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);
        run_step("ext_s1", 1'b0, 1'b1, 8'h80, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h03f7f81);
        run_step("ext_s2", 1'b0, 1'b1, 8'h7f, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7808080);
        run_step("ext_s3", 1'b0, 1'b1, 8'h80, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h09f3f81);
        run_step("ext_s4", 1'b0, 1'b1, 8'h40, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h740c080);
        run_step("ext_s5", 1'b0, 1'b1, 8'hc0, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0a1bbc0);
        run_step("ext_s6", 1'b0, 1'b1, 8'h01, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h77c4440);
        run_step("ext_s7", 1'b0, 1'b1, 8'hff, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h0547d7c);
        run_step("final_drain", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b1, 27'h7db0104);
        run_step("final_clear", 1'b0, 1'b0, 8'h00, 1'b0, 3'd0, 16'h0000, 1'b0, 27'h0000000);

        $display("");
        $display("===========================================");
        $display("  Tests Run: %0d", tests_run);
        $display("===========================================");

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

        $finish;
    end
endmodule
