Skip to content

fmc: streamline the FMC target and run it at 100 MHz on cosmo - #527

Open
nathanaelhuffman wants to merge 1 commit into
mainfrom
ndh/fmc-speedup
Open

nathanaelhuffman wants to merge 1 commit into
mainfrom
ndh/fmc-speedup

Conversation

@nathanaelhuffman

@nathanaelhuffman nathanaelhuffman commented Sep 10, 2026 •

Copy link
Copy Markdown
Collaborator

This contains a bunch of work to speed up the FMC interface to our theoretical limits here. Hubris currently runs the FMC inteface at 50MHz and the measured data rates are around 8.3MBps at this speed. We've re-tooled both the FMC state machine, and the timing constraints to allow this to get up to 100MHz (on cosmo/metro) and 66MHz (on grapefruit due to SP <-> FPGA pinout errata which limits our max freq due to not having fmc_clk on a clock-capable pin).

We hit 100MHz by phase shifting an internal representation of the fmc_clk forwards and backwards, depending on input/output direction to cancel out these delays as well as using IOB packing. Discussion in detail of this is in the documentation.

This change is backwards compatible with hubris such that it works with the existing clock (50MHz) as well as 66 or 100MHz without needing RTL changes. This is done by choosing PLL parameters for the shifted clocks that are functional between 50 and 100MHz and doing the timing constraints at 100MHz.

Benchmarking:
50MHz FMC bus: ~8.3MBps
66MHz FMC bus: ~9.9MBps,
100MHz FMC bus: ~11.4 MBps.

For validation:
I've run multiple hours of read/write hammering using the fmc_demo UDP blocks, including new features to be upstreamed in hubris that run the loops as fast as possible without network being the load-bearing factor.

The phase of the re-clocked FMC pins was phase shifted to prove that we're in the middle of the sample window and run both the + and - shifted images for 1 hour each with constant hammering of the FMC block which validates that we have margin on our sample points even though timing appears tight (but passing!). It is expected that this timing will remain tight, but not have much build-build variance due to it being limited more by physics than synth and placement.

Analysis showed the SP has been driving FMC_CLK at 50 MHz (CLKDIV=3)
against FPGA constraints signed off at 66.67 MHz, and the target FSM was
spending 7-8 fmc_clk cycles per 32-bit write plus a dead cycle before
every read beat. This change reworks the interface for line rate while
keeping the architecture (NWAIT-paced two-beat 32-bit accesses, async
FIFO CDC into the 125 MHz AXI fabric) intact.

RTL (stm32h7_fmc_target):
- All FMC inputs are captured in dedicated per-pin registers before the
  FSM sees them; the FSM runs one cycle behind the bus, which the NWAIT
  pacing absorbs. On cosmo the capture registers are clocked by a
  later-phased (+135deg) sibling of the deskewed FSM clock, which is
  what gives the input pins setup margin at 10 ns; grapefruit ties both
  clock ports to the same clock for identical cycle behavior.
- Streamlined FSM: writes take 6 fmc_clk cycles of bus occupancy (was
  7-8) and read beats stream back-to-back (extra_beat_setup restores the
  old cadence if a board can't close single-cycle data-out).
- Hardening: fmc_state now resets; an NWAIT timeout (generic, default
  4096 cycles) converts a wedged AXI responder into poisoned reads /
  swallowed writes plus a sticky timeout_count instead of a hung SP bus,
  with stale read data discarded before any live read; the tristate
  control is re-derived from live NOE/NE every cycle and a sticky
  contention counter trips if we ever drive during an SP-owned phase;
  aborted writes complete with zero filler so the AXI side can't wedge;
  AWVALID and WVALID are now raised together.
- The tristate control is registered in OBUFT T polarity (data_out_hiz,
  one IOB-attributed flop per pin): an active-high enable inferred a
  LUT1 inverter that blocked IOB packing and cost ~1.5 ns.

cosmo clocking: new fmc_pll (clk_wiz, BUFG-in-feedback deskew, +45deg
FSM/output phase, +135deg capture phase, OVERRIDE_MMCM to force
M=12/D=1/O=12 so the 600-1200 MHz VCO locks at either a 50 or 100 MHz
input -- one bitstream serves both hubris CLKDIV settings), an
fmc_clk_monitor that holds the MMCM in reset across SP clock stops and
retries lock on frequency change, and reset_fmc now gates on MMCM lock
with async assert (no FMC clock edges exist while unlocked).

Constraints: cosmo at 10 ns with a setup-2 multicycle establishing the
edge relationship into the phase-shifted clocks, corrected min input
delays (the old formulas omitted the SP's half-period of hold, telling
Vivado data could change at the capture edge), multicycle data-out
exceptions deleted, and IOB packing on the output/enable/nwait flops;
grapefruit stays at 15 ns with the same structure. Both boards close
(cosmo WNS +0.116/WHS +0.023, grapefruit +0.159/+0.031).

Verification: the model now implements the WAITCFG=1/DATLAT=0 pacing
contract, blocking writes via wait_until_idle (the 150 ns hack is gone),
ES0491 dummy read cycles, configurable inter-transaction gaps, and
one-shot mid-transaction aborts; the harness runs real fmc_clk/aclk
ratios with a bus-contention tripwire, and the suite grew from 3 tests
to 18 covering back-to-back traffic, FIFO backpressure, slow and wedged
responders, timeout recovery, aborts, reset, and randomized soaks at
50/66.67/100 MHz.

tools/fmc_sweep/fmc_sweep.py drives the hardware bring-up: a phased
frequency sweep over the fmc-demo-server UDP protocol (reads of a
constant register first at each step, so a timing miss is data-only,
never contention), with CLKDIV control through humility.

The hubris side (CLKDIV flips, fmc-demo-server timing-setter fixes)
lands separately; the FPGA is NWAIT-paced, so this bitstream works
unchanged at 50 MHz until hubris moves.
@@ -0,0 +1,203 @@
:showtitle:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably doesn't have to be checked in, but I don't think it harms anything if it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants