[clkmgr] jitter_regwen does not block writes to jitter_enable on official CW340 bitstream
Nobody has claimed this yet.
- Dominant language
- SystemVerilog
- Stars
- 3.6k
- Forks
- 1.1k
- Avg merge
- 2d 22h
- Merged PRs (30d)
- 141
Description
## Summary
`jitter_regwen` (the write-enable lock for `CLKMGR.JITTER_ENABLE`) does not actually gate writes on the CW340 bitstream we've tested. `JITTER_REGWEN` reads back correctly as locked (`0`) after `dif_clkmgr_lock_jitter_enable()`, but a subsequent write to `JITTER_ENABLE` still lands. We believe this is a real defect in the lock mechanism itself, not a test-methodology artifact -- see Scope below.
## Affected IP
`hw/top_earlgrey/ip_autogen/clkmgr` -- `jitter_regwen` / `jitter_enable` register pair (`clkmgr_reg_top.sv`).
## Environment
- Board: CW340 (`lowrisc_systems_chip_earlgrey_cw340_0.1.bit`)
- Debug probe: HyperDebug, `interface=hyper340`
- Bitstream commits tested (bisected the full range): `c10d1097fc9a2c930497e80013f0cddb815a4262` (2026-02-27) through `af43bb2ffcc9f1d638c3882fac713a30eddf2424` (2026-08-10) -- **all commits in this range reproduce the failure** when flashed via the standard UART bootstrap flow (`opentitantool ... bootstrap`).
## Minimal repro (isolated from `clkmgr_jitter_test`)
`clkmgr_jitter_test` flips `jitter_enable` before ever reaching its lock-verification step, which entangles two different behaviors (see below). Isolated repro, no enable/disable toggling before the lock:
```c
dif_clkmgr_t clkmgr;
dif_clkmgr_init(mmio_region_from_addr(TOP_EARLGREY_CLKMGR_AON_BASE_ADDR), &clkmgr);
// Right after reset.
uint32_t regwen = mmio_region_read32(clkmgr.base_addr, CLKMGR_JITTER_REGWEN_REG_OFFSET);
// regwen == 1 (open), JITTER_ENABLE == 0x9 (kMultiBitBool4False), as expected.
dif_clkmgr_lock_jitter_enable(&clkmgr); // writes JITTER_REGWEN = 0
regwen = mmio_region_read32(clkmgr.base_addr, CLKMGR_JITTER_REGWEN_REG_OFFSET);
// regwen == 0 -- correctly reads back as locked.
uint32_t before = mmio_region_read32(clkmgr.base_addr, CLKMGR_JITTER_ENABLE_REG_OFFSET);
mmio_region_write32(clkmgr.base_addr, CLKMGR_JITTER_ENABLE_REG_OFFSET, kMultiBitBool4True);
uint32_t after = mmio_region_read32(clkmgr.base_addr, CLKMGR_JITTER_ENABLE_REG_OFFSET);
// Expected: after == before (write blocked by regwen)
// Observed: before=0x9, after=0x6 -- the write went through despite regwen reading 0.
```
As a control, we ran the identical procedure against `measure_ctrl_regwen` / `IO_MEAS_CTRL_EN` in the same `clkmgr` instance (same reggen-generated RTL pattern, `gated_we = we & regwen_qs`, same 4-bit mubi encoding). That one blocks the write correctly (`before == after`), which rules out a chip-wide or `clkmgr`-instance-wide REGWEN defect -- this looks specific to the `jitter_enable` path.
## Relationship to `clkmgr_jitter_test` and to boot/load methodology
We believe this defect is also why `clkmgr_jitter_test` fails for us on every bitstream we've tried, including `af43bb2f` (2026-08-10), the commit our internal comparison shows a PASS for on the mainline side. When we bisected and re-tested `af43bb2f` ourselves via the standard UART bootstrap flow, it reproduced the same failure. Our understanding is that the PASS result for this commit was obtained via a backdoor JTAG loading flow rather than UART bootstrap; we don't have tooling for that flow, so we can't fully rule out a difference in boot/reset sequencing -- but since the isolated repro above doesn't depend on `clkmgr_jitter_test` at all, we believe the underlying regwen defect is real regardless of load method.
Digging further, the failure mode is broader than "regwen doesn't gate": within a single boot, the **first** write to `JITTER_ENABLE` always lands, and **every subsequent write** (locked or not, whatever value) is silently dropped. `clkmgr_jitter_test` flips `jitter_enable` twice before ever reaching its lock-verification step, so it fails earlier (on the second toggle) for what looks like an unrelated reason, and never actually exercises the regwen path at all.
## Scope / impact
We don't believe this affects any currently-shipping ROM/firmware -- boot code writes `JITTER_ENABLE` exactly once (based on the `CREATOR_SW_CFG_JITTER_EN` OTP value) and never writes to it again, so the "second write" failure mode is never triggered today.
That said, we don't think this should be read as "no real impact": `jitter_regwen` is a security-lock primitive (the same pattern is used throughout this chip to let trusted boot code lock a setting against later/untrusted code), and it currently doesn't work at all. Anyone who exercises this lock as a real hardening step, or who needs to toggle jitter more than once per boot for any reason (e.g. power management), will hit this on the shared official bitstream, independent of test methodology or which fork they're running.
We haven't been able to narrow this down further than the bitstream level; a synthesized-netlist/timing investigation (Vivado) is out of reach from the firmware side. Happy to share our diagnostic test files or full logs if useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in hw/top_earlgrey/ip_autogen/clkmgr/clkmgr_reg_top.sv and compare the jitter_regwen/jitter_enable path with measure_ctrl_regwen/IO_MEAS_CTRL_EN, then run the isolated repro described in the issue. Done means writes to JITTER_ENABLE remain blocked after JITTER_REGWEN reads 0 on the CW340 bitstream, with the failure narrowed beyond the firmware-side observations.
Written by the indexing model from the issue text.
Assessment
- Domain
- embedded-iot, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100