// tb_keccak_core_xsim.v - Self-checking testbench for keccak_core
//
// Tests keccak_core with a known 1600-bit all-zero input state.
// Runs 24 rounds and compares output with pre-computed expected value.
// Uses $error for mismatches.
//
// The expected output was pre-computed using a Python reference
// implementation of Keccak-f[1600] verified against hashlib SHA3.
//
// Usage:
//   xvlog -sv keccak_round.v keccak_core.v tb_keccak_core_xsim.v
//   xelab tb_keccak_core_xsim -s keccak_sim
//   xsim keccak_sim -R

`timescale 1ns / 1ps

module tb_keccak_core_xsim;

    // ================================================================
    // DUT signals
    // ================================================================
    reg              clk;
    reg              rst_n;
    reg  [1599:0]    state_i;
    reg              valid_i;
    wire             ready_o;
    wire [1599:0]    state_o;
    wire             valid_o;
    reg              ready_i;

    // ================================================================
    // DUT instantiation (ROUNDS=24, the default)
    // ================================================================
    keccak_core #(.ROUNDS(24)) u_keccak (
        .clk      (clk),
        .rst_n    (rst_n),
        .state_i  (state_i),
        .valid_i  (valid_i),
        .ready_o  (ready_o),
        .state_o  (state_o),
        .valid_o  (valid_o),
        .ready_i  (ready_i)
    );

    // ================================================================
    // Clock generation: 100 MHz (10 ns period)
    // ================================================================
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ================================================================
    // Expected output state for all-zero input
    //
    // Keccak-f[1600](0) after 24 rounds, flat 1600-bit output.
    // Verilog packing: {lane24, lane23, ..., lane1, lane0}
    // where lane[i] = A[i%5][i/5] at bits [i*64+63 : i*64].
    // lane0 = 0xf1258f7940e1dde7 is the well-known FIPS 202 test value;
    // independently recomputed and confirmed against the RTL output.
    // ================================================================
    parameter [1599:0] EXPECTED_STATE = 1600'heaf1ff7b5ceca24975f644e97f30a13b16f53526e70465c21841f924a2c509e4940c7922ae3a26148c3ee88a1ccf32c8b87c5a554fd00ecb613670957bc4661164befef28cc970f205e5635a21d9ae6101f22f1a11a5569f43b831cd0347c82681a57c16dbcf555fa9a6e6260d712103eb5aa93f2317d63530935ab7d08ffc64ad30a6f71b19059c8c5bda0cd6192e7690fee5a0a44647c4ff97a42d7f8e6fd48b284e056253d057bd1547306f80494dd598261ea65aa9ee84d5ccf933c0478af1258f7940e1dde7;

    // ================================================================
    // Test sequence
    // ================================================================
    reg [1599:0] captured_state;
    integer      error_count;
    integer      cycle_count;
    parameter    TIMEOUT = 200;

    initial begin
        error_count = 0;

        $display("========================================");
        $display(" Keccak Core Self-Checking Testbench");
        $display(" Input: 1600-bit all-zero state");
        $display(" Rounds: 24");
        $display("========================================");

        // Initialize
        state_i = 1600'd0;
        valid_i = 1'b0;
        ready_i = 1'b1;  // always accept output

        // Reset: rst_n low for 3 cycles
        rst_n = 1'b0;
        repeat (3) @(posedge clk);
        rst_n = 1'b1;
        @(posedge clk);

        $display("INFO: Reset complete. Starting permutation...");

        // Check ready_o is high
        if (!ready_o) begin
            $error("ERROR: ready_o not asserted after reset");
            error_count = error_count + 1;
        end

        // Drive input
        state_i = 1600'd0;
        valid_i = 1'b1;
        @(posedge clk);
        valid_i = 1'b0;

        $display("INFO: Input driven. Waiting for valid_o...");

        // Wait for valid_o (24 cycles + latency)
        cycle_count = 0;
        while (!valid_o && cycle_count < TIMEOUT) begin
            @(posedge clk);
            cycle_count = cycle_count + 1;
        end

        if (cycle_count >= TIMEOUT) begin
            $error("TIMEOUT: valid_o not asserted within %0d cycles", TIMEOUT);
            error_count = error_count + 1;
        end else begin
            captured_state = state_o;

            $display("INFO: valid_o asserted after %0d cycles", cycle_count + 1);

            // Verify: check the first and last lanes
            $display("INFO: state_o[63:0]      = 64'h%0h", captured_state[63:0]);
            $display("INFO: state_o[1599:1536] = 64'h%0h", captured_state[1599:1536]);

            // Full 1600-bit comparison
            if (captured_state !== EXPECTED_STATE) begin
                $error("MISMATCH! Output state differs from expected.");

                // Find first mismatching lane
                begin
                    integer lane_idx;
                    reg [63:0] exp_lane, got_lane;
                    for (lane_idx = 0; lane_idx < 25; lane_idx = lane_idx + 1) begin
                        exp_lane = EXPECTED_STATE[(lane_idx*64)+:64];
                        got_lane = captured_state[(lane_idx*64)+:64];
                        if (exp_lane !== got_lane) begin
                            $display("  Lane %0d mismatch:", lane_idx);
                            $display("    Expected: 64'h%0h", exp_lane);
                            $display("    Got:      64'h%0h", got_lane);
                        end
                    end
                end

                error_count = error_count + 1;
            end else begin
                $display("PASS: state_o matches expected Keccak-f[1600](0) output.");
                $display("      All 25 lanes verified correctly.");
            end
        end

        // ============================================================
        // Test 2: Check that valid_o goes low after handshake
        // ============================================================
        if (valid_o) begin
            @(posedge clk);
            // After one cycle with ready_i=1, valid_o should go low
            // (keccak_core transitions back to idle)
            if (valid_o) begin
                $display("NOTE: valid_o still high after handshake (core may need extra cycle)");
            end
        end

        // ============================================================
        // Summary
        // ============================================================
        $display("========================================");
        if (error_count == 0) begin
            $display("ALL TESTS PASSED");
        end else begin
            $display("TESTS FAILED: %0d error(s)", error_count);
        end
        $display("========================================");

        $finish;
    end

    // ================================================================
    // Timeout watchdog
    // ================================================================
    initial begin
        #(TIMEOUT * 10 * 10);
        $display("FATAL: Global simulation timeout");
        $finish;
    end

endmodule
