#!/usr/bin/env python3
"""
Generate golden test vectors for Key-Value Store testbench.
Implements the exact behavior specified: GET, PUT with round-robin replacement.
"""

class KVStore:
    def __init__(self):
        self.entries = [None] * 8  # Each entry is (key, value) or None
        self.next_slot = 0  # Round-robin pointer for replacement
        
    def reset(self):
        self.entries = [None] * 8
        self.next_slot = 0
        
    def get(self, key):
        """GET operation: returns (value, hit)"""
        for i, entry in enumerate(self.entries):
            if entry is not None and entry[0] == key:
                return (entry[1], 1)  # value, hit=1
        return (0, 0)  # not found, hit=0
        
    def put(self, key, value):
        """PUT operation: returns hit (always 1 for PUT)"""
        # Check if key already exists -> update
        for i, entry in enumerate(self.entries):
            if entry is not None and entry[0] == key:
                self.entries[i] = (key, value)
                return 1
        # Find first empty slot
        for i, entry in enumerate(self.entries):
            if entry is None:
                self.entries[i] = (key, value)
                return 1
        # Store is full: round-robin replacement
        self.entries[self.next_slot] = (key, value)
        self.next_slot = (self.next_slot + 1) % 8
        return 1

def generate_test_sequence():
    """Generate a sequence of operations and expected results."""
    store = KVStore()
    tests = []
    
    # Test 1: GET on empty store (miss)
    tests.append({'op': 0, 'key': 0x10, 'value_in': 0, 'desc': "GET empty",
                  'exp_value': 0, 'exp_hit': 0})
    
    # Test 2-5: PUT 4 entries
    for i, (k, v) in enumerate([(0x10, 0x1234), (0x20, 0x5678), (0x30, 0xABCD), (0x40, 0xEF01)]):
        store.put(k, v)
        tests.append({'op': 1, 'key': k, 'value_in': v, 'desc': f"PUT key={k:#x}",
                      'exp_value': 0, 'exp_hit': 1})
    
    # Test 6-9: GET those 4 entries (hit)
    for k, expected_v in [(0x10, 0x1234), (0x20, 0x5678), (0x30, 0xABCD), (0x40, 0xEF01)]:
        val, hit = store.get(k)
        tests.append({'op': 0, 'key': k, 'value_in': 0, 'desc': f"GET key={k:#x}",
                      'exp_value': expected_v, 'exp_hit': 1})
    
    # Test 10: GET non-existent key (miss)
    val, hit = store.get(0xFF)
    tests.append({'op': 0, 'key': 0xFF, 'value_in': 0, 'desc': "GET non-existent",
                  'exp_value': 0, 'exp_hit': 0})
    
    # Test 11: Update existing key
    store.put(0x10, 0x9999)
    tests.append({'op': 1, 'key': 0x10, 'value_in': 0x9999, 'desc': "PUT update key=0x10",
                  'exp_value': 0, 'exp_hit': 1})
    
    # Test 12: GET updated key
    val, hit = store.get(0x10)
    tests.append({'op': 0, 'key': 0x10, 'value_in': 0, 'desc': "GET updated key=0x10",
                  'exp_value': 0x9999, 'exp_hit': 1})
    
    # Test 13-16: Fill remaining slots (5, 6, 7, 8 -> slots 4, 5, 6, 7)
    for i, (k, v) in enumerate([(0x50, 0x1111), (0x60, 0x2222), (0x70, 0x3333), (0x80, 0x4444)]):
        store.put(k, v)
        tests.append({'op': 1, 'key': k, 'value_in': v, 'desc': f"PUT fill key={k:#x}",
                      'exp_value': 0, 'exp_hit': 1})
    
    # Store is now full (8 entries)
    # Test 17: PUT new key -> should replace slot 0 (round-robin)
    store.put(0x90, 0x5555)
    tests.append({'op': 1, 'key': 0x90, 'value_in': 0x5555, 'desc': "PUT replace slot 0",
                  'exp_value': 0, 'exp_hit': 1})
    
    # Test 18: GET old key that was in slot 0 (should miss now)
    val, hit = store.get(0x10)  # This was the first PUT, should be evicted
    tests.append({'op': 0, 'key': 0x10, 'value_in': 0, 'desc': "GET evicted key=0x10",
                  'exp_value': 0, 'exp_hit': 0})
    
    # Test 19: GET new key that replaced it
    val, hit = store.get(0x90)
    tests.append({'op': 0, 'key': 0x90, 'value_in': 0, 'desc': "GET new key=0x90",
                  'exp_value': 0x5555, 'exp_hit': 1})
    
    # Test 20: PUT another new key -> should replace slot 1
    store.put(0xA0, 0x6666)
    tests.append({'op': 1, 'key': 0xA0, 'value_in': 0x6666, 'desc': "PUT replace slot 1",
                  'exp_value': 0, 'exp_hit': 1})
    
    # Test 21: GET key that was in slot 1 (should miss)
    val, hit = store.get(0x20)
    tests.append({'op': 0, 'key': 0x20, 'value_in': 0, 'desc': "GET evicted key=0x20",
                  'exp_value': 0, 'exp_hit': 0})
    
    # Test 22: Verify remaining keys still exist
    for k in [0x30, 0x40, 0x50, 0x60]:
        val, hit = store.get(k)
        tests.append({'op': 0, 'key': k, 'value_in': 0, 'desc': f"GET surviving key={k:#x}",
                      'exp_value': val, 'exp_hit': hit})
    
    return tests

def main():
    tests = generate_test_sequence()
    
    print("// ==============================================")
    print("// Key-Value Store Golden Test Vectors")
    print("// Generated by: python3 generate_golden.py")
    print("// ==============================================")
    print()
    print(f"localparam NUM_TESTS = {len(tests)};")
    print()
    print(f"reg        tb_op      [0:{len(tests)-1}];")
    print(f"reg [7:0]  tb_key     [0:{len(tests)-1}];")
    print(f"reg [15:0] tb_value_in[0:{len(tests)-1}];")
    print(f"reg [15:0] exp_value  [0:{len(tests)-1}];")
    print(f"reg        exp_hit    [0:{len(tests)-1}];")
    print()
    print("initial begin")
    
    for i, t in enumerate(tests):
        print(f"    // Test {i}: {t['desc']}")
        print(f"    tb_op[{i}]       = 1'b{t['op']};")
        print(f"    tb_key[{i}]      = 8'h{t['key']:02x};")
        print(f"    tb_value_in[{i}] = 16'h{t['value_in']:04x};")
        print(f"    exp_value[{i}]   = 16'h{t['exp_value']:04x};")
        print(f"    exp_hit[{i}]     = 1'b{t['exp_hit']};")
        print()
        
    print("end")

if __name__ == "__main__":
    main()
