`timescale 1ns / 1ps

module tb_popcount64_threshold;
    reg clk;
    reg rst;
    reg [63:0] data_in;
    reg [5:0] threshold;

    wire [6:0] count_out;
    wire threshold_exceeded;

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

    reg [6:0] exp_count_d0;
    reg [6:0] exp_count_d1;
    reg exp_flag_d0;
    reg exp_flag_d1;

    popcount64_threshold uut (
        .clk(clk),
        .rst(rst),
        .data_in(data_in),
        .threshold(threshold),
        .count_out(count_out),
        .threshold_exceeded(threshold_exceeded)
    );

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

    function [6:0] popcount64;
        input [63:0] value;
        integer bit_idx;
        begin
            popcount64 = 7'd0;
            for (bit_idx = 0; bit_idx < 64; bit_idx = bit_idx + 1)
                popcount64 = popcount64 + value[bit_idx];
        end
    endfunction

    task apply_cycle;
        input [255:0] tag;
        input next_rst;
        input [63:0] next_data;
        input [5:0] next_threshold;
        reg [6:0] next_count;
        reg next_flag;
        reg [6:0] exp_count_now;
        reg exp_flag_now;
        begin
            rst = next_rst;
            data_in = next_data;
            threshold = next_threshold;

            next_count = popcount64(next_data);
            next_flag = (next_count > {1'b0, next_threshold});

            if (next_rst) begin
                exp_count_now = 7'd0;
                exp_flag_now = 1'b0;
            end else begin
                exp_count_now = exp_count_d1;
                exp_flag_now = exp_flag_d1;
            end

            @(posedge clk);
            #1;

            tests_run = tests_run + 1;

            if (count_out !== exp_count_now) begin
                $display("ERROR [%s]: count_out mismatch. Expected %0d, got %0d",
                         tag, exp_count_now, count_out);
                errors = errors + 1;
            end

            if (threshold_exceeded !== exp_flag_now) begin
                $display("ERROR [%s]: threshold_exceeded mismatch. Expected %b, got %b",
                         tag, exp_flag_now, threshold_exceeded);
                errors = errors + 1;
            end

            if (next_rst) begin
                exp_count_d0 = 7'd0;
                exp_count_d1 = 7'd0;
                exp_flag_d0 = 1'b0;
                exp_flag_d1 = 1'b0;
            end else begin
                exp_count_d1 = exp_count_d0;
                exp_flag_d1 = exp_flag_d0;
                exp_count_d0 = next_count;
                exp_flag_d0 = next_flag;
            end
        end
    endtask

    initial begin
        rst = 1'b0;
        data_in = 64'b0;
        threshold = 6'b0;
        exp_count_d0 = 7'd0;
        exp_count_d1 = 7'd0;
        exp_flag_d0 = 1'b0;
        exp_flag_d1 = 1'b0;

        $display("===========================================");
        $display("  64-bit Popcount with Threshold Testbench");
        $display("===========================================");

        // Reset behavior.
        apply_cycle("reset_0", 1'b1, 64'h0, 6'd0);
        apply_cycle("reset_1", 1'b1, 64'hFFFF_FFFF_FFFF_FFFF, 6'd63);
        apply_cycle("post_reset_0", 1'b0, 64'h0, 6'd0);
        apply_cycle("post_reset_1", 1'b0, 64'h0, 6'd0);

        // Basic patterns.
        apply_cycle("all_zero",      1'b0, 64'h0000_0000_0000_0000, 6'd0);
        apply_cycle("all_one",       1'b0, 64'hFFFF_FFFF_FFFF_FFFF, 6'd63);
        apply_cycle("single_bit_lsb",1'b0, 64'h0000_0000_0000_0001, 6'd0);
        apply_cycle("single_bit_msb",1'b0, 64'h8000_0000_0000_0000, 6'd1);
        apply_cycle("alternating_a", 1'b0, 64'hAAAA_AAAA_AAAA_AAAA, 6'd31);
        apply_cycle("alternating_5", 1'b0, 64'h5555_5555_5555_5555, 6'd32);
        apply_cycle("cluster_low",   1'b0, 64'h0000_0000_0000_FFFF, 6'd15);
        apply_cycle("cluster_high",  1'b0, 64'hFFFF_0000_0000_0000, 6'd16);

        // Threshold edge cases and strict comparison.
        apply_cycle("threshold_eq_0",   1'b0, 64'h0000_0000_0000_0000, 6'd0);
        apply_cycle("threshold_gt_0",   1'b0, 64'h0000_0000_0000_0001, 6'd0);
        apply_cycle("threshold_eq_31",  1'b0, 64'h7FFF_FFFF_0000_0000, 6'd31);
        apply_cycle("threshold_eq_32",  1'b0, 64'hFFFF_FFFF_0000_0000, 6'd32);
        apply_cycle("threshold_gt_32",  1'b0, 64'hFFFF_FFFF_0000_0001, 6'd32);
        apply_cycle("threshold_eq_63",  1'b0, 64'hFFFF_FFFF_FFFF_FFFE, 6'd63);
        apply_cycle("threshold_gt_63",  1'b0, 64'hFFFF_FFFF_FFFF_FFFF, 6'd63);

        // Back-to-back traffic.
        for (i = 0; i < 40; i = i + 1) begin
            apply_cycle("streaming", 1'b0,
                        {$random, $random},
                        i % 64);
        end

        // Reset during activity.
        apply_cycle("pre_reset_inflight_0", 1'b0, 64'h0123_4567_89AB_CDEF, 6'd20);
        apply_cycle("pre_reset_inflight_1", 1'b0, 64'hFEDC_BA98_7654_3210, 6'd20);
        apply_cycle("mid_reset",           1'b1, 64'hFFFF_0000_FFFF_0000, 6'd31);
        apply_cycle("after_reset_fill_0",  1'b0, 64'h1111_2222_3333_4444, 6'd16);
        apply_cycle("after_reset_fill_1",  1'b0, 64'h0000_0000_0000_0000, 6'd0);
        apply_cycle("after_reset_fill_2",  1'b0, 64'hFFFF_FFFF_FFFF_FFFF, 6'd10);

        // Randomized regression.
        for (i = 0; i < 200; i = i + 1) begin
            apply_cycle("random", 1'b0, {$random, $random}, $urandom_range(0, 63));
        end

        // Drain the pipeline.
        apply_cycle("drain_0", 1'b0, 64'h0, 6'd63);
        apply_cycle("drain_1", 1'b0, 64'h0, 6'd63);

        $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
