`timescale 1ns / 1ps

module tb_round_robin_arbiter;
    reg clk;
    reg rst;
    reg [7:0] req;
    reg grant_ready;

    wire grant_valid;
    wire [2:0] grant_idx;

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

    reg [2:0] golden_start;
    reg expected_valid;
    reg [2:0] expected_idx;

    round_robin_arbiter uut (
        .clk(clk),
        .rst(rst),
        .req(req),
        .grant_ready(grant_ready),
        .grant_valid(grant_valid),
        .grant_idx(grant_idx)
    );

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

    task compute_selected;
        input [7:0] req_bits;
        input [2:0] start_idx;
        output sel_valid;
        output [2:0] sel_idx;
        integer offset;
        reg found_local;
        reg [2:0] candidate_local;
        begin
            sel_valid = 1'b0;
            sel_idx = 3'd0;
            found_local = 1'b0;
            candidate_local = 3'd0;

            for (offset = 0; offset < 8; offset = offset + 1) begin
                candidate_local = start_idx + offset;
                if (!found_local && req_bits[candidate_local]) begin
                    sel_valid = 1'b1;
                    sel_idx = candidate_local;
                    found_local = 1'b1;
                end
            end
        end
    endtask

    task step_cycle;
        input [255:0] tag;
        input next_rst;
        input [7:0] next_req;
        input next_grant_ready;
        reg pre_valid;
        reg [2:0] pre_idx;
        reg post_valid;
        reg [2:0] post_idx;
        reg [2:0] next_start;
        begin
            rst = next_rst;
            req = next_req;
            grant_ready = next_grant_ready;

            if (next_rst) begin
                pre_valid = 1'b0;
                pre_idx = 3'd0;
                next_start = 3'd0;
                post_valid = 1'b0;
                post_idx = 3'd0;
            end else begin
                compute_selected(next_req, golden_start, pre_valid, pre_idx);

                if (next_grant_ready && pre_valid) begin
                    next_start = pre_idx + 3'd1;
                end else begin
                    next_start = golden_start;
                end

                compute_selected(next_req, next_start, post_valid, post_idx);
            end

            @(posedge clk);
            #1;

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

            if (grant_valid !== post_valid) begin
                $display("ERROR [%s cycle %0d]: grant_valid mismatch. rst=%b req=%b ready=%b start_before=%0d expected=%b got=%b",
                         tag, cycle_count, next_rst, next_req, next_grant_ready,
                         golden_start, post_valid, grant_valid);
                errors = errors + 1;
            end

            if (post_valid && (grant_idx !== post_idx)) begin
                $display("ERROR [%s cycle %0d]: grant_idx mismatch. rst=%b req=%b ready=%b start_before=%0d expected=%0d got=%0d",
                         tag, cycle_count, next_rst, next_req, next_grant_ready,
                         golden_start, post_idx, grant_idx);
                errors = errors + 1;
            end

            golden_start = next_start;
            expected_valid = post_valid;
            expected_idx = post_idx;
        end
    endtask

    initial begin
        rst = 1'b0;
        req = 8'b00000000;
        grant_ready = 1'b0;
        golden_start = 3'd0;
        expected_valid = 1'b0;
        expected_idx = 3'd0;

        $display("===========================================");
        $display("      8-Input Round-Robin Arbiter TB");
        $display("===========================================");

        // Reset and idle behaviour.
        step_cycle("reset_clear",         1'b1, 8'b00000000, 1'b0);
        step_cycle("reset_with_req",      1'b1, 8'b11111111, 1'b1);
        step_cycle("idle",                1'b0, 8'b00000000, 1'b1);

        // All requesters active: verify full rotation and wrap.
        step_cycle("all_req_0",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_1",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_2",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_3",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_4",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_5",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_6",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_7",           1'b0, 8'b11111111, 1'b1);
        step_cycle("all_req_wrap",        1'b0, 8'b11111111, 1'b1);

        // Sparse request patterns and wrap-around selection.
        step_cycle("sparse_seed",         1'b0, 8'b10010001, 1'b1);
        step_cycle("sparse_next",         1'b0, 8'b10010001, 1'b1);
        step_cycle("sparse_wrap",         1'b0, 8'b10010001, 1'b1);
        step_cycle("sparse_hold",         1'b0, 8'b10010001, 1'b0);
        step_cycle("sparse_resume",       1'b0, 8'b10010001, 1'b1);

        // Start near the end of the ring and verify wrap to low IDs.
        step_cycle("boundary_seed_6",     1'b0, 8'b01000000, 1'b1);
        step_cycle("boundary_seed_7",     1'b0, 8'b10000000, 1'b1);
        step_cycle("boundary_mix_0",      1'b0, 8'b10000011, 1'b1);
        step_cycle("boundary_mix_1",      1'b0, 8'b10000011, 1'b1);
        step_cycle("boundary_mix_2",      1'b0, 8'b10000011, 1'b1);

        // grant_ready low must stall both acceptance and pointer advance.
        step_cycle("stall_seed",          1'b0, 8'b00110100, 1'b0);
        step_cycle("stall_hold",          1'b0, 8'b00110100, 1'b0);
        step_cycle("stall_release",       1'b0, 8'b00110100, 1'b1);
        step_cycle("stall_followup",      1'b0, 8'b00110100, 1'b1);

        // Single persistent requester.
        step_cycle("single_5_a",          1'b0, 8'b00100000, 1'b1);
        step_cycle("single_5_b",          1'b0, 8'b00100000, 1'b1);
        step_cycle("single_5_c",          1'b0, 8'b00100000, 1'b1);

        // Changing request patterns without acceptance.
        step_cycle("change_noacc_0",      1'b0, 8'b00001100, 1'b0);
        step_cycle("change_noacc_1",      1'b0, 8'b11000000, 1'b0);
        step_cycle("change_accept",       1'b0, 8'b11000000, 1'b1);

        // Mid-stream reset should clear the rotation state.
        step_cycle("midreset_seed",       1'b0, 8'b00111100, 1'b1);
        step_cycle("midreset_assert",     1'b1, 8'b00111100, 1'b1);
        step_cycle("midreset_release",    1'b0, 8'b00000001, 1'b1);

        // Randomized regression.
        for (i = 0; i < 400; i = i + 1) begin
            step_cycle("random", $urandom_range(0, 15) == 0, $urandom_range(0, 255), $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
