Added full design created with Claude

This commit is contained in:
Dennis Brentjes
2026-06-13 18:35:38 +02:00
parent 57b5b471b8
commit 8d0ab1d948
30 changed files with 7424 additions and 395 deletions
+222
View File
@@ -0,0 +1,222 @@
"""EEPROM model — exi domain.
Emulates the MX98730EC's 93C46 serial EEPROM.
93C46 protocol (Microwire, bit-bang)
-------------------------------------
CS=1 activates the device.
Data clocked on rising SK edge, 9-bit header then data:
Bit 0: start (always 1)
Bit 1: opcode MSB } READ = 10
Bit 2: opcode LSB }
Bits 38: 6-bit address (MSB first)
After the 9th rising SK the DO line presents the MSB of the 16-bit word.
Each subsequent rising SK advances one bit (MSB→LSB).
Shift register `shift_in` convention
--------------------------------------
`Cat(di_s, shift_in[:-1])` places di_s at bit 0 and shifts existing bits up.
After N edges:
shift_in[N-1] = first bit received (start)
shift_in[0] = last bit received so far
At bit_ctr==8 (after 8 edges, receiving 9th on di_s):
shift_in[7] = start (bit 0)
shift_in[6] = opcode MSB (bit 1)
shift_in[5] = opcode LSB (bit 2)
shift_in[4:0] = addr[5:1] (bits 37, MSB first→LSB first in register)
di_s = addr[0] (bit 8)
opcode = Cat(shift_in[5], shift_in[6]) → 0b10 = READ
address = Cat(di_s, shift_in[0:5]) → addr[0..5]
EEPROM content (64 × 16-bit words)
-------------------------------------
Words 02 hold the source MAC address (Nintendo OUI 00:09:BF:AA:BB:CC).
The GC BBA driver reads words 03 then copies to PAR05.
"""
from amaranth import *
from amaranth.lib.cdc import FFSynchronizer
__all__ = ["EEPROMModel"]
_EEPROM_WORDS = [
0x0009, # word 0: PAR0=0x00, PAR1=0x09
0xBFAA, # word 1: PAR2=0xBF, PAR3=0xAA
0xBBCC, # word 2: PAR4=0xBB, PAR5=0xCC
0x0000, # word 3: checksum placeholder
]
_EEPROM_WORDS += [0x0000] * (64 - len(_EEPROM_WORDS))
_OP_READ = 0b10 # opcode for READ
class EEPROMModel(Elaboratable):
"""93C46 serial EEPROM model in the exi domain (read-only).
Ports
-----
sk / cs / di : bit-bang inputs (raw async; synchronized internally)
do : serial data output
"""
def __init__(self):
self.sk = Signal()
self.cs = Signal()
self.di = Signal()
self.do = Signal()
def elaborate(self, platform):
m = Module()
words = Array([Signal(16, init=v, name=f"e{i}") for i, v in enumerate(_EEPROM_WORDS)])
# ── Input synchronization (async → exi, 2 stages) ────────────────
sk_s = Signal()
cs_s = Signal()
di_s = Signal()
m.submodules.sync_sk = FFSynchronizer(self.sk, sk_s, o_domain="exi")
m.submodules.sync_cs = FFSynchronizer(self.cs, cs_s, o_domain="exi")
m.submodules.sync_di = FFSynchronizer(self.di, di_s, o_domain="exi")
sk_prev = Signal()
m.d.exi += sk_prev.eq(sk_s)
rising_sk = Signal()
m.d.comb += rising_sk.eq(sk_s & ~sk_prev)
# ── State ─────────────────────────────────────────────────────────
shift_in = Signal(9)
bit_ctr = Signal(4) # 0..8 during header receive
shift_out = Signal(16) # data word being shifted out MSB-first
out_ctr = Signal(4) # 0..15, counts bits shifted out
in_read = Signal() # 1 while outputting a word
# DO is combinatorial: MSB of shift_out while in read-out phase
m.d.comb += self.do.eq(Mux(in_read, shift_out[15], 0))
with m.If(~cs_s):
m.d.exi += bit_ctr.eq(0)
m.d.exi += in_read.eq(0)
m.d.exi += out_ctr.eq(0)
with m.Elif(rising_sk):
with m.If(in_read):
# Shift out next bit (MSB first: left shift, zero into LSB)
m.d.exi += shift_out.eq(Cat(0, shift_out[:-1]))
with m.If(out_ctr == 15):
m.d.exi += in_read.eq(0)
m.d.exi += out_ctr.eq(0)
with m.Else():
m.d.exi += out_ctr.eq(out_ctr + 1)
with m.Else():
# Shift di_s in at bit 0 (existing bits move up)
m.d.exi += shift_in.eq(Cat(di_s, shift_in[:-1]))
m.d.exi += bit_ctr.eq(bit_ctr + 1)
with m.If(bit_ctr == 8):
# 9th bit (di_s = addr[0]) arrives.
# shift_in[7] = start, [6]=op_MSB, [5]=op_LSB, [4:0]=addr[5:1]
op = Cat(shift_in[5], shift_in[6]) # 0b10 for READ
adr = Cat(di_s, shift_in[0:5]) # addr[0..5]
with m.If(op == _OP_READ):
m.d.exi += shift_out.eq(words[adr])
m.d.exi += in_read.eq(1)
m.d.exi += out_ctr.eq(0)
return m
# ── Testbench ─────────────────────────────────────────────────────────────
if __name__ == "__main__":
import sys
from amaranth.sim import Simulator, Period
dut = EEPROMModel()
errors = []
HALF = 6 # exi-domain ticks per SK half-period (much longer than sync latency)
async def eeprom_read(ctx, addr):
"""93C46 READ at 6-bit address; returns 16-bit word.
DO is read BEFORE each rising SK edge, since in_read=1 causes
shift_out[15] to be valid between edges. After 16 reads the full
16-bit word is assembled MSB-first.
"""
ctx.set(dut.cs, 1)
ctx.set(dut.sk, 0)
await ctx.tick("exi").repeat(HALF)
# Transmit 9 bits: start(1) + opcode READ(10) + addr[5:0] MSB-first
bits = [1, 1, 0]
for a in range(5, -1, -1):
bits.append((addr >> a) & 1)
for bit in bits:
ctx.set(dut.di, bit)
ctx.set(dut.sk, 1) # rising edge: DUT latches bit
await ctx.tick("exi").repeat(HALF)
ctx.set(dut.sk, 0)
await ctx.tick("exi").repeat(HALF)
# After 9th falling SK: in_read=1, shift_out=word[addr], do=MSB.
# Read DO before each rising edge (it is valid in the LOW phase).
result = 0
for _ in range(16):
result = (result << 1) | ctx.get(dut.do) # sample before rising SK
ctx.set(dut.sk, 1)
await ctx.tick("exi").repeat(HALF)
ctx.set(dut.sk, 0)
await ctx.tick("exi").repeat(HALF)
ctx.set(dut.cs, 0)
await ctx.tick("exi").repeat(HALF)
return result
async def testbench(ctx):
await ctx.tick("exi").repeat(4)
ctx.set(dut.cs, 0)
ctx.set(dut.sk, 0)
ctx.set(dut.di, 0)
await ctx.tick("exi").repeat(4)
w0 = await eeprom_read(ctx, 0)
print(f"T1 word 0 = 0x{w0:04X} (expected 0x0009)")
if w0 != 0x0009:
errors.append(f"T1: word 0 = 0x{w0:04X}, expected 0x0009")
w1 = await eeprom_read(ctx, 1)
print(f"T2 word 1 = 0x{w1:04X} (expected 0xBFAA)")
if w1 != 0xBFAA:
errors.append(f"T2: word 1 = 0x{w1:04X}, expected 0xBFAA")
w2 = await eeprom_read(ctx, 2)
print(f"T3 word 2 = 0x{w2:04X} (expected 0xBBCC)")
if w2 != 0xBBCC:
errors.append(f"T3: word 2 = 0x{w2:04X}, expected 0xBBCC")
# T4: word 3 → 0x0000
w3 = await eeprom_read(ctx, 3)
print(f"T4 word 3 = 0x{w3:04X} (expected 0x0000)")
if w3 != 0x0000:
errors.append(f"T4: word 3 = 0x{w3:04X}, expected 0x0000")
sim = Simulator(dut)
sim.add_clock(Period(MHz=24), domain="exi")
sim.add_testbench(testbench)
with sim.write_vcd("EEPROMModel.vcd"):
sim.run()
if errors:
print("\nFAILURES:")
for e in errors:
print(" ", e)
sys.exit(1)
else:
print("\nAll tests passed.")