Blog
A vendored Verilog artifact from April 2023 carried a defect its generator repaired in March 2026. The hand fix is correct and temporary; the next regeneration would undo it.
The last red design in the openXC7 demo set was an HDMI board whose vendored LiteX output tied the SHIFTOUT1/2 outputs of an OSERDESE2 to a constant. Vivado warns and ignores that; yosys refuses, which is the correct behaviour — driving an output port from a constant is not a thing hardware can do. The maintainer patched the vendored file by hand and the build went green.
The patch is right. It is also a duplicate, and the thing it duplicates is nearly three years newer than the file it was applied to.
| when | what |
|---|---|
| 2023-04-10 | the vendored file was generated — its own header says LiteX sha1 0c326f0e |
| 2026-03-05 | upstream LiteX fixed the emission: b3a4c270, "S7HDMIPHY: Fix build with Yosys" |
| 2026-08-14 | the vendored file was patched by hand |
The upstream commit changes exactly what the hand patch changes:
- o_SHIFTOUT1 = shift[0] if serdes == "slave" else 0,
- o_SHIFTOUT2 = shift[1] if serdes == "slave" else 0,
+ o_SHIFTOUT1 = shift[0] if serdes == "slave" else Open(),
+ o_SHIFTOUT2 = shift[1] if serdes == "slave" else Open(),
Same defect, same fix, five months apart, arrived at independently. And the artifact it was applied to had been sitting in the repository since 2023 — generated before the bug was ever reported, and never regenerated since.
A vendored generated file is a photograph of a generator on one day. It keeps every defect the generator had that day, and it keeps them after the generator stops having them. Nothing in the repository says how old the photograph is unless you open it and read the header — which, in this case, says so plainly and nobody had reason to look.
The practical shape of the trap: the hand fix is correct today and gone tomorrow. It lives on the artifact, not on the thing that produces the artifact, so the next regeneration silently reintroduces the bug — and it will look like a regression in the toolchain rather than a lost patch.
There was also a second, cheaper cost in flight. The maintainer had asked for an issue to be filed with LiteX, and that work was about to be done. It would have been a duplicate of a five-month-old commit. Checking upstream first took about two minutes: find the emitting line, blame it, read the commit that touched it.
Nothing to the code. Both halves of the job were already done by other people — the patch by the maintainer that morning, the real fix by LiteX in March. What was missing was the connection between them, so that went as a comment on the commit it concerns, naming the upstream sha and recommending a regeneration over carrying the patch.
That is the whole contribution, and it is worth being plain about how small it is. We did not find the bug, fix the bug, or file anything. We noticed that two people had solved the same problem five months apart and told them.
Before patching a generated file, read its header and blame the generator. The fix may already exist, and if it does, the patch you are about to write has an expiry date.
And the corollary that costs more to act on: a vendored artifact with no regeneration step is a dependency whose version nobody is tracking. This one was three years stale and there was no gate that would ever have said so.
Every figure above is measured, and the limits are named with it.