`timescale 1ns/1ps
module tb_fft_demo;
    reg clk = 0;
    always #10 clk = ~clk;
    reg resetn = 0;
    reg [31:0] control = 0;
    wire [31:0] report_data;
    wire led_n;
    reg [31:0] value, iq_word, power_word;
    reg start_toggle = 0;
    reg case_select = 0;
    integer fd, frame, k, padding_count = 0, stall_cycles;
    reg [31:0] held_input;
    reg held_last, was_stalled;
    fft_demo #(.SIM_OUTPUT_STALLS(1)) dut(clk, resetn, control, report_data, led_n);

    task read_reg;
        input integer selector;
        input integer index;
        output [31:0] result;
        begin
            @(negedge clk); control = {start_toggle, case_select, 10'b0, selector[3:0], 7'b0, index[8:0]};
            repeat (4) @(posedge clk);
            #1; result = report_data;
        end
    endtask
    // AXI producer must keep its payload/last unchanged while stalled.
    always @(posedge clk) begin
        if (!resetn) begin was_stalled <= 0; stall_cycles <= 0; end
        else begin
            if (was_stalled && ((dut.state != 2) || dut.source_data !== held_input || (dut.sample_index == 511) !== held_last))
                $fatal(1, "Input payload changed before handshake");
            was_stalled <= (dut.state == 2 && !dut.input_ready);
            held_input <= dut.source_data;
            held_last <= (dut.sample_index == 511);
            if (dut.output_valid && !dut.output_ready) stall_cycles <= stall_cycles + 1;
            if (dut.state == 2 && dut.input_ready && dut.sample_index >= 500) begin
                if (dut.source_data !== 32'b0) $fatal(1, "Padding must be exactly zero");
                padding_count <= padding_count + 1;
            end
        end
    end
    initial begin
        fd = $fopen("fft_sim.csv", "w");
        if (fd == 0) $fatal(1, "Cannot open simulation results");
        repeat (8) @(negedge clk);
        resetn = 1;
        for (frame = 1; frame <= 2; frame = frame + 1) begin
            wait(dut.state == 4);
            repeat (8) @(posedge clk);
            if (dut.errors != 0 || dut.input_count != 512 || dut.output_count != 512 || dut.peak_index != (frame == 1 ? 34 : 82))
                $fatal(1, "Frame failed: errors=%h input=%d output=%d peak=%d", dut.errors, dut.input_count, dut.output_count, dut.peak_index);
            if (dut.peak_power < 59000000 || dut.peak_power > 65000000)
                $fatal(1, "Unexpected scale: peak power=%d", dut.peak_power);
            if (padding_count != frame * 12 || dut.case_id != frame - 1)
                $fatal(1, "Padding count or ROM bank mismatch");
            for (k = 0; k < 512; k = k + 1) begin
                read_reg(6, k, iq_word);
                read_reg(7, k, power_word);
                $fdisplay(fd, "%0d,%0d,%0d,%0d,%0d", frame, k, $signed(iq_word[15:0]), $signed(iq_word[31:16]), power_word);
            end
            if (frame == 1) begin
                @(negedge clk); start_toggle = ~start_toggle;
                case_select = 1;
                control[30] = case_select;
                control[31] = start_toggle;
                wait(dut.state != 4);
            end
        end
        if (stall_cycles == 0) $fatal(1, "No output backpressure exercised");
        if (dut.frame_count != 2) $fatal(1, "Frame restart failed");
        $fclose(fd);
        $display("FFT_SIM_PASS: peaks=34/82, padding=12/frame, backpressure cycles=%0d", stall_cycles);
        $finish;
    end
    initial begin #10000000; $fatal(1, "FFT simulation timed out"); end
endmodule
