Skip to content

SYN-40 - Class C case 2: a x32 SDRAM card with declared bus skew budgets - #191

Open
JeffMboya wants to merge 14 commits into
absmach:mainfrom
JeffMboya:SYN-40-sdram-board
Open

JeffMboya wants to merge 14 commits into
absmach:mainfrom
JeffMboya:SYN-40-sdram-board

Conversation

@JeffMboya

@JeffMboya JeffMboya commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What type of PR is this?

This is a feature.

What does this do?

Adds a x32 SDRAM module: two dice, 24 damping resistors, presence detect, current monitoring, thermal provision, pinned floorplan.

Which issue(s) does this PR fix/relate to?

Related Issue #40, case 2. Uncovered #221, #222, #223, #229, #230, #231, #232, #233, #234.

Have you included tests for your changes?

The board is data. Carried commits add two ERC fixtures and one re-recorded placement snapshot.

Did you document any new/modified features?

Yes. README.md carries stackup, topology, pinouts, classes, the identity schema and every measured figure.

Notes

74 components, 89 nets, 69.09 x 62.00 mm at 36.3% fill. 88 of 89 close; 76 DRC errors.

@JeffMboya

JeffMboya commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

PCB, TOP
pcb_3d_top

PCB, BOTTOM
pcb_3d_bottom

@SammyOina

SammyOina commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

[P1] Do not silently ignore failure while copying the exported .kicad_pro file in crates/synth-router/src/contract.rs:479-482. If this copy fails, routing continues without the project rules and the external router falls back to its defaults—the exact fabrication-policy regression this change is intended to prevent. Propagate the copy error or fail clearly when the exported project exists but cannot be preserved.

@SammyOina

Copy link
Copy Markdown
Collaborator

[P1] This board does not yet resolve #40 case 2 as a production-qualified SDRAM interface. The documented run has two open nets, six KiCad DRC errors, all multi-net skew budgets exceeded, and Synth’s own signal-integrity rules did not run against the routed copper. This is useful as a documented limitation/fixture, but please either add the missing qualification evidence and a passing routed result, or narrow the PR claim accordingly.

@JeffMboya

JeffMboya commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebased onto main and the board rewritten. 7041198.

On quality. The card was one die on three 1×20 headers, so the connectors
sized the board rather than the circuit. It is now x32 — two dice on one
address and command bus — on two 2×20 headers: 20 → 31 components, 40 → 58
nets, fill 20.2% → 28.7%, all 58 nets closed.

Still not dense, and the reason is the compiler: every decoupling capacitor
declares placement_hint { near: ... priority: hard }, documented as an 8 mm
bound, and nine of fourteen land outside it — the worst 11.8 mm beyond, the
farthest cap 20.7 mm from its die. Filed with the measurement: #221.

On the second P1. Claim narrowed: this is the case-2 fixture and
measurement, not a production-qualified interface. Every skew budget is still
missed, and measuring it turned up two defects in the arithmetic itself —
#222 (length_for_net measures dx + dy, so a 45° track reads up to 41%
long; 384 of 791 segments here are diagonal) and #223 (branches of one net
are summed, so CLK reaching two dice is judged on both halves together).

The 162 DRC errors are two defects, not 162: 158 are the annular-ring
contradiction in #216 which #170 fixes, and 4 are two stitching vias 0.055 mm
from U2's pads — commented on #183 with the geometry.

On the first P1. Already fixed and merged: #168 landed as 2a8a6b4 with
the copy error propagated and three tests. The rebase dropped it from this
branch because main now carries it.

@JeffMboya

JeffMboya commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Rebuilt — the review was right on both counts.

Content. It was a bus, not a module. Added: 24 × 22 Ω series damping on
every shared line, serial presence detect (24LC256 at address 0, pull-ups,
clamp), a 10 k NTC divider between the dice, and a filtered supply (polyfuse
at entry, bead splitting VDD from VDDQ). 31 → 66 components, 58 → 87 nets.

