`timescale 1ns / 1ps

// Latency-agnostic testbench for 8-tap FIR filter
// Supports any pipeline depth, requires single-cycle throughput
module tb_fir_8tap;
    // Clock and Reset
    reg clk;
    reg rst;
    
    // Interface signals
    reg signed [7:0] sample_in;
    reg in_valid;
    wire signed [15:0] sample_out;
    wire out_valid;
    
    // Test control
    integer errors = 0;
    integer total_sent = 0;
    integer total_received = 0;
    parameter MAX_TIMEOUT = 500;
    parameter QUEUE_SIZE = 64;
    
    // FIR coefficients (symmetric: H0=H7, H1=H6, H2=H5, H3=H4)
    reg signed [7:0] h [0:7];
    
    // Expected output queue
    reg signed [15:0] expected_queue [0:QUEUE_SIZE-1];
    integer queue_head = 0;
    integer queue_tail = 0;
    
    // Sample history for golden model
    reg signed [7:0] x_history [0:7];
    
    // Test samples
    reg signed [7:0] test_samples [0:63];
    integer num_samples;
    
    integer i, j;
    integer timeout_count;
    
    // Instantiate UUT
    fir_8tap uut (
        .clk(clk),
        .rst(rst),
        .sample_in(sample_in),
        .in_valid(in_valid),
        .sample_out(sample_out),
        .out_valid(out_valid)
    );
    
    // Clock Generation (100MHz for simulation)
    initial begin
        clk = 0;
        forever #5 clk = ~clk;
    end
    
    // Initialize coefficients (symmetric)
    task init_coefficients;
        begin
            h[0] = 8'sd8;
            h[1] = 8'sd21;
            h[2] = 8'sd45;
            h[3] = 8'sd54;
            h[4] = 8'sd54;  // h[4] = h[3]
            h[5] = 8'sd45;  // h[5] = h[2]
            h[6] = 8'sd21;  // h[6] = h[1]
            h[7] = 8'sd8;   // h[7] = h[0]
        end
    endtask
    
    // Clear golden model state
    task clear_state;
        begin
            for (j = 0; j <= 7; j = j + 1)
                x_history[j] = 0;
            queue_head = 0;
            queue_tail = 0;
            total_sent = 0;
            total_received = 0;
        end
    endtask
    
    // Compute expected output for a new sample and add to queue
    task push_expected;
        input signed [7:0] new_sample;
        reg signed [15:0] acc;
        begin
            // Shift history
            for (j = 7; j > 0; j = j - 1)
                x_history[j] = x_history[j-1];
            x_history[0] = new_sample;
            
            // Compute FIR output: y = sum(h[k] * x[k])
            acc = 0;
            for (j = 0; j <= 7; j = j + 1)
                acc = acc + h[j] * x_history[j];
            
            // Push to expected queue
            expected_queue[queue_tail] = acc;
            queue_tail = (queue_tail + 1) % QUEUE_SIZE;
        end
    endtask
    
    // Check output against queue
    task check_output;
        begin
            if (queue_head == queue_tail) begin
                $display("ERROR: Received unexpected output (queue empty)!");
                errors = errors + 1;
            end else begin
                if (sample_out !== expected_queue[queue_head]) begin
                    $display("ERROR: Output %0d: Expected %0d, got %0d", 
                             total_received, expected_queue[queue_head], sample_out);
                    errors = errors + 1;
                end
                queue_head = (queue_head + 1) % QUEUE_SIZE;
                total_received = total_received + 1;
            end
        end
    endtask
    
    // Run test with given samples
    task run_test;
        input [255:0] test_name;
        integer send_idx;
        integer timeout;
        integer pending;
        begin
            $display("\n[%s] Sending %0d samples", test_name, num_samples);
            
            // Apply reset and clear state
            rst = 1;
            in_valid = 0;
            repeat(3) @(posedge clk);
            rst = 0;
            repeat(2) @(posedge clk);
            clear_state;
            
            send_idx = 0;
            timeout = 0;
            sample_in = test_samples[0];
            in_valid = 1; // Start
            
            while (send_idx < num_samples && timeout < MAX_TIMEOUT) begin
                @(posedge clk);
                
                // Handle input handshake (Valid-Only, always ready)
                if (in_valid) begin
                    push_expected(test_samples[send_idx]);
                    total_sent = total_sent + 1;
                    send_idx = send_idx + 1;
                    
                    if (send_idx < num_samples) begin
                        sample_in <= test_samples[send_idx];
                        // in_valid <= 1; // Keep high
                    end else begin
                        in_valid <= 0;
                    end
                end
                
                // Check for outputs
                if (out_valid) begin
                    check_output;
                    timeout = 0;
                end else begin
                    timeout = timeout + 1;
                end
            end
            
            if (timeout >= MAX_TIMEOUT) begin
                $display("ERROR: Timeout during input phase after %0d samples", send_idx);
                errors = errors + 1;
            end
            
            // Drain remaining outputs
            timeout = 0;
            pending = (queue_tail >= queue_head) ? (queue_tail - queue_head) : (QUEUE_SIZE - queue_head + queue_tail);
            while (pending > 0 && timeout < MAX_TIMEOUT) begin
                @(posedge clk);
                if (out_valid) begin
                    check_output;
                    timeout = 0;
                    pending = (queue_tail >= queue_head) ? (queue_tail - queue_head) : (QUEUE_SIZE - queue_head + queue_tail);
                end else begin
                    timeout = timeout + 1;
                end
            end
            
            if (pending > 0) begin
                $display("ERROR: Timeout waiting for %0d remaining outputs", pending);
                errors = errors + pending;
            end
            
            $display("[%s] Sent: %0d, Received: %0d", test_name, total_sent, total_received);
        end
    endtask
    
    // Test Procedure
    initial begin
        // Initialize
        rst = 1;
        in_valid = 0;
        sample_in = 0;
        init_coefficients;
        
        $display("--- Starting Test for fir_8tap ---");
        $display("8-tap symmetric FIR filter (performance)");
        $display("Latency-agnostic verification");
        $display("-----------------------------------");
        
        // --- Test 1: Impulse response ---
        num_samples = 15;
        for (i = 0; i < 15; i = i + 1)
            test_samples[i] = (i == 0) ? 8'sd1 : 8'sd0;
        run_test("Impulse");
        
        // --- Test 2: Step response ---
        num_samples = 20;
        for (i = 0; i < 20; i = i + 1)
            test_samples[i] = 8'sd50;
        run_test("Step");
        
        // --- Test 3: Alternating ---
        num_samples = 20;
        for (i = 0; i < 20; i = i + 1)
            test_samples[i] = (i % 2 == 0) ? 8'sd30 : -8'sd30;
        run_test("Alternating");
        
        // --- Test 4: Ramp ---
        num_samples = 20;
        for (i = 0; i < 20; i = i + 1)
            test_samples[i] = i * 5;
        run_test("Ramp");
        
        // --- Test 5: Max values (stress test) ---
        num_samples = 15;
        for (i = 0; i < 15; i = i + 1)
            test_samples[i] = (i % 2 == 0) ? 8'sd127 : -8'sd128;
        run_test("MaxValues");
        
        // --- Final Results ---
        $display("\n-----------------------------------");
        if (errors == 0) begin
            $display("TEST_RESULT: PASS");
        end else begin
            $display("TEST_RESULT: FAIL (%0d errors)", errors);
        end
        
        $finish;
    end

endmodule
