`timescale 1ns / 1ps

module tb_interrupt_controller_4src;
    reg clk;
    reg rst;
    reg [3:0] irq_req;
    reg [3:0] irq_enable;
    reg irq_ack;

    wire irq_valid;
    wire [1:0] irq_id;

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

    reg [3:0] golden_pending;
    reg expected_valid;
    reg [1:0] expected_id;

    interrupt_controller_4src uut (
        .clk(clk),
        .rst(rst),
        .irq_req(irq_req),
        .irq_enable(irq_enable),
        .irq_ack(irq_ack),
        .irq_valid(irq_valid),
        .irq_id(irq_id)
    );

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

    task compute_selected;
        input [3:0] pending_bits;
        input [3:0] enable_bits;
        output sel_valid;
        output [1:0] sel_id;
        reg [3:0] active_bits;
        begin
            active_bits = pending_bits & enable_bits;
            sel_valid = 1'b1;

            if (active_bits[0]) begin
                sel_id = 2'd0;
            end else if (active_bits[1]) begin
                sel_id = 2'd1;
            end else if (active_bits[2]) begin
                sel_id = 2'd2;
            end else if (active_bits[3]) begin
                sel_id = 2'd3;
            end else begin
                sel_valid = 1'b0;
                sel_id = 2'd0;
            end
        end
    endtask

    task step_cycle;
        input [255:0] tag;
        input next_rst;
        input [3:0] next_irq_req;
        input [3:0] next_irq_enable;
        input next_irq_ack;
        reg pre_valid;
        reg [1:0] pre_id;
        reg post_valid;
        reg [1:0] post_id;
        reg [3:0] next_pending;
        begin
            rst = next_rst;
            irq_req = next_irq_req;
            irq_enable = next_irq_enable;
            irq_ack = next_irq_ack;

            compute_selected(golden_pending, next_irq_enable, pre_valid, pre_id);

            if (next_rst) begin
                next_pending = 4'b0000;
            end else begin
                next_pending = golden_pending;
                if (next_irq_ack && pre_valid)
                    next_pending[pre_id] = 1'b0;
                next_pending = next_pending | next_irq_req;
            end

            compute_selected(next_pending, next_irq_enable, post_valid, post_id);

            @(posedge clk);
            #1;

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

            if (irq_valid !== post_valid) begin
                $display("ERROR [%s cycle %0d]: irq_valid mismatch. req=%b enable=%b ack=%b pending_before=%b expected=%b got=%b",
                         tag, cycle_count, next_irq_req, next_irq_enable, next_irq_ack,
                         golden_pending, post_valid, irq_valid);
                errors = errors + 1;
            end

            if (post_valid && (irq_id !== post_id)) begin
                $display("ERROR [%s cycle %0d]: irq_id mismatch. req=%b enable=%b ack=%b pending_before=%b expected=%0d got=%0d",
                         tag, cycle_count, next_irq_req, next_irq_enable, next_irq_ack,
                         golden_pending, post_id, irq_id);
                errors = errors + 1;
            end

            golden_pending = next_pending;
            expected_valid = post_valid;
            expected_id = post_id;
        end
    endtask

    initial begin
        rst = 1'b0;
        irq_req = 4'b0000;
        irq_enable = 4'b0000;
        irq_ack = 1'b0;
        golden_pending = 4'b0000;
        expected_valid = 1'b0;
        expected_id = 2'd0;

        $display("===========================================");
        $display("    4-Source Interrupt Controller Testbench");
        $display("===========================================");

        // Reset and idle behavior.
        step_cycle("reset_clear",         1'b1, 4'b0000, 4'b1111, 1'b0);
        step_cycle("reset_with_req",      1'b1, 4'b1010, 4'b1111, 1'b1);
        step_cycle("idle_none_enabled",   1'b0, 4'b0000, 4'b0000, 1'b0);
        step_cycle("idle_all_enabled",    1'b0, 4'b0000, 4'b1111, 1'b0);

        // Single request per source.
        step_cycle("src0_req",            1'b0, 4'b0001, 4'b1111, 1'b0);
        step_cycle("src0_ack",            1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("src1_req",            1'b0, 4'b0010, 4'b1111, 1'b0);
        step_cycle("src1_ack",            1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("src2_req",            1'b0, 4'b0100, 4'b1111, 1'b0);
        step_cycle("src2_ack",            1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("src3_req",            1'b0, 4'b1000, 4'b1111, 1'b0);
        step_cycle("src3_ack",            1'b0, 4'b0000, 4'b1111, 1'b1);

        // Fixed priority resolution and multiple simultaneous requests.
        step_cycle("multi_23_req",        1'b0, 4'b1100, 4'b1111, 1'b0);
        step_cycle("multi_23_ack",        1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("multi_23_ack2",       1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("multi_all_req",       1'b0, 4'b1111, 4'b1111, 1'b0);
        step_cycle("multi_all_ack0",      1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("multi_all_ack1",      1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("multi_all_ack2",      1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("multi_all_ack3",      1'b0, 4'b0000, 4'b1111, 1'b1);

        // Masking without clearing, then re-enabling.
        step_cycle("mask_req_hidden",     1'b0, 4'b0100, 4'b0000, 1'b0);
        step_cycle("mask_still_hidden",   1'b0, 4'b0000, 4'b0000, 1'b0);
        step_cycle("mask_reenable",       1'b0, 4'b0000, 4'b1111, 1'b0);
        step_cycle("mask_ack_visible",    1'b0, 4'b0000, 4'b1111, 1'b1);

        // Change enables while multiple sources are pending.
        step_cycle("enable_mix_req",      1'b0, 4'b1010, 4'b1111, 1'b0);
        step_cycle("enable_only_3",       1'b0, 4'b0000, 4'b1000, 1'b0);
        step_cycle("enable_only_1",       1'b0, 4'b0000, 4'b0010, 1'b0);
        step_cycle("enable_none",         1'b0, 4'b0000, 4'b0000, 1'b0);
        step_cycle("enable_all_again",    1'b0, 4'b0000, 4'b1111, 1'b0);
        step_cycle("enable_ack_1",        1'b0, 4'b0000, 4'b1111, 1'b1);
        step_cycle("enable_ack_3",        1'b0, 4'b0000, 4'b1111, 1'b1);

        // Acknowledge with no valid interrupt must have no effect.
        step_cycle("ack_without_valid",   1'b0, 4'b0000, 4'b1111, 1'b1);

        // Same-cycle acknowledge and re-request of the selected source.
        step_cycle("same_src_seed",       1'b0, 4'b0001, 4'b1111, 1'b0);
        step_cycle("same_src_ack_req",    1'b0, 4'b0001, 4'b1111, 1'b1);
        step_cycle("same_src_check",      1'b0, 4'b0000, 4'b1111, 1'b0);
        step_cycle("same_src_final_ack",  1'b0, 4'b0000, 4'b1111, 1'b1);

        // Same-cycle acknowledge of selected source plus request of a different source.
        step_cycle("diff_src_seed",       1'b0, 4'b0010, 4'b1111, 1'b0);
        step_cycle("diff_src_ack_req",    1'b0, 4'b1000, 4'b1111, 1'b1);
        step_cycle("diff_src_ack_final",  1'b0, 4'b0000, 4'b1111, 1'b1);

        // Spurious acknowledge must not block request capture.
        step_cycle("spur_ack_new_req",    1'b0, 4'b0100, 4'b1111, 1'b1);
        step_cycle("spur_ack_followup",   1'b0, 4'b0000, 4'b1111, 1'b0);
        step_cycle("spur_ack_clear",      1'b0, 4'b0000, 4'b1111, 1'b1);

        // Repeated request on an already pending source has no extra effect.
        step_cycle("repeat_req_seed",     1'b0, 4'b1000, 4'b1111, 1'b0);
        step_cycle("repeat_req_again",    1'b0, 4'b1000, 4'b1111, 1'b0);
        step_cycle("repeat_req_clear",    1'b0, 4'b0000, 4'b1111, 1'b1);

        // Reset in the middle of pending activity.
        step_cycle("midreset_seed",       1'b0, 4'b0110, 4'b1111, 1'b0);
        step_cycle("midreset_assert",     1'b1, 4'b0000, 4'b1111, 1'b0);
        step_cycle("midreset_release",    1'b0, 4'b0000, 4'b1111, 1'b0);

        // Randomized regression with the same golden model.
        for (i = 0; i < 250; i = i + 1) begin
            step_cycle("random", 1'b0, $urandom_range(0, 15), $urandom_range(0, 15), $urandom_range(0, 1));
        end

        $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