Also a real defect: class appeared once per group, so SDRAM_DQL held one
net of sixteen — the skew budgets were never being checked. All 87 nets now
name their class.

Layout. The floorplan is pinned. Automatically this circuit comes out
108.59 × 85.84 mm at 15.5% fill (#233: the canvas the solver settles on
becomes the board, because the headers are pinned to its edges).

before now
Outline 68.59 × 67.90 mm 69.09 × 62.00 mm
Fill 28.7% 34.4%
Nets closed 58 of 58 87 of 87
kicad-cli pcb drc 162 errors 88

2.1× the parts on 8% less board. SDRAM_CLK_HOST at 6.9 mm against 25 mm is
the first budget here the copper meets; the damped stubs spread 6.4 and 6.8 mm
against 3 mm where their branches spread 58 and 77 mm. Full figures in the
README.

Found on the way: #235, #236, #237 (open, green) and #230, #233, #234 —
#234 being the root cause of #183. #235 and #236 are needed to compile, so
their commits ride along until they merge.

@JeffMboya
JeffMboya force-pushed the SYN-40-sdram-board branch 3 times, most recently from 2715703 to 581fe39 Compare October 20, 2026 09:18
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap

@rodneyosodo rodneyosodo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. I checked the claimed recursion: net_carries_clock_source is a flat helper (called at 1718 and 1742, never itself), so there is no stack overflow. Board data checks out.

JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
@SammyOina

SammyOina commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

[P1] This is not ready to approve as a Class-C qualification PCB.

The committed evidence still shows a failed manufacturing result: 1 of 89 nets open, 76 KiCad DRC errors, 3 unconnected items, 6 warnings, five plane-stitching vias inside pads, and validation_failed. Every multi-net SDRAM skew budget fails (14.0–89.3 mm against 2.5–5 mm limits), and the SDRAM clock exceeds its 45 mm limit. Synth’s own SI rules also did not run against the routed copper, so impedance, crosstalk, ampacity, IR-drop, and return-path qualification evidence is missing.

The attached renders add visible Class-C concerns:

  • The BGA/SDRAM escape area is extremely congested, with a dense via field and routing that corresponds to the documented in-pad/clearance violations. This needs a clean, verified escape and return-path review, not only a connectivity result.
  • Reference designators and silkscreen are heavily overlapping around U1/U2 and the resistor/capacitor banks. That is not assembly- or inspection-ready.
  • The board has large unused areas while the critical memory region is crowded and difficult to inspect or rework, indicating that placement/escape quality has not been brought to a production-reviewable level.
  • There is no visible board identifier or revision marking, despite the source declaring revision C. A fabricated board cannot be reliably traced to this design revision.
  • The README acknowledges no thermal analysis, no complete return-path/via-stub analysis, unconnected monitor alerts, no Kelvin current sensing, an unprogrammed EEPROM, and no orderable BOM. These may be acceptable limitations for an exploratory fixture, but they are blockers for a qualification artifact.

Please bring the routed board and evidence to a passing, reviewable state—including expert sign-off and reproducible fabrication artifacts.

The registry's only multi-way connector is `header_1x20`: twenty pins over
49 mm of board edge. A bus wide enough to need forty of them therefore sets
the board's size rather than fitting on it — three single rows on three edges
leaves the parts occupying a tenth of an outline the connectors sized.

`Conn_02x20_Odd_Even` with `PinHeader_2x20_P2.54mm_Vertical` carries forty
pins in the same 51.81 mm, because the second row costs 2.54 mm of width and
no length at all. Verified against the installed KiCad 10.0.6: 40 part pins,
40 symbol pins, 40 footprint pads, matched both ways, all passive, and the
declared dimensions are the courtyard KiCad ships rather than an estimate.

