xilinx: stop holding the MMCM in reset (fixes #79) - #129
Conversation
An MMCME2_ADV whose RST is driven by a real net came out with ZINV_RST CLEAR, which per prjxray means IS_RST_INVERTED -- a hardware inverter on the reset input. An ordinary active-high reset held low then presents RST=1 to the MMCM for ever, so it never leaves reset and never locks. That is openXC7#79's "does not lock, output clock erratic". The bug needed two mistakes to line up, which is why it survived and why the obvious one-line fix makes things worse rather than better. 1. Polarity. ZINV_x is asserted when the signal is NOT inverted -- prjxray's fuzzers tag it as `1 ^ IS_<reg>_INVERTED` (031-cmt-mmcm/generate.py:46, 032-cmt-pll) -- and it is how every other ZINV_ in fasm.cc is written. write_pll/write_mmcm wrote the un-negated value under a "FIXME: should be INV not ZINV (XRay error?)" that guessed the database was wrong. It is not; the negation was missing. 2. A fabricated parameter masking it. pack_constants (pack.cc:958) sets IS_<pin>_INVERTED = 1 when it ties an unconnected invertible pin to VCC -- a routing optimisation, not a netlist attribute. The const-tie cleanup in pack_clocking_xc7 then disconnects that pin but left the parameter behind, so fasm.cc saw an inversion on a pin connected to nothing. For const-tied pins that spurious TRUE cancelled mistake 1 exactly, which is why a default MMCM looked healthy. So negating alone zeroes all four bits on the case that used to work, and erasing alone does nothing. Both together are correct. Measured on xc7a200tfbg484-2, both directions: before after RST from a net PWRDWN PSEN PSINCDEC + ZINV_RST RST const-tied all four all four and the whole MMCME2_ADV FASM block for the const-tied design is BYTE IDENTICAL to the unpatched output, so the previously-working case does not move and no demo golden shifts. Not hardware-validated: no Vivado here, and no vendor artix7 bitstream containing an MMCM or PLL -- the four references in prjxray-db/artix7/harness/ have none. The causal step "inverted RST => LOCKED never asserts" is read off the fuzzer semantics; what is measured is the emission asymmetry and that it is gone. Existing regression cases all still pass.
|
@hansfbaier — about A dead SoC with no UART output is what you get when there is no clock, not when logic is wrong. And that design's PLL is driven the way this bug needs:
That is precisely the case that breaks. When those pins are const-tied, So the PLL sees an inverted reset held asserted and an inverted power-down. It never locks. No clock, no UART, dead SoC that nonetheless builds and flashes cleanly. Measured on
That is why const-tied designs like the blinkies kept working while a LiteX SoC did not, and why this went unnoticed: the demo matrix has no golden for On "it used to work in 2023": worth checking What I cannot claim: I have no Arty S7 and no way to see a UART come alive. Everything above is the FASM emission plus the fuzzer semantics. You have the board and a design that is currently dead — building it with this patch is a one-command test, and if If it does not, tell me and I will withdraw the PR. I retracted a hardware claim on #119 two days ago for exactly this kind of gap and would rather do it early than argue. Also, unrelated but relevant to your hesitancy about merging: the red |
|
Closing a gap in my own evidence, since it matters for the Everything I measured before was on MMCME2_ADV. That design uses PLLE2_ADV. The patch touches both paths but only one was verified, so I built the PLL case too — same shape as its CRG, PLLE2_ADV #(...) u_pll (
.CLKIN1(clk_in), .CLKFBIN(fb), .CLKFBOUT(fb), .CLKOUT0(clk_out),
.LOCKED(locked), .RST(rst), .PWRDWN(pd), ...);
Both bits clear means both signals inverted, so the PLL is held in reset and powered down at once. It builds, it flashes, and it has no output clock. On Still not a hardware claim. What is measured is the emission on both the MMCM and PLL paths, in both the const-tied and net-driven cases. Whether |
|
@hansfbaier @cavearr — flagging this because the console is reportedly still dead after #138, and this PR fixes a different PLL bug in the same function. They are independent, and this one is still unmerged. #138 fixed what the PLL is programmed with: This PR fixes whether the PLL is allowed to start at all. Current No negation. The reason it only bites some designs: when those pins are const-tied,
Both real nets. Measured on
Both bits clear means the PLL is held in reset and powered down simultaneously. Correct
What I cannot claim: I have no Arty S7, and no way to watch a UART. This is FASM emission plus fuzzer semantics, measured on both the MMCM and the PLL path, in both the const-tied and net-driven cases. @cavearr, you have Vivado goldens for PLLE2 from the 190-build harvest — if any of those has If it turns out not to be the cause, say so and I will withdraw it; I retracted a hardware claim on #119 two days ago for exactly this kind of gap. |
|
@gHashTag , first of all a lot of thanks. Confirmed with the goldens, your analysis is exactly right, and the harvest answers it cleanly: Semantics. Our PLLE2 golden harvest (one minimal PLLE2_ADV design per CLKFBOUT_MULT, built by Vivado and disassembled with bit2fasm) ties both RST and PWRDWN to constant 0, no inverters anywhere, and Vivado sets The litex-ddr-arty-s7 case. In our routed netlist:
One subtlety worth recording for future forensics: comparing our fasm against the Vivado golden of the same design did NOT flag this, and now we know why — Vivado absorbs logic inverters into the pin's IS_x_INVERTED (its artys7 golden has ZINV_RST clear, legitimately, because its netlist pushed an inverter into the pin). The bits can differ between two correct implementations; the invariant is signal-polarity XOR bit, per netlist, not the bit itself. Your fix makes our emission honor exactly that invariant. So: +1 to merge from us. Great catch, and thank you for flagging it against our #138 — the two fixes are complementary: #138 makes the PLL programmed right, this one lets it start. @hansfbaier with both merged, litex-ddr-arty-s7 is worth a board retest. |
|
Merged. Thanks! |
|
I flashed this to the board with the 2023 design, now the design is completely dead. |
|
@hansfbaier 's board result is decisive: please do not merge #129 as-is — and What I got wrong: the LED chaser running was always proof that the
Where that leaves the ZINV question: the prjxray fuzzer tag
Until the table exists, the safe statement is: for net-driven RST with @hansfbaier: sorry for the wasted flash — and note your board just gave |
|
I just checked the verilog, led_chaser seems to run on sys_clk, and before this merge it was running, now it is dead. |
|
@hansfbaier just supplied the behavioural evidence I could not produce myself, probably without realising it:
Confirmed in the source. always @(posedge sys_clk) beginand That changes the diagnosis decisively. While the chaser was blinking, the reasonable reading was "clock is alive, the CPU is stuck" — a firmware or BRAM problem. Now that the chaser is dead too, and it is nothing but a shift register on Which is exactly what this PR describes: the PLL sees It also explains why the earlier BRAM hunt found nothing: @cavearr's three independent proofs that the BRAM layer is bit-faithful were correct, and irrelevant to the symptom. A dead clock takes the whole domain down regardless of how perfect the memory contents are. So the evidence now lines up from three directions:
@hansfbaier, that is as close to a boot report as this can get without flashing it. Worth merging and retesting the board. |
|
@gHashTag BTW, serial console output does not depend on DDR3 initialization, because it runs in ROM. |
|
I reverted this merge in stable-backports, so fixes to this will need a new PR. |
|
Withdrawing this. It was wrong, the revert is correct, and my last comment on this PR argued the evidence backwards. @cavearr's truth table settles it: the bit follows the final netlist's So the asymmetry I measured — bits emitted for a const-tied reset, not emitted for a net-driven one — was correct behaviour. I found a real asymmetry and misattributed it. Where the reasoning actually failedNot in the measurement. In what I did with a refutation. My first version of this patch was negation-only. I A/B'd it and found it removed all four bits from the case that was working. That was the refutation, right there, on the first test. Instead of treating it as one, I went looking for a mechanism that would let the hypothesis survive — found The parameter is not spurious. It is the correct representation of a pin tied inactive through an inverter. I built an explanation to preserve a conclusion, which is precisely the failure I have been putting guardrails against all week, and the guardrails did not catch it because I was the one holding them. The CostHans merged this and lost a board test to it: the negated build killed even the LED chaser. Then, when he reported the dead chaser, I read it as confirmation and posted a comment recommending exactly the thing that had caused it. That comment is the worst artefact of this and I would strike it if I could — treat it as retracted in full. Sorry, @hansfbaier. The revert is right, and #79 should go back to open. What holdsNothing about the PLL. #142 is unaffected — different subsystem, verified from both directions including @cavearr's golden INIT check. And the suite rule this earns is sharper than the one I proposed: not just measure, never infer, but a failed A/B on the previously-working case is a refutation, not a puzzle to solve. If a fix makes the working path worse, the hypothesis is dead; anything else is fitting. |
|
For the record, the full ZINV truth table from Vivado goldens (PLLE2 on xc7s50, one minimal design per row, disassembled with bit2fasm), closing what the thread already concluded:
Every row fits one model and nothing else: the bit equals the FINAL netlist's IS_*_INVERTED — identity, no negation. Vivado implements a tied-inactive pin as VCC plus an absorbed inverter (rows a, and the PWRDWN column throughout), which is why those pins carry the bit SET; a tied-active pin becomes VCC direct (rows b, d, h: bit clear); a live Rows c+f are the litex case: live-net pins, no inversion → both bits clear... except PWRDWN in litex is effectively unused, hence tied-inactive → set. Which is byte-for-byte what the pre-#129 code emits, what the Vivado golden of the same design contains, and what @hansfbaier 's board runs. Harness and the eight designs are being folded into our golden suite as the permanent polarity tests (net-driven variants included, so pack_constants can never mask a real polarity bug again, nor fabricate the appearance of one). |
An
MMCME2_ADVwhoseRSTis driven by a real net comes out withZINV_RSTclear, which per prjxray meansIS_RST_INVERTED— a hardware inverter on the reset input. An ordinary active-high reset held low then presentsRST=1to the MMCM for ever, so it never leaves reset and never locks. That is this issue's "does not lock, output clock erratic".Why it survived, and why the obvious fix makes it worse
Two mistakes were lined up so that they cancelled in the common case.
1. Polarity.
ZINV_xis asserted when the signal is not inverted — prjxray's fuzzers tag it as1 ^ IS_<reg>_INVERTED(031-cmt-mmcm/generate.py:46,032-cmt-pll), and it is how every otherZINV_infasm.ccis written (IFF.ZINV_C,ZINV_D,ZINV_CE0,ZINV_ODATAIN, …).write_pll/write_mmcmwrote the un-negated value under a comment that guessed the database was wrong:The database is right. The negation was missing.
2. A fabricated parameter masking it.
pack_constants(pack.cc:958) setsIS_<pin>_INVERTED = 1when it ties an unconnected invertible pin to VCC — a routing optimisation, not a netlist attribute. The const-tie cleanup inpack_clocking_xc7.ccthen disconnects that pin but left the parameter behind, sofasm.ccread an inversion for a pin connected to nothing.For const-tied pins that spurious
TRUEcancelled mistake 1 exactly. Which is why a default MMCM looks healthy, and why only designs with a real reset net are broken.I know the trap is real because I fell in it: I wrote the negation-only patch first, A/B'd it, and found it removed all four bits from the working case. I discarded it and reported it as a failed hypothesis before finding the second half.
Measured, both directions
xc7a200tfbg484-2:RSTfrom a netPWRDWN PSEN PSINCDECZINV_RSTRSTconst-tiedand the whole
MMCME2_ADVFASM block for the const-tied design is byte-identical to unpatched output. So the previously-working case does not move, and this should not shift any demo golden.All five existing regression cases still pass.
What I have not established
Hardware validation. No Vivado here, and no vendor artix7 bitstream containing an MMCM or PLL — the four references shipped in
prjxray-db/artix7/harness/have none, so the differential oracle that settled the IOB questions in #120 cannot reach this one.The causal step "inverted RST ⇒
LOCKEDnever asserts" is read off the fuzzer semantics and the primitive's behaviour. What is measured is the emission asymmetry between a const-tied and a net-driven reset, and that the asymmetry is gone.@hansemro / anyone on this thread with the board and a failing design: this is cheap to falsify — build with and without, and see whether
LOCKEDcomes up. If it does not, say so and I will withdraw it; I retracted a hardware claim on #119 yesterday for exactly this kind of gap and would rather do it early.Also worth a look, separately
pack.cc:958fabricatingIS_<pin>_INVERTEDis a foot-gun for any pass that later removes the pin. This PR fixes the one consumer that tripped over it; a comment at the source would stop the next one. I left the parameter-erase local to the clocking cleanup rather than changingpack_constants, since that is the conservative half.