fpga: ULX3S v3.1.8 smoke + 1 Mbaud UART bridge to exact lean_silicon_lsc1 (MinCore) - #16
Conversation
- Minimal 25 MHz counter/LED smoke_top + LPF (v3.1.8 pin-subset hypothesis) - UART RX/TX at 1 Mbaud, 2-flop sync, framing error handling - uart_bridge.sv: exact lean_silicon_lsc1 instantiation, byte buffering, POR, backpressure - New driver: fpga_harness/ulx3s_uart.py (explicit --port only) - Icarus TB skeleton: test/tb_uart_bridge.sv - SRAM-only build scripts; explicit prohibition of -f documented - Real yosys/nextpnr/ecppack run; 291.97 MHz post-route; bitstream SHA captured Ownership: PR #13 (host runtime), PR #15 (board_detect/SHA256SUMS) untouched. Worktree: fpga/ulx3s-smoke-uart-mincore @ 03926bb [skip ci] sprint deliverable
- Exact commands, tool versions, timing (291.97 MHz smoke / 160.26 MHz bridge) - Bitstream SHAs recorded - SRAM-only openFPGALoader recipe with explicit -f prohibition - Supported MinCore transaction list - Bounded claims only; no hardware execution evidence [skip ci]
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_9458fc2c-f5d5-4295-8f37-26c6c498bbc5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 353a22814b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Every response byte was being corrupted or lost on the way to the host, and none of the checked-in verification could see it. RTL: - uart_tx released tx_ready at bit_cnt == 8, so data bit 7 lasted one 25 MHz clock instead of a full baud interval. A receiver sampled every zero-valued bit 7 as one (a SET echo of 00 arrived as 80). Ready now releases at 9. - The bridge's power-on "synchroniser" only ever shifted in ones, so rst_n was never low and no register in the bridge or the core was ever initialised. The core latched x into its error state and answered nothing. Replaced with a zero-initialised shift register that genuinely holds reset for 8 edges. - Both handshake directions now use a one-deep skid buffer whose ready deasserts on the handshake that fills it. Previously RX_VALID was pulsed against a ready sampled a cycle early (dropping bytes that arrived mid transaction) and TX_READY mirrored the serialiser a cycle late (letting the core hand over a second byte that overwrote the first). - Added a sticky rx_overrun probe that drives no logic, so the bench can assert no byte was ever displaced. Verification: - Replaced the tb_uart_bridge liveness window with a real 1 Mbaud 8N1 receiver. It fails on a missing byte, a short final bit, a bad stop bit, a wrong or extra byte, and on rx_overrun, across STATUS, SET, XOR, MUL, unknown opcode and abort recovery. Wired it into `make sim`, which previously never ran it. - ulx3s_uart.py --tx mul printed the board's reply and exited 0 whatever it was. It now computes the expected GF(2^128) product with both independent oracles in sim/model.py, cross-checks them, and gates the exit status. Added tests that feed complete but wrong 16-byte responses and require exit 1. Build and evidence: - build_smoke.sh and build_uart.sh resolved sources and output through one ".." too many, so the bridge build could not find any RTL and evidence landed outside the repository. Both now resolve the repository root from their own location and archive artefacts under the names the manifests use. - Committed the executable bit so `make -C fpga/ulx3s smoke` runs, and pointed the uart target at build_uart.sh instead of an echo. - The LPF had no frequency constraint, so a "25 MHz" claim was really nextpnr's 12 MHz default. Constrained it; both designs still close (291.97 / 154.37 MHz). - results/ SHA256SUMS named smoke.bit for a file archived as ulx3s_smoke.bit, so the integrity check could not run. The scripts now emit and self-verify both manifests. Regenerated ulx3s_bridge.bit from the repaired RTL; the digest is reproducible across three independent builds. - .gitignore's blanket *.log dropped the synthesis and route logs the manifest promised. Exempted the results directory and archived them. Not addressed by design: 0x7f still cannot appear as a payload byte, since the bridge treats it as an abort before command framing. Escaping it would change the wire protocol. Documented as a limitation instead.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ca273a2d-9c95-43be-9729-f38c932e9664) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01e704685f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_15fe7362-d16b-4981-8c84-5c81e3797117) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 078e55e385
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_31457b2a-5692-4615-90c4-01bda0720d97) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 63a2054099
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
status is the default --tx, and it was the only command whose reply was printed without being checked. A misbuilt bridge emitting four arbitrary bytes passed the default board-smoke invocation with exit 0, so the check that is supposed to catch a broken build was the one command that could not fail. Compare against 01 01 0f 08 and exit 1 on mismatch, matching how set, xor and mul already gate their exit status. A test parses status_byte() out of leanvm_b_stream_alu.sv so the host constant cannot drift from the RTL.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_cf1f8fe8-ae59-48fe-a24d-fd5c3b8cc6f8) |
Verification at
|
| artifact | rebuilt sha256 | committed | result |
|---|---|---|---|
ulx3s_bridge.bit |
eacee77bdca486f173cacdb56709e442942eb85f9111eabcb96026976f8bae51 |
same | byte-identical |
ulx3s_smoke.bit |
96eb9eda7421bac902eacaeced21eee0db9a80b8f2f2effdb52b515d68e0b2e3 |
same | byte-identical |
smoke.config |
8243770de3fb1cb65f8ef0ee345addab105cf7e219d0c3be5cf009743e27f59f |
same | match |
smoke.svf |
0a2dcd734f194819b6483b6d5a7e394677eb30634760d46a62d4f5542cdec944 |
same | match |
timing_uart.txt and SHA256SUMS_bridge.txt also came back byte-identical.
Timing closure at 25 MHz — gate proven non-vacuous
FREQUENCY PORT "clk" 25.000000 MHz in the LPF, nextpnr-ecp5 invoked with no --timing-allow-fail, under set -eu.
- bridge: 161.47 / 154.37 MHz, PASS at 25.00 MHz
- smoke: 352.61 / 291.97 MHz, PASS at 25.00 MHz
A passing timing report is only worth something if the tool would actually have failed, so I re-ran place-and-route with the constraint raised to 400 MHz: nextpnr exits 1 with ERROR: ... (FAIL at 400.00 MHz). The gate is real, and without the LPF FREQUENCY line the router would silently fall back to its 12 MHz default.
Pin / safe-output audit
nextpnr places TRELLIS_IO: 4/365, and the bitstream config contains exactly four BASE_TYPE pin configurations — two outputs only:
ledB2OUTPUT_LVCMOS33uart_txL4OUTPUT_LVCMOS33 DRIVE=4clkG2INPUT_LVCMOS33uart_rxM1INPUT_LVCMOS33 PULLMODE=UP
No SDRAM, flash, SPI or stray GPIO is constrained or driven. ecppack is called with no flash options; no -f appears in any build path.
Deterministic checks (repeated runs, identical results)
make check— exit 0: 163 model tests, 123 harness tests, repo consistency OK,SHA256SUMS matches tracked source artifactsmake fpga-boundary— the exact 8-bit ready/valid ASIC boundary holds;uio_oemask0b10110110unchangedmake sim—PASS: leanVM-b MinCore stream ALU,tb_uart_bridge PASSED- CI on the previous head:
executable-models,formal-and-lint,lean,systemverilogall green
Compatibility
No RTL, LPF or build-script change in this commit — host Python, docs and the generated SHA256SUMS only. Compatible with #17, which independently uses the same 01010f08 signature; the only shared file is the generated SHA256SUMS.
Scope — what this does not claim
No board was attached and no hardware was touched at any point: no JTAG, UART, bitstream load or flash. Everything above is simulation, source build and static audit. This is not physical validation, not full CPU or formal verification of the complete core, and not tapeout readiness or GDS. The .bit is proven reproducible, not proven correct on silicon.
|
@codex review Please re-review at head |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc3254f794
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The usage example shipped `--tx 03`, the SET128 opcode byte, but argparse only accepts command names. The first command a new user copies exited 2 before opening the port, so the documented entry point never worked. Point the example at `--tx set` and give a status example alongside it. PAYLOAD_BYTES now backs both the argparse choices and the width check, so the accepted commands and their payload widths cannot drift apart. Guarded by tests that pull every `python3 -m fpga_harness.ulx3s_uart` line out of the module docstring and ULX3S_SMOKE_AND_UART.md, parse each one, and check the payload width matches the command.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_204889d2-fda7-42f2-8054-d6ac3ccecd3d) |
uart_bridge.sv derives its abort pulse from any received 0x7f, not only from one in command position, so a 0x7f inside an operand tears the transaction down mid-flight. Reproduced in simulation: a SET128 whose payload byte 5 is 0x7f comes back as 15 bytes with 0xe0 from byte 5 on. 128-bit operands are arbitrary data, so ordinary vectors reach this -- about 6% of random SET payloads and 12% of random XOR/MUL operand pairs. Previously the host printed MATCH: False and exited 1, which is the exit status for "the board answered wrong" and points an investigation at the silicon instead of the link. Reject such operands up front and exit 2, and write the limitation down as an open bridge design question rather than an LSC-1 property. The ASIC takes ABORT on uio_in[6], a pin, and has no in-band abort byte, so this is the bridge's framing to settle. No RTL is changed here, so the committed bitstreams and their checksums still describe the shipped gates.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 218b6f9f1a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_bb64b50e-4fd7-4841-8c17-0b9314031171) |
Review findings on
|
| Criterion | State |
|---|---|
| Source build reproducible | Not verified here — yosys / nextpnr-ecp5 / ecppack absent from this container |
| Strict 25 MHz closure, no allow-fail | Script is correct (set -eu, no --timing-allow-fail, FREQUENCY PORT "clk" 25 MHz in LPF); archived logs report 154.37 MHz PASS — but not re-run by me |
| Bitstream SHA256 / provenance | sha256sum -c passes on all 4 committed artefacts; ecppack_uart.log is written by the build script but not committed and not in SHA256SUMS |
| LPF / pins reviewed | G2/B2/M1/L4 consistent with top level. wifi_gpio0 is not constrained or driven — on ULX3S that is the usual keep-alive; flagging for a board owner, I cannot test it |
| Safe-output audit | No SDRAM/flash pins constrained; no flash tool invoked; -f never passed |
0x7f transparency |
Open — item 2 above |
| PR #17 compatibility | File sets are disjoint except SHA256SUMS, which will conflict textually on the second merge; resolve with make checksums |
Verification run
make check → exit 0 (163 sim + 134 harness tests OK)
make -C test sim → PASS: leanVM-b MinCore stream ALU / tb_uart_bridge PASSED
sha256sum -c SHA256SUMS → 4/4 OK (bitstreams, config, svf)
pyserial applies the timeout set in open_port to every individual read, so threading args.timeout into recv_exact bounded each read rather than the call. A byte landing just before the deadline bought the read after it a whole fresh timeout. Measured against a virtual clock, a 2.00 s budget took 3.90 s: one byte at t=1.9 s, then a second read that blocked the full 2 s. A link that dribbles bytes stretches it further, since every arrival re-arms. Re-arm the port from the time actually left before each read and restore the caller's value afterwards, so the next transaction is unaffected.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_fbf468e6-43b3-43d8-bc8d-0e313bc03290) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad278d5853
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_42ee78d8-68d4-47e1-8050-05c0d7da8ab3) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4305b5d274
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Please review the fresh exact head |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fab44eb0e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_855e3726-b10e-409a-b684-5568321a29b5) |
|
Codex Review: Didn't find any major issues. Chef's kiss. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_e8e4d594-229d-45c4-8c31-a88d4da8b05a) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0717a3f68e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_b1e048a2-7c73-46ea-b2c4-edbaf1bb1013) |
|
@codex review Fresh exact-head review requested for Fresh verification on this head: The previously open dependency and repository-status threads were verified as already addressed in the preceding head and are now resolved; the serial-write deadline thread is addressed here and resolved. All CI/review/build receipts from before this push are stale. Hardware validation remains explicitly closed; do not merge on this request. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: edf5c26bc2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if hasattr(ser, "write_timeout"): | ||
| ser.write_timeout = remaining | ||
| written = _call_within( | ||
| lambda: ser.write(data[offset:]), |
There was a problem hiding this comment.
Preserve requested inter-byte pacing
When inter_byte_delay is nonzero and the serial backend accepts the full buffer—as pyserial normally does—this writes all bytes in one call, advances offset to len(data), and skips the delay condition below. Callers requesting paced transmission therefore receive no inter-byte gaps, unlike the previous byte-at-a-time implementation; limit each write to one byte when pacing is enabled while retaining the shared deadline.
Useful? React with 👍 / 👎.
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_73c8ecd6-c444-4f28-906b-046335d9cbf7) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de296a5724
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Add _validate_timeout rejecting math.inf/NaN/<=0 with clear ValueError (addresses Codex line ~382 thread) - Replace time.time() with time.monotonic() in drain() and recv_exact() (addresses Codex line ~128 thread) - Add adversarial TimeoutValidationTest covering inf, nan, negative, zero, large finite - Existing tests updated for monotonic clock Refs: PR #16 Codex review threads on timeout handling
|
@codex review on the new head — both timeout threads addressed |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_96b331f6-b7f8-4136-8515-dad01c0e2719) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c68877c6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ser.timeout = remaining | ||
| chunk = ser.read(n - len(buf)) |
There was a problem hiding this comment.
Bound response reads with the worker deadline
When a disconnecting or stalled serial backend blocks inside read() despite its mutable timeout, this direct call never returns, so the monotonic deadline is never checked again and --timeout cannot terminate the transaction. This is the same backend behavior already guarded for writes, flushes, and in_waiting; run the read through _call_within with remaining so the CLI can abandon the port at the configured deadline.
Useful? React with 👍 / 👎.
| if hasattr(ser, "timeout"): | ||
| ser.timeout = remaining | ||
| out.extend(ser.read(min(waiting, max_bytes - len(out)))) |
There was a problem hiding this comment.
Bound stale-input reads as well as availability queries
When in_waiting reports bytes but the subsequent backend read wedges—for example after a disconnect or because another reader consumed the snapshot—assigning ser.timeout alone does not protect against an implementation that blocks despite that value. The direct read() can therefore outlive the drain's settling deadline indefinitely; wrap it with _call_within using the remaining budget, just as the preceding in_waiting query is wrapped.
Useful? React with 👍 / 👎.
|
@codex review Fresh independent review requested for exact head |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_2c3916d8-fc21-46bf-8771-b366e232f1da) |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_73958b49-69cc-4ff8-8c93-0d2a6000a9ce) |
|
@codex review Fresh independent review requested for exact head |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
* fpga: add ULX3S v3.1.8 smoke + UART bridge skeleton (P0/P1) - Minimal 25 MHz counter/LED smoke_top + LPF (v3.1.8 pin-subset hypothesis) - UART RX/TX at 1 Mbaud, 2-flop sync, framing error handling - uart_bridge.sv: exact lean_silicon_lsc1 instantiation, byte buffering, POR, backpressure - New driver: fpga_harness/ulx3s_uart.py (explicit --port only) - Icarus TB skeleton: test/tb_uart_bridge.sv - SRAM-only build scripts; explicit prohibition of -f documented - Real yosys/nextpnr/ecppack run; 291.97 MHz post-route; bitstream SHA captured Ownership: PR #13 (host runtime), PR #15 (board_detect/SHA256SUMS) untouched. Worktree: fpga/ulx3s-smoke-uart-mincore @ 03926bb [skip ci] sprint deliverable * docs: ULX3S_SMOKE_AND_UART.md + local artefact manifest (P3) - Exact commands, tool versions, timing (291.97 MHz smoke / 160.26 MHz bridge) - Bitstream SHAs recorded - SRAM-only openFPGALoader recipe with explicit -f prohibition - Supported MinCore transaction list - Bounded claims only; no hardware execution evidence [skip ci] * fpga: repair UART bridge datapath, build paths and evidence integrity Every response byte was being corrupted or lost on the way to the host, and none of the checked-in verification could see it. RTL: - uart_tx released tx_ready at bit_cnt == 8, so data bit 7 lasted one 25 MHz clock instead of a full baud interval. A receiver sampled every zero-valued bit 7 as one (a SET echo of 00 arrived as 80). Ready now releases at 9. - The bridge's power-on "synchroniser" only ever shifted in ones, so rst_n was never low and no register in the bridge or the core was ever initialised. The core latched x into its error state and answered nothing. Replaced with a zero-initialised shift register that genuinely holds reset for 8 edges. - Both handshake directions now use a one-deep skid buffer whose ready deasserts on the handshake that fills it. Previously RX_VALID was pulsed against a ready sampled a cycle early (dropping bytes that arrived mid transaction) and TX_READY mirrored the serialiser a cycle late (letting the core hand over a second byte that overwrote the first). - Added a sticky rx_overrun probe that drives no logic, so the bench can assert no byte was ever displaced. Verification: - Replaced the tb_uart_bridge liveness window with a real 1 Mbaud 8N1 receiver. It fails on a missing byte, a short final bit, a bad stop bit, a wrong or extra byte, and on rx_overrun, across STATUS, SET, XOR, MUL, unknown opcode and abort recovery. Wired it into `make sim`, which previously never ran it. - ulx3s_uart.py --tx mul printed the board's reply and exited 0 whatever it was. It now computes the expected GF(2^128) product with both independent oracles in sim/model.py, cross-checks them, and gates the exit status. Added tests that feed complete but wrong 16-byte responses and require exit 1. Build and evidence: - build_smoke.sh and build_uart.sh resolved sources and output through one ".." too many, so the bridge build could not find any RTL and evidence landed outside the repository. Both now resolve the repository root from their own location and archive artefacts under the names the manifests use. - Committed the executable bit so `make -C fpga/ulx3s smoke` runs, and pointed the uart target at build_uart.sh instead of an echo. - The LPF had no frequency constraint, so a "25 MHz" claim was really nextpnr's 12 MHz default. Constrained it; both designs still close (291.97 / 154.37 MHz). - results/ SHA256SUMS named smoke.bit for a file archived as ulx3s_smoke.bit, so the integrity check could not run. The scripts now emit and self-verify both manifests. Regenerated ulx3s_bridge.bit from the repaired RTL; the digest is reproducible across three independent builds. - .gitignore's blanket *.log dropped the synthesis and route logs the manifest promised. Exempted the results directory and archived them. Not addressed by design: 0x7f still cannot appear as a payload byte, since the bridge treats it as an abort before command framing. Escaping it would change the wire protocol. Documented as a limitation instead. * fpga: make UART evidence and host drain fail-safe * fpga: resynchronize UART host transactions * fpga: validate the fixed STATUS signature in the UART host driver status is the default --tx, and it was the only command whose reply was printed without being checked. A misbuilt bridge emitting four arbitrary bytes passed the default board-smoke invocation with exit 0, so the check that is supposed to catch a broken build was the one command that could not fail. Compare against 01 01 0f 08 and exit 1 on mismatch, matching how set, xor and mul already gate their exit status. A test parses status_byte() out of leanvm_b_stream_alu.sv so the host constant cannot drift from the RTL. * fpga: make the advertised UART driver command line runnable The usage example shipped `--tx 03`, the SET128 opcode byte, but argparse only accepts command names. The first command a new user copies exited 2 before opening the port, so the documented entry point never worked. Point the example at `--tx set` and give a status example alongside it. PAYLOAD_BYTES now backs both the argparse choices and the width check, so the accepted commands and their payload widths cannot drift apart. Guarded by tests that pull every `python3 -m fpga_harness.ulx3s_uart` line out of the module docstring and ULX3S_SMOKE_AND_UART.md, parse each one, and check the payload width matches the command. * fpga: refuse UART operands the bridge cannot carry intact uart_bridge.sv derives its abort pulse from any received 0x7f, not only from one in command position, so a 0x7f inside an operand tears the transaction down mid-flight. Reproduced in simulation: a SET128 whose payload byte 5 is 0x7f comes back as 15 bytes with 0xe0 from byte 5 on. 128-bit operands are arbitrary data, so ordinary vectors reach this -- about 6% of random SET payloads and 12% of random XOR/MUL operand pairs. Previously the host printed MATCH: False and exited 1, which is the exit status for "the board answered wrong" and points an investigation at the silicon instead of the link. Reject such operands up front and exit 2, and write the limitation down as an open bridge design question rather than an LSC-1 property. The ASIC takes ABORT on uio_in[6], a pin, and has no in-band abort byte, so this is the bridge's framing to settle. No RTL is changed here, so the committed bitstreams and their checksums still describe the shipped gates. * fpga: bound recv_exact by its deadline, not by each read pyserial applies the timeout set in open_port to every individual read, so threading args.timeout into recv_exact bounded each read rather than the call. A byte landing just before the deadline bought the read after it a whole fresh timeout. Measured against a virtual clock, a 2.00 s budget took 3.90 s: one byte at t=1.9 s, then a second read that blocked the full 2 s. A link that dribbles bytes stretches it further, since every arrival re-arms. Re-arm the port from the time actually left before each read and restore the caller's value afterwards, so the next transaction is unaffected. * fpga: classify UART transaction timeouts * fpga: stage build evidence until success * fpga: publish build archive atomically * build: include publisher tests in source manifest * fpga: serialize shared archive publishers * fpga: reject surplus response bytes and record artefact provenance recv_exact returned the instant the nth byte landed, so a reply that was wrong but happened to start with the expected prefix left its surplus queued and the harness exited 0. Route every command through a settling reader that requires the link to fall silent, and classify a straggler as a transport fault rather than a match. The docs blamed the branch base for the archived bitstreams, but that revision contains no fpga/ulx3s file and so cannot have built them. Record the source revision and per-input digests from the build scripts themselves, and name the revision the artefacts actually reproduce from. * build: do not claim a match against an unidentified revision When the recorder ran outside a git checkout, every per-input comparison was skipped and the dirty flag stayed false, so the manifest reported inputs-match-revision: yes against a revision it had just recorded as unknown. Report the match as unknown in that case, so the archive claims only what was actually compared. * build: record the build recipe among the provenance inputs The recorder only digested $SOURCES and $LPF, so a build run from a locally edited build_smoke.sh or build_uart.sh -- different yosys or nextpnr flags, identical RTL -- still wrote inputs-match-revision: yes. The archive then attributed its bitstream to the recipe at HEAD, which never ran and cannot reproduce it. Both flows now hand their own script to the recorder. The test helper reads the recorded input set back out of the recorder invocation instead of restating it, so a build that drops an input fails rather than quietly narrowing what the archive claims to cover. * results: re-record source manifests over the widened input set The archived manifests predate the recipe becoming a recorded input, so they covered only the RTL and the LPF. Regenerated by the recorder itself from a clean checkout of 62e96e7, and the documented artefact revision moved to the revision that actually holds those inputs. Only the recorded input set moved: every RTL and LPF digest is byte-identical to the previous manifests, and no synthesis command changed, so the archived bitstreams are unaffected. * docs: clarify ULX3S provenance reachability * docs: make ULX3S artifact provenance reachable * fix: publish ULX3S artifacts atomically on macOS * fix: regenerate checksum inventory * Make ULX3S build support portable * Refresh ULX3S portable build evidence * fix: address four Codex P1/P2 threads for macOS atomic publish - tools/atomic_publish.py: use AT_FDCWD_DARWIN (-2) for renameatx_np on macOS - tools/portable_build_support.py: start build in own session and forward cancellation via os.killpg so grandchildren (yosys/nextpnr) are signalled - results/.../tool_versions*.txt: restore pinned OSS CAD Suite 0.67+94 / 0.10 / 1.4-79 - docs/ULX3S_SMOKE_AND_UART.md + manifests: update published SHA-256 to eb3d81ac... (smoke) and 7272b0d8... (bridge) matching refreshed artefacts Preserves: reproducible source at a472265, 25 MHz timing closure, pin/output safety (G2/B2/M1/L4 only), exact SHA256/provenance, protocol correctness (lean_silicon_lsc1 MinCore, no packet lanes touched). Focused tests exercising fixed modules: OK. Gate checks (smoke, boundary, consistency, checksums): OK. Provenance tests report expected mismatch for portable_build_support.py because committed artefacts record the pre-fix digest; bitstream regeneration is out of scope for this repair. Refs: discussion_r3651892558, discussion_r3651892560, discussion_r3651892562, discussion_r3651892564 * build: regenerate ULX3S evidence provenance * fix(provenance): correct docs digests and add atomic_publish.py to source manifests * fix(provenance): correct docs digests and add atomic_publish.py to source manifests * fix(provenance): record reachable artefact revision; add squash ancestry/reachability check * build: refresh ULX3S source evidence after merged provenance fix * fix(docs): correct provenance SHA and macOS verification path * fix(provenance): record manifest generator as a build input * build: regenerate provenance manifests and checksums * fix: reject empty checksum manifests * docs: align ULX3S provenance revision * rtl: integrate packetized LSC-1 FPGA path * test: preserve historical FPGA provenance * fix bounded drains and build input revalidation * build: archive packet FPGA routing outputs * refresh ULX3S source provenance * ci: run RTL differential in SystemVerilog job * fix: isolate canonical packet host imports * host: execute DEREF and JUMP programs * rtl: execute DEREF and JUMP packets * ci: include field encoder in source closure * build: refresh source checksums * fpga: bound UART drain availability queries * build: regenerate ULX3S evidence with clean Yosys * fix: make ULX3S UART host path installable and bounded * docs: reconcile ULX3S harness status snapshots * fix: reconcile ULX3S evidence status * fix: bound ULX3S UART writes by transaction deadline * fix: preserve paced UART writes * fix(uart): reject non-finite timeouts, use monotonic clock for deadlines - Add _validate_timeout rejecting math.inf/NaN/<=0 with clear ValueError (addresses Codex line ~382 thread) - Replace time.time() with time.monotonic() in drain() and recv_exact() (addresses Codex line ~128 thread) - Add adversarial TimeoutValidationTest covering inf, nan, negative, zero, large finite - Existing tests updated for monotonic clock Refs: PR #16 Codex review threads on timeout handling * Fix LSC1 packet inverse and XOR backsolve paths * Fix differential test checksum * build: refresh checksum inventory * fix(uart): bound all serial reads by deadline * Bound packet UART exchanges by deadline * Validate physical scalar results before retire * Validate retirement acknowledgement * Require fresh bounded packet startup * Tighten physical program completion checks * fpga: add safe Linux ULX3S preflight evidence * docs: scope MinCore state count accurately * fix: align jump faults and keep preflight signal-neutral * fix review trace and protocol accounting * docs: align host comparison status * chore: refresh checksum inventory * fix: decode instruction payloads before state guard * fix: align remaining packet fault precedence
ONE-HOUR HARDWARE SPRINT fallback after Codex mission 39f96ddc failed at OAuth. Goal: maximize Thomas's chance to later run physical SET/XOR/MUL on confirmed ULX3S v3.1.8 / LFE5U-85F over US1 UART.
Never merge. Never program hardware.
Scope / ownership
03926bbbbdc907d74214e4004985d36055d93a76, branchfpga/ulx3s-smoke-uart-mincorefpga/ulx3s/,fpga_harness/ulx3s_uart.py,test/tb_uart_bridge.sv,docs/ULX3S_SMOKE_AND_UART.md,results/ulx3s-smoke-uart-20260725/Confirmed board facts used
Architecture (hard rule)
ui_in/uo_out/uio_*oflean_silicon_lsc1leanvm_b_stream_alu(MinCore), not v1 packet executor — preservedDeliverables (stop at verified boundary)
P0 smoke
P1 UART + bridge + driver
uart_bridge.svinstantiates exactlean_silicon_lsc1; buffering + backpressure + PORfpga_harness/ulx3s_uart.py: explicit--portonly, SET/XOR/MUL fixture with independent expected for SET/XOR, stale drain, timeout, status/clearP2 tests
tb_uart_bridge.svcompiles and runsmake fpga-boundary,make fpga-harness,make simall greenP3 docs
docs/ULX3S_SMOKE_AND_UART.md: detached head, artefacts + checksums, expected output, power-cycle recovery, SRAM-onlyopenFPGALoader -b ulx3s <bit>, explicit-fprohibitionReal build artefacts (this run)
results/ulx3s-smoke-uart-20260725/ulx3s_smoke.bitSHA96eb9eda7421bac902eacaeced21eee0db9a80b8f2f2effdb52b515d68e0b2e3results/ulx3s-smoke-uart-20260725/ulx3s_bridge.bitSHAeacee77bdca486f173cacdb56709e442942eb85f9111eabcb96026976f8bae51Max frequency ... 291.97 MHz (PASS at 25.00 MHz)Max frequency ... 154.37 MHz (PASS at 25.00 MHz)Tests / exits
Supported transaction sequence (MinCore)
SET128 (0x03), XOR128 (0x01), MUL128 (0x02), STATUS (0x7e), CLEAR (0x7d). All via exact ui/uio ready/valid.
Blockers / limits
Bounded claims
lean_silicon_lsc1pin contract instantiated with no wide bypass@codex please review.
Note
Medium Risk
Large FPGA/harness addition with archived binaries and build tooling; datapath is explicitly unvalidated on silicon, but mistaken programming or treating archive evidence as hardware proof would be operationally risky.
Overview
Adds a ULX3S v3.1.8 deliverable: P0 LED smoke and P1 1 Mbaud UART design that wraps
lean_silicon_lsc1on the 8-bit ready/valid pins (no wide bypass), plusfpga_harness/ulx3s_uart.pyfor explicit-port SET/XOR/MUL/STATUS with oracle checks.Build and evidence:
fpga/ulx3s/build_smoke.shandbuild_uart.shrun yosys → nextpnr (25 MHz LPF) → ecppack, then publish atomically intoresults/ulx3s-smoke-uart-20260725/with SHA256 manifests, source manifests, and route logs. Newtools/source_provenance.py,atomic_publish.py, andportable_build_support.pyback locking, checksums, and squash-safe verification..gitignorekeeps scratch FPGA outputs out of git but allows archivedresults/**/*.log.CI and tests:
fetch-depth: 0on checkout for provenance.test/tb_uart_bridge.svis wired intomake sim. Harness tests cover provenance, atomic publish, portable builds, and UART driver edge cases.pyserial==3.5added torequirements.txt.Docs/status:
docs/ULX3S_SMOKE_AND_UART.mdrecords limits (SRAM-only, no hardware validation, 0x7f abort in payload). Harness README/BUILD_PLAN/INVENTORY andboard_detectnow say bitstreams exist but datapath remains not-validated without a programmed board and byte log.Reviewed by Cursor Bugbot for commit a94cd1a. Bugbot is set up for automated code reviews on this repo. Configure here.