Odd-even numbering rather than counter-clockwise or row-letter, because it is
the one a pin header's silkscreen is printed in: consecutive numbers sit
across the two rows at one position, so a ground between two signal groups
lands beside both.
Two ISSI IS42S16400J dice on four layers, sharing one address and command bus
and bringing a 32-bit data bus to a pair of dual-row carrier connectors, under
declared skew and impedance budgets. absmach#40's case 2 asks for a length- and
impedance-sensitive parallel interface; this is the card for it.

WHY TWO DICE. One x16 die gives sixteen data lines running point to point
from a connector to one pad each, which is the easy half of the problem:
every data net has two endpoints, so there is nothing to match but net against
net. Two dice are how memory is actually built, and they change the question.
The data bus splits into two groups of sixteen, matched within a group because
each is captured by its own die under its own byte masks and nothing requires
DQ0 and DQ31 to agree. The twenty-four address and command lines each become a
two-load branch whose halves have to match. The clock becomes a T. Five bus
classes carry that: seventy-one signals, fifty-eight nets.

WHY DUAL-ROW CONNECTORS. Seventy-one signals need pins, and at 2.54 mm a
single row buys twenty per fifty millimetres of board edge. Two 2x20 headers
carry twice the pins in the same length, so the card is sized by what is on
it: 4658 mm² of board holding 1335 mm² of courtyards, 28.7% fill. Data leaves
on one edge, address and command on the other.

WHY NO CONTROLLER. A driver needs about thirty-six signal pins, honest SDRAM
controller hardware, and a package whose pads a minimum track can leave. The
source lists what the registry has and why none of the five has all three, so
this is a memory card rather than a system — which is also where module length
matching actually lives, since a SO-DIMM matches from its dice to its edge
fingers and no further.

The measured run is in the README and it is not a pass. All 58 nets close and
every track and via stays above the fabrication floor, but every skew budget
is missed, because nothing between the declaration and the check tunes a track
toward it: the language declares the budget, E-SYNTH-DRC-019 verifies it, and
there is no length-matching pass in between. Two of the three parts of a
matched interface are present, and the README says which.

The run also turned up four defects, each filed with its own evidence rather
than described here: absmach#221 on hard placement hints being dropped, absmach#222 on skew
measured with Manhattan arithmetic, absmach#223 on a branched net's length being the
sum of its branches, and further evidence for absmach#183 on stitching-via placement.
All four declared pins named the wrong pad. `Memory_EEPROM:24LC256` extends
`24LC16` and numbers its pins as the datasheet does — A0 A1 A2 GND SDA SCL WP
VCC — so the part had `vdd` on A0, `gnd` on A1, `sda` on A2 and `scl` on GND,
and pads 5 through 8, the real SDA, SCL, WP and VCC, had no pin at all.

A board using it put the rail on an address input, ground on another, and the
I2C bus on a third and VSS. `fixtures/designs/feather_m4_express.synth` has
been doing exactly that.

The three address inputs and WP are declared without `required`, because a
design is free to strap them to either rail; a design that means to use the
part straps all four, and this card does.

`synth registry qualify --part 24lc256_eeprom` now reports nine checks passing
where it reported `E-SYNTH-QUAL-002`, four undeclared pads. Nothing reported
the four that were declared wrongly, which is its own defect.
The part declared `Device:Ferrite_Bead`. KiCad 10.0.6 ships `Device:FerriteBead`
and `Device:FerriteBead_Small`, with no underscore, so the symbol could not be
read: `qualify` reported `symbol could not be read` even with KICAD_SYMBOL_DIR
set, and an export of any board using the part reached E-SYNTH-EXPORT-001 and
synthesized a rectangle instead.
Pads 3 and 4 of `Sensor_Temperature:TMP117xxDRV` are ALERT and ADD0, and
neither was declared, so `E-SYNTH-QUAL-002` blocked the part and a design had
no way to strap its I2C address. An unstrapped ADD0 leaves the device's address
to whatever a floating input decides, which is not a measurement.

