Finished the board including the connector and mousebites

This commit is contained in:
Dennis Brentjes
2026-07-22 22:02:12 +02:00
parent a86aa255f1
commit 5828d6a388
16 changed files with 11159 additions and 6453 deletions
+3 -1
View File
@@ -60,7 +60,9 @@ class BBATop(Elaboratable):
# iCEbreaker — see synth.py). panel_led bit order matches StatusPanel.
self._status_panel = status_panel
# Optional UART debug console (8N1 115200, sync domain).
# uart_tx → FT2232H Channel B pin 9; uart_rx ← pin 6.
# uart_tx → FT2232H Channel B (net UART_RXD, pin 19);
# uart_rx ← FT2232H Channel B (net UART_TXD, pin 18) — net names are
# FTDI-perspective, so this is not a naming mismatch, see synth.py.
self._uart_console = uart_console
# EXI (GC side)
+88 -56
View File
@@ -1,4 +1,4 @@
"""Synthesis script for BBATop → iCEbreaker (iCE40UP5K SG48).
"""Synthesis script for BBATop → re-bba-rb interposer board (iCE40UP5K SG48).
Run from workspace root:
python -m exi_bba.synth # synthesize only
@@ -20,19 +20,43 @@ from exi_bba.bba_top import BBATop
# ── Platform definition ───────────────────────────────────────────────────
# Pin assignments use the iCEbreaker PMOD connectors as placeholders.
# Replace with actual SP1-interposer pin numbers once PCB is finalised.
# Real re-bba-rb board pin map, pulled directly from the schematic netlist
# (U9 = iCE40UP5K-SG48ITR) — not iCEbreaker PMOD placeholders.
#
# PMOD1A (J2): pins 4 2 47 45 / 3 48 46 44 (top/bottom)
# PMOD1B (J3): pins 43 38 34 31 / 42 36 32 28
# PMOD2 (J4): pins 27 25 21 19 / 26 23 20 18
# EXI (GC side) : CLK=44(G6) MOSI=4(IOB_8a) MISO=3(IOB_9b) CS=45 INT=46
# W5100 bus (IOT bank, indirect parallel):
# A0=42 A1=38 D0=34 D1=32 D2=31 D3=28 D4=27 D5=26 D6=25 D7=23
# CS_N=43 RD_N=37 WR_N=36 RST_N=2 (net /ethernet/ETH_RST, NOT on the IOT
# bank — separate pin)
# Board ties the W5100's upper address lines A[14:2] to 0 (only A[1:0]
# wired); DATA[7:0] is bidirectional (SB_IO tristate, shared output-enable).
#
# EXI : CLK=4 MOSI=2 MISO=47 CS_N=45 INT_N=3 (PMOD1A)
# W5100 : indirect parallel bus — 15 pins across PMOD1B + PMOD2.
# ADDR[1:0]=43 38 DATA[7:0]=34 31 42 36 32 28 27 25
# CS_N=21 RD_N=19 WR_N=26 INT_N=23 RST_N=20 (pin 18 free)
# Board: tie the W5100's upper address lines A[14:2] to 0 (only A[1:0] wired);
# DATA[7:0] is bidirectional (SB_IO tristate, single shared output-enable).
# INT_N=13 — MOVED off the schematic's original pin 35: pin 35 physically
# conflicts with the iCE40UP5K's PLL hard macro (nextpnr: "PLL bel
# 'X12/Y31/pll_3' cannot be used... conflicts with input... on pin 35"),
# and this design needs the PLL for the 54 MHz capture-domain clock. Pin 35
# cannot be used as regular I/O at all while the PLL is instantiated,
# regardless of what drives it — this is a fixed silicon constraint, not a
# routing choice. Pin 13 was picked because it's the ONLY spare GPIO on this
# exact SG48 package/board combo: the 5k-sg48 package has 39 usable I/O pins
# total (per icestorm's icebox.py pin database), and every other one is
# already assigned to a real signal on this board — pin 13 shows up in the
# schematic netlist as an unrouted U9 pad, nothing else was available.
# Board is still in design, so PIN 13 ON THE SCHEMATIC/PCB MUST BE REWORKED
# to carry ETH_INT instead of pin 35 before layout is final.
#
# Debug (J4 header, unpopulated): DBG0=9 DBG1=10 DBG2=11 DBG3=12 DBG4=6
# UART (FT2232H channel B): net UART_TXD is the FTDI's OUTPUT, so it is the
# FPGA's RX input, and vice versa for UART_RXD — FPGA RX=18(UART_TXD net),
# FPGA TX=19(UART_RXD net). Do not swap by "TXD means transmit" instinct.
# Status LEDs: D6(green,heartbeat)=LED_G=pin47, D7(red,EXI-activity)=LED_R=
# pin48 — both wired ACTIVE-HIGH (LED anode toward the FPGA pin via its
# series resistor, cathode to GND), unlike the iCEbreaker's own onboard
# LEDs which are active-low. No physical button exists on this board (the
# iCEbreaker's BTN_N was dev-board-only); panel_btn is tied idle instead.
# RGB status LED (D11=red/rx, D12=green/tx, D13=yellow/ready) is on the
# iCE40UP5K's dedicated SB_RGBA_DRV pins 39/40/41 — fixed by the chip
# package on any board, not board-specific, so no resource needed here.
class IceBreakerPlatform(LatticeICE40Platform):
device = "iCE40UP5K"
@@ -45,43 +69,38 @@ class IceBreakerPlatform(LatticeICE40Platform):
Clock(12e6),
Attrs(GLOBAL=True, IO_STANDARD="SB_LVCMOS")),
# EXI interface (GC side, SPI Mode 3) — PMOD1A FPGA pins
# EXI interface (GC side, SPI Mode 3)
Resource("exi", 0,
Subsignal("clk", Pins("4", dir="i")),
Subsignal("mosi", Pins("2", dir="i")),
Subsignal("miso", Pins("47", dir="o")),
Subsignal("clk", Pins("44", dir="i")),
Subsignal("mosi", Pins("4", dir="i")),
Subsignal("miso", Pins("3", dir="o")),
Subsignal("cs_n", Pins("45", dir="i")),
Subsignal("int_n", Pins("3", dir="o")),
Subsignal("int_n", Pins("46", dir="o")),
Attrs(IO_STANDARD="SB_LVCMOS")),
# W5100 indirect parallel bus — PMOD1B + PMOD2 FPGA pins
# W5100 indirect parallel bus
Resource("w5100", 0,
Subsignal("addr", Pins("43 38", dir="o")),
Subsignal("data", Pins("34 31 42 36 32 28 27 25", dir="io")),
Subsignal("cs_n", Pins("21", dir="o")),
Subsignal("rd_n", Pins("19", dir="o")),
Subsignal("wr_n", Pins("26", dir="o")),
Subsignal("int_n", Pins("23", dir="i")),
Subsignal("rst_n", Pins("18", dir="o")),
Subsignal("addr", Pins("42 38", dir="o")),
Subsignal("data", Pins("34 32 31 28 27 26 25 23", dir="io")),
Subsignal("cs_n", Pins("43", dir="o")),
Subsignal("rd_n", Pins("37", dir="o")),
Subsignal("wr_n", Pins("36", dir="o")),
Subsignal("int_n", Pins("13", dir="i")),
Subsignal("rst_n", Pins("2", dir="o")),
Attrs(IO_STANDARD="SB_LVCMOS")),
# Bring-up status panel → iCEbreaker ONBOARD parts (dedicated pins, not
# on any PMOD, so they coexist with EXI + W5100). LEDR/LEDG are
# active-low discrete LEDs; BTN_N is the user button.
# RGB LED (pins 39/40/41) is driven via SB_RGBA_DRV — not declared here
# as a platform resource (see BBATopSynth.elaborate, status_panel block).
Resource("ledr", 0, Pins("11", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("ledg", 0, Pins("37", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("btn", 0, Pins("10", dir="i"), Attrs(IO_STANDARD="SB_LVCMOS")),
# Bring-up status panel: D6/D7 discrete LEDs (active-high on this
# board). RGB (pins 39/40/41) is driven via SB_RGBA_DRV — not
# declared here as a platform resource (see BBATopSynth.elaborate).
# No onboard button on this board (see note above).
Resource("ledr", 0, Pins("48", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")),
Resource("ledg", 0, Pins("47", dir="o"), Attrs(IO_STANDARD="SB_LVCMOS")),
# UART debug console → iCEbreaker FT2232H Channel B (onboard USB-UART).
# No external hardware needed — the FT2232H is already on the board.
# UART debug console → FT2232H Channel B.
# On the PC: open the second USB serial port at 115200 8N1.
# pin 9 = FPGA TX → FT2232H Channel B RX (FPGA drives)
# pin 6 = FPGA RX ← FT2232H Channel B TX (FPGA reads)
Resource("uart", 0,
Subsignal("tx", Pins("9", dir="o")),
Subsignal("rx", Pins("6", dir="i")),
Subsignal("tx", Pins("19", dir="o")),
Subsignal("rx", Pins("18", dir="i")),
Attrs(IO_STANDARD="SB_LVCMOS")),
]
@@ -124,23 +143,23 @@ class BBATopSynth(BBATop):
w5100.rst_n.o .eq(self.w5100_rst_n),
]
# ── Bring-up status panel → onboard LEDs / button ──────────────
# ── Bring-up status panel → onboard LEDs ────────────────────────
# All 5 panel LEDs mapped:
# LEDG (pin 37) = led[0] heartbeat
# LEDR (pin 11) = led[1] EXI activity
# LEDG (pin 47) = led[0] heartbeat
# LEDR (pin 48) = led[1] EXI activity
# RGB (pins 39/40/41) = led[2] rx / led[3] tx / led[4] ready
# The one onboard button → panel btn[1] (manual re-init).
# No physical button on this board — panel_btn tied idle/released.
if self._status_panel:
ledr = platform.request("ledr", 0)
ledg = platform.request("ledg", 0)
btn = platform.request("btn", 0)
led = self.panel_led
m.d.comb += [
ledg.o.eq(~led[0]), # heartbeat (active-low LED)
ledr.o.eq(~led[1]), # EXI activity (active-low LED)
# btn[0]/[2] held released (active-low idle = 1)
self.panel_btn.eq(Cat(C(1, 1), btn.i, C(1, 1))),
ledg.o.eq(led[0]), # heartbeat (active-high LED)
ledr.o.eq(led[1]), # EXI activity (active-high LED)
# all 3 bits idle/released (active-low idle = 1) — no
# physical button exists on this board to read
self.panel_btn.eq(C(0b111, 3)),
]
# iCEbreaker RGB LED has no series resistors — must use
@@ -205,26 +224,39 @@ if __name__ == "__main__":
# versions; treat as non-fatal timing failure.
print(f" [seed {seed}] build exception (timing?): {exc}")
# Parse fmax from nextpnr log in build/top.tim (if present)
# Parse fmax from nextpnr log in build/top.tim (if present). Domain
# names are 'clk' (exi/sync, 24 MHz target) and 'capture_clk'
# (54 MHz target, the tighter constraint) — NOT 'exi', which never
# matched any real log line (this regex silently reported 0.0 MHz
# for every seed until fixed).
import glob, re
tim_files = glob.glob("build/top.tim") + glob.glob("build/*.tim")
fmax_exi = 0.0
fmax_clk = 0.0
fmax_capture = 0.0
for tf in tim_files:
try:
with open(tf) as f:
for line in f:
m_ = re.search(
r"Max frequency.*exi.*?:\s*([\d.]+)\s*MHz", line)
r"Max frequency for clock\s+'(\w+)':\s*([\d.]+)\s*MHz", line)
if m_:
fmax_exi = float(m_.group(1))
domain, freq = m_.group(1), float(m_.group(2))
if domain == "clk":
fmax_clk = freq
elif domain == "capture_clk":
fmax_capture = freq
except OSError:
pass
print(f" [seed {seed}] exi fmax extracted: {fmax_exi:.1f} MHz")
if fmax_exi > best_fmax:
best_fmax = fmax_exi
print(f" [seed {seed}] clk fmax: {fmax_clk:.2f} MHz (target 24) "
f"capture_clk fmax: {fmax_capture:.2f} MHz (target 54.02)")
# capture_clk is the binding constraint (tighter target, historically
# the one that swings ±20% with seed) — rank seeds by it.
if fmax_capture > best_fmax:
best_fmax = fmax_capture
best_seed = seed
print(f"\nBest seed: {best_seed} exi fmax: {best_fmax:.1f} MHz")
print(f"\nBest seed: {best_seed} capture_clk fmax: {best_fmax:.2f} MHz "
f"(target 54.02) — {'PASS' if best_fmax >= 54.02 else 'FAIL'}")
if do_flash:
print(f"\nFlashing with seed {best_seed}...")