#!/usr/bin/env python3
"""gen_vectors.py - Generate test vectors for poly_arith_sync XSIM testbench.

Produces sync_rtl/poly_arith/TB/vectors/poly_arith_input.hex
Each line is a 40-bit hex value (10 hex chars, no spaces):
  bits[39:28] = expected[11:0]
  bits[27:16] = coeff_b_in[11:0]
  bits[15:4]  = coeff_a_in[11:0]
  bit[3]      = mode (0=add, 1=sub)
  bits[2:0]   = padding (0)

expected = (coeff_a +/- coeff_b) mod Q,  Q = 3329

Edge cases covered:
  - zeros
  - max values (Q-1 = 3328)
  - mid-range
  - overflow/underflow (a + b >= Q, a - b < 0)
"""

import os
import sys

Q = 3329
Q_MINUS_1 = Q - 1  # 3328

# Output path (relative to project root, where script should be run)
OUTPUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vectors")
OUTPUT_FILE = os.path.join(OUTPUT_DIR, "poly_arith_input.hex")


def mod_add(a: int, b: int) -> int:
    """(a + b) mod Q."""
    return (a + b) % Q


def mod_sub(a: int, b: int) -> int:
    """(a - b) mod Q."""
    return (a - b) % Q


def pack_vector(expected: int, coeff_b: int, coeff_a: int, mode: int) -> int:
    """Pack a single test vector into a 40-bit value."""
    val = 0
    val |= (expected & 0xFFF) << 28   # bits 39:28
    val |= (coeff_b  & 0xFFF) << 16   # bits 27:16
    val |= (coeff_a  & 0xFFF) << 4    # bits 15:4
    val |= (mode     & 0x1)   << 3    # bit  3
    # bits 2:0 are padding = 0
    return val


def generate() -> list[int]:
    """Generate all test vectors. Returns list of packed 40-bit values."""
    vectors: list[int] = []

    # Helper to add a vector
    def add_vector(a: int, b: int, mode: int) -> None:
        if mode == 0:
            exp = mod_add(a, b)
        else:
            exp = mod_sub(a, b)
        vectors.append(pack_vector(exp, b, a, mode))

    # ---- ADD mode vectors (mode=0) ----
    # Zero + zero
    add_vector(0, 0, 0)

    # Max + max = (3328 + 3328) % 3329 = 6656 % 3329 = 3327
    add_vector(Q_MINUS_1, Q_MINUS_1, 0)

    # Zero + max
    add_vector(0, Q_MINUS_1, 0)
    add_vector(Q_MINUS_1, 0, 0)

    # Mid-range values
    for a, b in [(1000, 2000), (1500, 1500), (1, 2), (42, 137)]:
        add_vector(a, b, 0)

    # Overflow: a + b > Q
    add_vector(2000, 2000, 0)  # 4000 % 3329 = 671
    add_vector(3000, 1000, 0)  # 4000 % 3329 = 671
    add_vector(3328, 1, 0)     # 3329 % 3329 = 0

    # Edge: Q-sized values that just fit
    add_vector(1664, 1665, 0)  # 3329 -> 0
    add_vector(1664, 1664, 0)  # 3328 -> 3328

    # ---- SUB mode vectors (mode=1) ----
    # Zero - zero
    add_vector(0, 0, 1)

    # Max - max
    add_vector(Q_MINUS_1, Q_MINUS_1, 1)

    # Zero - max: (0 - 3328) % 3329 = 1
    add_vector(0, Q_MINUS_1, 1)

    # Max - zero
    add_vector(Q_MINUS_1, 0, 1)

    # Mid-range values
    for a, b in [(2000, 1000), (1500, 1500), (2, 1), (137, 42)]:
        add_vector(a, b, 1)

    # Underflow: a < b -> negative result
    add_vector(1000, 2000, 1)  # (1000 - 2000) % 3329 = 2329
    add_vector(0, 1, 1)        # 3328
    add_vector(1, 3328, 1)     # (1 - 3328) % 3329 = 2
    add_vector(0, 2, 1)        # 3327

    # Edge cases
    add_vector(1, 3328, 1)     # (1 - 3328) % 3329 = 2

    # Run through all combinations of a few edge values
    for a in [0, 1, 1664, 3327, 3328]:
        for b in [0, 1, 1664, 3327, 3328]:
            add_vector(a, b, 0)
            add_vector(a, b, 1)

    return vectors


def write_vectors(vectors: list[int]) -> None:
    """Write vectors to hex file."""
    os.makedirs(OUTPUT_DIR, exist_ok=True)
    with open(OUTPUT_FILE, "w") as f:
        for v in vectors:
            # 40 bits = 10 hex digits
            f.write(f"{v:010X}\n")

    print(f"Generated {len(vectors)} test vectors -> {OUTPUT_FILE}")


def main() -> int:
    vectors = generate()
    write_vectors(vectors)

    # Print a few samples for debugging
    print("\nSample vectors (first 5):")
    for i, v in enumerate(vectors[:5]):
        exp    = (v >> 28) & 0xFFF
        coeff_b = (v >> 16) & 0xFFF
        coeff_a = (v >> 4)  & 0xFFF
        mode   = (v >> 3)  & 0x1
        op = "ADD" if mode == 0 else "SUB"
        print(f"  [{i}] a={coeff_a:04d} b={coeff_b:04d} mode={op} expected={exp:04d}")

    return 0


if __name__ == "__main__":
    sys.exit(main())