ALERT is open-drain and may be left unused; ADD0 is an input a board is
expected to tie, and is declared without `required` for the same reason as the
EEPROM's address pins.

The footprint remains a question this does not settle: the part names
`Package_TO_SOT_SMD:SOT-563` while a TMP117DRV is WSON-6, and the symbol's own
Footprint property says so. Correcting that moves the part's geometry and wants
its exposed pad declared, which no part in the registry does yet.
The EEPROM went from four declared pins to eight and the TMP117 from four to
six. Pad geometry for a part is laid out from its pin list, and a hard `near`
hint aims at the anchor's pad, so the passives around U4 and U9 now target
different points and the greedy packing relocated a handful of them.

The board outline is unchanged at 90.57 x 76.16 mm. U3 and U8 move 1 mm, and
D5, R9, R10 and six other passives move within the same envelope. Nothing here
is a placement regression to chase: it is the pin maps being right.
… bus

Review on the card was that it is basic and mostly empty, and both halves of
that were fair. What it carried was two dice, their decoupling and two
connectors: the bus was the whole design, and a bus is not a module.

What a module carries, and now does:

**Series damping.** Nineteen lines on the command connector reach two dice,
and a 143 MHz edge arriving at a T reflects off both stubs. Twenty-four 22 ohm
resistors sit beside the pins they damp — the clock, the five command lines,
the four byte masks and the fourteen address and bank lines — which is what an
unbuffered module does instead of a parallel termination it would need a
reference rail and a regulator for. The data bus stays undamped on purpose:
each line runs from one pin to one pad on one die, so there is no branch to
damp and a resistor there would add a discontinuity and thirty-two parts.

It also changes what is being measured. Each shared line is now a stub from
the connector to its resistor and a branch from the resistor to the dice, so
the budgets split with them: `*_HOST` on the stub, the bus class on the
branch, and a total path budget is the sum. Measured, the stubs spread 6.4 and
6.8 mm against 3 mm where their branches spread 58 and 77 mm, so the part of
the path the card controls behaves and the part the router lays out does not.

**Serial presence detect.** A 24LC256 at address zero on its own two-wire bus,
brought out on the connector's last two pins with its own pull-ups, its own
ceramic and a dual rail clamp across both lines where they leave the board. A
module that cannot say what it is has to be configured by hand.

**Thermal provision.** A 10 k NTC between the dice in a divider against the
rail, out on one analog line. Deliberately not a digital sensor on the
presence detect bus: the registry's TMP117 cannot have its address strapped,
and a sensor whose address a floating pin decides is not a measurement.

**A filtered supply.** The rail arrives behind a 500 mA polyfuse, because a
card that can be inserted live can be inserted wrongly, and the die's separate
core and driver supplies are now separated in fact: a 600 R bead between them
gives thirty-two switching drivers a high impedance back toward the logic that
samples the clock. Each rail carries its own bulk and its own per-pin ceramics.

**Every net names its class.** They did not before: `class` appeared once per
group, so `SDRAM_DQL` held one net of sixteen and a `max_skew` over one net is
skipped. The budgets on this card were not being checked at all. All 87 nets
now carry a class, and the eight bus classes hold 16, 16, 14, 14, 9, 9, 1 and
1 nets.

66 components and 87 nets, against 31 and 58.
The automatic placement is not what this card should be judged on. It spreads
a 1476 mm² circuit over 9322 mm² — 108.59 x 85.84 mm at 15.5% fill — because
the solver's canvas ladder only has to find a canvas that works, and the two
edge-hinted headers are then placed against that canvas's edges however far
apart they are. Fitting the outline afterwards keeps the distance.

So the card ships a floorplan. The command connector takes one edge with its
twenty-four damping resistors in two columns beside the pins they damp, the
data connector takes the other, the dice are stacked between them so each
faces its own half of the data bus, each rail has a decoupling column per die,
and the sideband fills the space beside the dice that the bus does not need.
Holes and fiducials sit in the top and bottom bands.

