fpga-bringup — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited fpga-bringup (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Bringing up an FPGA on the bench means three things: get the bitstream in over a real transport, drive the design's inputs, and observe its outputs. Most early failures are transport and pin-mapping problems, not logic problems.
Core principle: Bring the transport up first and prove it independently, before you trust anything the design does. A bitstream that "loaded" but didn't is the most expensive hour on the bench.
Before any design-level work, prove the link end to end:
CONFIG opcode) to enter config mode, then shift the bitstream.Before you blame the bench, confirm the bitstream's configured clock is at or below the design's real Fmax. The PLL output, UART baud divisor, and timer timebase all derive from it. A bitstream configured for 48 MHz on logic that only times at 29 MHz fails with setup violations AND a wrong baud rate, which looks exactly like a wiring or transport fault. Regenerate at a clean integer PLL divide below Fmax. See fpga-synthesis-fit.
A dead-silent board with a clean configuration load is very often a PLL that never locked, not a logic or wiring fault. When the design gates reset on ~LOCK (the common sysReset = porReset | ~LOCK), a PLL that never locks holds the core in reset forever and every output stays dead.
Check the VCO math from the emitted PLL parameters BEFORE you flash:
fVCO = fIN / CLKI_DIV * CLKFB_DIV. A hardcoded CLKI_DIV = 1 that cannot divide a 48 MHz oscillator down to a 24 MHz system clock drives fVCO to 1584 MHz, out of band, no lock. Realize the ratio as a reduced fraction (CLKFB_DIV : CLKI_DIV by gcd) so the divider is honest.FREQUENCY PORT "clk" in the .lpf) must carry the OSCILLATOR frequency on the pin, not the system frequency. Constrain it to the system freq and the tool derives the VCO from the wrong input (24/2*25 = 300 MHz, out of band) and writes analog and loop settings the silicon never runs, even though the gateware is correct.A second PLL whose LOCK is unconnected (a DDR PHY PLL that does not gate core reset) can carry the same divider bug silently until you bring that block up.
Before bench guessing, partition logic from analog in simulation. Generate the design with NO target (sim-passthrough clock, external reset, flop memories, no PLL or block-RAM blackboxes), dump the SystemVerilog, and run it under a fast compiled simulator (Verilator). If the design streams its output here, the LOGIC is correct and the suspect is the PLL or a primitive the flop sim does not model. An interpreted RTL sim is usually too slow for a full boot (tens of thousands of cycles to first output); a compiled sim does it in a fraction of a second after a one-time compile. Lower the clock or UART divisor to shrink the run.
To sim the REAL netlist instead of the flop stand-in, supply functional models for the vendor blackboxes. The yosys ecp5/cells_sim.v DP16KD is a pure stub: it declares the INITVAL params but has no read or write logic, so a real-netlist sim reads every block-RAM ROM as zero and the core hangs at instruction zero. That hang is a SIM ARTIFACT, not a hardware bug. Write a functional primitive model (unpack INITVAL per fpga-synthesis-fit's dp16kd-initval-packing.md, clocked read and write) plus a behavioral PLL and config stub, and the real netlist runs, isolating the remaining failure to the analog PLL the stub cannot model.
When every simulation passes but the assembled design is dead on the board and you cannot observe internals, do NOT keep theorizing. Build a ladder of tiny bitstreams, each bit-banging ONE diagnostic byte out the UART pin, each isolating a single layer, from the rawest signal upward:
Each probe builds in about two minutes and routes in seconds. When every block passes in isolation, the conclusion is forced: it is the assembled, timing-loaded design, not any one block. Prefer a NON-latching live indicator (a heartbeat reflecting the current state) over a latching one; "it got a bit further when I slowed the clock" can be a latching-probe artifact or routing variation, not real progress, on a marginal high-utilization design.
Keep a pad map in config (a file, not scattered constants): logical signal name -> device pad -> host GPIO line. Every drive and observe goes through this map.
prepare, close them on release. Own the lifecycle so a crashed run doesn't leave lines claimed.RunVector is: set inputs per the pad map, pulse/settle, read outputs per the pad map, compare to expected.| Smell | Do instead |
|---|---|
| Assuming load worked because the shift finished | Read back DONE/status |
| IR width hardcoded in one spot | Parameterize it; confirm against the part |
| Pin numbers scattered through code | One pad map, name -> pad -> GPIO line |
| Sampling outputs immediately | Settle for propagation first |
| sysfs GPIO | character-device GPIO (cdev/gpiod) |
| Skipping IDCODE | Always read IDCODE before trusting the link |
| Dead UART, chasing loop-filter attrs | Check the VCO band and the .lpf input freq first |
| Theorizing about why the chip is silent | Probe one layer at a time out the UART pin |
| Trusting a real-netlist sim that hangs at instr 0 | yosys DP16KD is a stub; supply a functional model |
| 0-byte UART capture read as a boot hang | Confirm the flash command actually ran; zsh no-op |
| OpenOCD reads garbage dtmcontrol, blaming the RTL | Reload the bitstream and retry; it is often transient |
| Early boot prints missing | UART drops the first seconds after reconfig; re-print late |
| Building a DQS-strobed read capture on the open flow | Capture on CK plus calibration; DQS pins are not clock-capable |
| Running DDR3 at 200 MHz CK to "de-risk" | Below the DLL's slowest rated bin; drive 400 MHz plus or gate on DQS |
| A lane reads exactly 0x00, chasing the eye | Exact 0x00 is a whole-strobe framing failure, not tight timing |
| Tuning writes before proving the read path | Read the DDR3 MPR to prove the lane and read path first |
creek core's first OrangeCrab 25F bring-up needed five stacked fixes (two PLL/VCO bugs, a regfile write-mode quirk, and two logic bugs), each isolated by the probe ladder and the partition sim above.onchip-microprobe-ladder.md in this directory: the bit-bang-one-byte-per-layer diagnostic ladder for a silent chip, the silicon-only DP16KD x18-runtime-write gotcha, and the faithful-primitive partition sim.ddr3-read-capture-open-flow.md in this directory: the DDR3 read-path bring-up on Spartan-7 with openXC7 (capture on CK plus calibration not a DQS strobe, DQS pins are not clock-capable, do not underclock below the DLL bin, IDELAYCTRL for absolute taps, exact-0x00 is a strobe framing fault, MPR proves a lane read-perfect, CL+1 shifts capture by a whole memory clock).bench-and-capture-gotchas.md in this directory: the silent no-ops that look like hangs (a multi-word command that zsh does not word-split, UART dropping the first seconds after reconfig, stale UART readers splitting the stream, transient garbage JTAG state), and widening a marginal capture eye by slowing the clock with its analog-spec floor.differential-verification for comparing observed outputs against a golden model, and fpga-synthesis-fit for the clock and block-RAM details.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.