69.09 x 62.00 mm at 34.4% fill: 8% less board than the 31-component version
for 2.1 times the parts. The sidecar also makes placement byte-identical by
construction rather than by a heuristic being stable, which is what the
measurements in the README depend on.

Two notes for whoever edits it. The positions are courtyard centres, because
that is what the sidecar's x/y is applied as, and they differ from the
footprint origin for any part whose origin is not its centre — pad 1 of a pin
header is 24.13 mm from its centre. And the 0603 ceramics on the plane are
0603 because the plane fanout cannot find a stitching-via position beside an
0402 pad; the ones on the routed rail are 0402.
Every figure re-run on the pinned board with a release build, KiCad 10.0.6 and
KiCadRoutingTools v0.23.0.

87 of 87 nets closed with no open nets and no unconnected pads, 3502.4 mm of
copper over 1296 segments and 231 vias, no track below the 0.127 mm floor and
no undersized via. `kicad-cli pcb drc` reports 88 errors where the previous
revision reported 162, and the clearance figures say why they remain: the
exported `.kicad_dru` carries the manufacturer's edge and hole rules but not
the per-netclass clearances, so the engine routes to the global floor and
KiCad rejects the board against rules the engine never saw.

One blocking reason is new and is not a clearance: a plane stitching via sits
inside U1 pad 34. The fanout places its vias from a synthesized pad map rather
than the footprint the board carries, so where they land is not controlled.

The skew table now has a row that passes — the damped clock stub, 6.9 mm
against a 25 mm bound — and the new sections cover presence detect, the
thermal divider, the damping network and the pinned floorplan.
…sed pad

`Power_Management:INA3221` measures three high-side shunts and three bus
voltages over I2C, which is what lets a board carrying it report its own
current without spending a connector pin per rail.

Verified against the installed KiCad 10.0.6 the way absmach#229 showed is necessary:
all seventeen pads of
`Texas_RGV0016A_VQFN-16-1EP_4x4mm_P0.65mm_EP2.1x2.1mm` are declared, every
declared number exists on the symbol, and the declared 4.75 x 4.75 mm is the
footprint's own pad extent. `synth registry qualify` passes all nine
mechanical checks; only provenance fails, as it does for every shipped part.

Two notes on the pin map. The six sense pins are `analog` rather than the
symbol's `input`, because a few tens of millivolts across a shunt is what
arrives on them. And pad 17 is the package's exposed pad — the first in the
registry to be declared, which is what `E-SYNTH-QUAL-002` wants of any
footprint carrying one. It is not `required`: TI's land pattern asks for it to
be soldered to a thermal land, and whether that land is ground is the board's
decision, not the part's.

0.65 mm pitch leaves 0.35 mm between pads, so the package escapes on the
standard floor rather than needing via-in-pad.
…probed

Review feedback asked for the things that make a module integrable and
diagnosable rather than more components. Three of them fit this card, and one
constraint decided how: **both connectors are full**, 40 of 40 pins each, so
anything host-visible has to ride the presence-detect bus or it cannot exist.

**A power monitor on that bus.** One INA3221 at 0x40 measures two 0.1 ohm
shunts and a bus voltage: channel 1 across R31 is everything the host
delivers, channel 2 across R32 is the driver rail, and the core current is
the difference rather than a third shunt. 20 mV at the card's 200 mA, against
a 163.8 mV full scale.

R31 sits ahead of the fuse, which is not where a shunt conventionally goes.
The monitor has to tap both ends of what it measures, its pads are on an
0.65 mm grid, and a declared plane needs a stitching via beside every pad on
it — there is no room for one beside a pad that fine, so both ends of the
shunt have to be routed copper rather than the plane. Ahead of the fuse they
are, and what channel 1 then reports is the current the host delivers, fuse
drop included, which is the more useful of the two readings.

Its `WARNING`, `CRITICAL`, `PV` and `TC` outputs are left unconnected, and
there is no voltage supervisor, for the same pin-budget reason. A supervisor
whose power-good signal cannot reach the host is a part that only adds a
failure mode.

**Six test pads instead of two:** both rails, ground, the thermal line and
both presence-detect lines, so bring-up and a production test need no wire
soldered to a component. The bus is deliberately not probed — a stub off a
143 MHz line to a test pad is a stub.

**A documented identity record.** The EEPROM now has a fixed 32-byte schema
with a CRC-16, written out where the device is declared: format version,
organisation, capacity, clock, CAS latency, supply, board and BOM revision,
which sensors are fitted, and a checksum. It is deliberately not called SPD,
because a JEDEC image has a layout and a checksum of its own and claiming
compatibility without meeting it would be worse than carrying no schema.
Nothing here programs the device, which the README says plainly.

Declined, with reasons in the README: ESD on the fast bus (28 clamp channels
at about a picofarad each), signal-integrity tuning footprints (stubs on the
damped lines), and a status LED (the registry's only LED is a WS2812B — a
switching load with a data line, beside the bus).

74 components and 89 nets, against 66 and 87. One of those 89 had no class:
`VDDQ` lost it when the shunt split the rail, which is the same defect the
previous commit fixed board-wide.
The shunts go in the rails between the connector and the fuse, the monitor
beside them, its ceramic beside it, and the four new test pads continue the
column the first two started on the board's right edge.

Same outline: 69.09 x 62.00 mm. Fill 34.4% to 36.3%, because eight parts went
into space the floorplan already had.
…board

The sheet still described the board before the power monitor: 66 components,
87 nets, and a run that closed all 87. The board it ships with has 74 and 89.

Re-run on the committed tree with a release build, KiCad 10.0.6 and
KiCadRoutingTools v0.23.0: 88 of 89 nets closed with one open, 3820.4 mm of
copper over 1601 segments and 261 vias, no track below the 0.127 mm floor and
no undersized via. `kicad-cli pcb drc` reports 76 errors where the previous
revision reported 88 — 75 clearance and one hole clearance, with the same
cause as before, the exported `.kicad_dru` carrying the manufacturer's rules
but not the per-netclass clearances.

Two figures moved in opposite directions and both are worth reading. The data
groups tightened — `SDRAM_DQH` from 41.6 mm of spread to 22.3 — because the
monitor's rail took routing pressure off the middle of the board. The command
branches loosened, `SDRAM_CTRL` to 89.3 mm, because `WE_N` now takes a longer
way round. The damped stubs are unchanged at 6.4 and 6.8 mm, and the clock
stub still meets its bound at 6.9 mm against 25.

The plane fanout now gets five vias wrong rather than one: `C4.1`, `C5.1`,
`U3.1`, `R32.2` and `C19.1`. Adding a monitored rail added plane pads for it
to place vias against a synthesized pad map, which is the defect, not the
rail.
Both this branch and absmach#195 completed these two parts independently, from the
same symbols and the same footprints, and arrived at the same pin numbers.
The files still differed: the comments told the story two ways, and this
branch left the EEPROM's `scl` as `bidirectional` where absmach#195 made it `input`.

`input` is the right type. SCL on an I2C slave is driven by the master; the
24LC256 does not stretch the clock, so the pin never leaves its input state.
Nothing enforces this -- `i2c_scl` declares no required electrical type, so
either value loads -- which is why the two branches could disagree silently.

Both files are now byte-identical to absmach#195's, so whichever of the two merges
first the other applies without a conflict, and the pin maps have one owner.
JeffMboya added a commit to JeffMboya/synth that referenced this pull request Oct 21, 2026
# Conflicts:
#	crates/synth-place/tests/snapshots/snapshots__feather_m4_express_placement.snap
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.

3 participants