Skip to content

[rtl,cheriot] Implement CHERIoT ISA - #2475

Merged
SamuelRiedel merged 6 commits into
lowRISC:masterfrom
SamuelRiedel:cheriot/pr
Aug 20, 2026
Merged

[rtl,cheriot] Implement CHERIoT ISA #2475
SamuelRiedel merged 6 commits into
lowRISC:masterfrom
SamuelRiedel:cheriot/pr

Conversation

@SamuelRiedel

@SamuelRiedel SamuelRiedel commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Implement CHERIoT ISA

This PR integrates the full CHERIoT ISA specification v1.0 into Ibex, building on the rebased microsoft/cheriot-ibex code (the first commit in this series).

Main features

This PR adds complete CHERIoT ISA support to Ibex. The key design decisions include:

  • Merged integration: All CHERIoT logic (decode, ID/EX control, CSRs, LSU, and register file) is merged directly into the existing Ibex pipeline modules. Only the capability execution stage (ibex_cheriot_ex) is a standalone new module.
  • Spec-aligned types (ibex_cheriot_pkg): Capability types are packed structs whose lower bits match the spec encoding 1:1, so their vector forms match the format in memory. Two forms are used throughout the design: cap_t (compressed capabilities with correction factors, which live in the register file and on load/store ports) and decoded_cap_t (uncompressed bounds and permissions, which are used inside the core). The correction factors (2 bits) are stored in the register file to prevent the need for expensive comparators on every capability read.
  • No revocation engine inside the pipeline: The TRVK filter (ibex_trvk) is connected after the LSU in ibex_top to keep the pipeline clean.
  • No stack zeroing and no TBRE engine: The cheri_stkz and cheri_tbre modules from the Microsoft fork are not ported. The scope of this PR is limited strictly to the ISA.
  • Capability register file: The lower 16 integer registers carry a cap_t metadata word. The upper 16 registers are integer-only. All three register file variants (FF, FPGA, and latch) are updated consistently.
  • All modules are renamed from cheri_* to ibex_cheriot_* or ibex_*, and legacy instruction formats are dropped.

Parametrization

  • BaseIsa (ibex_pkg::base_isa_e): This is a static build-time switch. BaseIsaRV32I elaborates away all CHERIoT hardware (no change through this PR). BaseIsaRV32IorCHERIoT includes both RV32I and CHERIoT modes.
  • cheriot_enable_i (ibex_mubi_t): This is a dynamic runtime switch. When CHERIoT hardware is present and this signal is deasserted, the core behaves as a plain RV32I core. This signal has to be held constant throughout execution with one exception. Ibex can switch from Off (RV32I mode) to On (CHERIoT mode) exactly once at runtime. Ibex can never switch from CHERIoT mode to RV32I mode without a reset.

Verification

  • RV32I regressions pass with BaseIsaRV32I.
  • BaseIsaRV32IorCHERIoT with cheriot_enable_i turned off runs all existing RV32I tests without regressions.
  • CHERIoT mode (cheriot_enable_i turned on) is verified with basic smoketests.
  • Full CHERIoT DV (both directed and constrained-random) is coming in a follow-up PR.
  • OpenTitan now has BaseIsaRV32IorCHERIoT enabled, meaning we have both ISAs available in this configuration. However, all current testbenches (cosim, simple systems, formal) still disable CHERIoT using the cheriot_enable_i signal.

To-Dos for follow up PRs

  • Implement register file register sharing.
  • CSR register sharing.
  • DV PRs for smoketest
  • DV PR for cosim with cheriot sail

@andreaskurth andreaskurth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SamuelRiedel for this work! As agreed, the first commit imports the Microsoft's CHERIoT implementation, which is based on an earlier fork of Ibex, and rebases it onto the current Ibex, and the second commit reimplements the CHERIoT ISA in a way that fits Ibex's architecture and design very neatly. This two-commit approach makes code provenance and differences explicit, which is important.

I checked your CHERIoT ISA implementation against the v1.0 spec (chapter 9 Sail listings, the §8 encoding tables, and §7.13) and the approved OpenTitan RFC and am convinced that many/most aspects are correct - nice!

One item needs changing in this PR, I think: The register file doesn't seem to implement the RFC's shared 16x65-bit arrangement.

The remaining comments are functional and conformance gaps, and some of them I think are major, but none of them blocks this PR -> follow-up issues.

Comment thread rtl/ibex_register_file_ff.sv Outdated
Comment thread rtl/ibex_compressed_decoder.sv
Comment thread rtl/ibex_cs_registers.sv Outdated
Comment thread rtl/ibex_cheriot_ex.sv Outdated
Comment thread rtl/ibex_cs_registers.sv
Comment thread rtl/ibex_cheriot_pkg.sv
Comment thread rtl/ibex_cheriot_pkg.sv Outdated
Comment thread rtl/ibex_cheriot_ex.sv
Comment thread rtl/ibex_if_stage.sv
Comment thread rtl/ibex_cheriot_pkg.sv Outdated
Comment thread rtl/ibex_top_tracing.sv
Comment thread rtl/ibex_compressed_decoder.sv
Comment thread rtl/ibex_top.sv

@thommythomaso thommythomaso left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick review focusing mainly on the interfaces to the rest of the CHERIoT system.

This commit takes the entire cheriot-ibex development since it forked
from lowrisc/ibex (from bdf2f2b)

This commit was ported from https://github.com/microsoft/cheriot-ibex

Co-authored-by: Kunyan Liu <kunyanliu@microsoft.com>

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>

@andreaskurth andreaskurth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for turning these around so quickly, @SamuelRiedel; nice update!

I re-reviewed only the delta. Three things left: the register file is still only half-done, the NMI/MEPCC item is still open, and one of my earlier requests turned out to be too broad and has introduced a small divergence (details inline).

Comment thread rtl/ibex_register_file_ff.sv Outdated
Comment thread rtl/ibex_cs_registers.sv Outdated
Comment thread rtl/ibex_cheriot_ex.sv Outdated

@nasahlpa nasahlpa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Sam for your work!

I looked into the code from a fault injection perspective. I can confirm that in non-CHERI mode the lockstep comparison is identical to before. In CHERI mode, all relevant new signals are compared.

I also can confirm that the register file split (main and shadow core RF instances) look secure in the RF variant we use.

Regarding functionality - have you tested if switching from non-CHERI mode to CHERI mode works with the register file? Or do we need to clear the register file before doing the switch?

Comment thread rtl/ibex_lockstep.sv Outdated
Comment thread rtl/ibex_lockstep.sv Outdated
Comment thread rtl/ibex_top.sv
input prim_ram_1p_pkg::ram_1p_cfg_req_t [ibex_pkg::IC_NUM_WAYS-1:0] ram_cfg_icache_data_i,
output prim_ram_1p_pkg::ram_1p_cfg_rsp_t [ibex_pkg::IC_NUM_WAYS-1:0] ram_cfg_icache_data_o,

input ibex_mubi_t cheriot_enable_i,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to have that MuBi encoded. Would it make sense to add a MuBi encoding checker at some place and connect it with an alert? Or are you planning to do this in OpenTitan where you instantiate Ibex?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to do this in Ibex. Ideally in the duplicated core, right? I will add this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this: 0cc88fb

I ran into an issue that the check needs to be gated somehow, otherwise we have combinatorial alerts at reset. Especially the shadow core triggers alerts initially because we reset the shadow inputs to zero, so during reset, the mubi is invalid. Do you think we should directly reset the mubi there to Off?

Instead of using the reset as a gating signal now, I gated with the fetch_enable, which should indicate whether we're actually executing instructions. @nasahlpa do you think that is a good solution?

Comment thread rtl/ibex_cs_registers.sv
assign rdata_b_o = (raddr_b_i[4] && !cheriot_enabled) ?
DataWidth'(rf_shared[raddr_b_i[3:0]]) : rf_data[raddr_b_i[3:0]];

// Cap: gated to CapWordZeroVal in non-CHERIoT mode; x0 always returns CapWordZeroVal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it guaranteed that CapWordZeroVal is a valid ECC code word? Maybe it would make sense to add an assertion like: WordZeroVal == CapWordZeroVal

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think neither is guaranteed to be a valid ECC to the parameter in the main register file. We would need an assertion that checks whether ECC(ibex_top.regfile.WordZeroVal) == ibex_top.lockstep.regfile.WordZeroVal, right?

But the word and cap don't need to match. While neither ISA defines register values at reset, we initialize them mainly to establish the initial ECC. And now we set the lower half to "word" values, upper half with "capability" values mainly because the width is different. Both are currently zero. After reset, any writes to the register files will keep their values and ECC permanently synchronized, regardless of how the registers are used.

@nasahlpa nasahlpa Aug 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think neither is guaranteed to be a valid ECC to the parameter in the main register file. We would need an assertion that checks whether ECC(ibex_top.regfile.WordZeroVal) == ibex_top.lockstep.regfile.WordZeroVal, right?

Ah yes, right.

After reset, any writes to the register files will keep their values and ECC permanently synchronized, regardless of how the registers are used.

Agreed, but what happens if we are switching from non- CHERI mode to CHERI mode? If we are switching the mode, aren't the values computed in the old mode checked with the new mode code?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we switch mode, currently we don't change any registers. So all of them will have valid value+ECC pairs. And we always compare the full width of the register, even if the upper bits are never accessed in the non-CHERIoT mode. So for the ECC checker, nothing changes depending on mode. And a switch should be treated like a fresh boot, so the registers will have to be initialized by sw.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of this output of the register file:
https://github.com/SamuelRiedel/ibex/blob/a4c00bf4065746b166794621a4a1f68daca1c804/rtl/ibex_register_file_ff.sv#L227-L228
When we are switching from non-cheri mode to cheri mode we could have old data in rf_shared. And then, rcap_a_o gets checked by:
https://github.com/SamuelRiedel/ibex/blob/a4c00bf4065746b166794621a4a1f68daca1c804/rtl/ibex_core.sv#L1263
here we have a different decoder than for the plain data:
rdata_o gets checked by:
https://github.com/SamuelRiedel/ibex/blob/a4c00bf4065746b166794621a4a1f68daca1c804/rtl/ibex_core.sv#L1227
So SW would basically need to be initialized by SW as you mentioned.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. The fact that the ZeroECC was the same independent of the decoder led me to believe that zero extending a 32-bit word on the wider ECC path would result in the same ECC value because the zeros don't add to the sum. But for this to work, we would have to use the same generator matrix, right? Would this be possible?

We also have to be careful about x16 because it's a real register in the rv32i mode, but always reads zero as the x0 capability part in CHERIoT. Currently, this should be fine, because in CHERIoT, we hardcode the output to ZeroCap and ZeroECC.

I see a few options here:

  • Force the software to zero out the registerfile before the switch. The ZeroECC will match and the x16 corner case is covered.
  • Add hardware to reset the registers when the switch happens. At the switch, we have to flush the pipeline anyway and could also add some logic to hardware enforce that the regsiters are reset to the values we want for the capabilities.
  • Always route out the full registerwidth into the ECC. So even in non-cheriot mode, we could have the upper bits routed to the ECC path, even though they will always be zero in this mode. Then we always use the narrow ECC path on the lower registers and the wider ECC path for the upper ones and nothing changes from the ECC's perspective between switches. But we have to handle the width truncation outside of the registerfile, which is not very clean.

What do you prefer? I will also open a dedicated issue for this discussion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked here: #2480

Comment thread rtl/ibex_register_file_ff.sv

@andreaskurth andreaskurth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @SamuelRiedel! Everything from the last round is resolved, nice 👍

A few more changes: the register-file sharing has surfaced a disagreement with the decoder that I think has to be fixed alongside it, plus three smaller items. Details inline.

Comment thread rtl/ibex_decoder.sv Outdated
Comment thread rtl/ibex_register_file_ff.sv
Comment thread rtl/ibex_cheriot_ex.sv Outdated
Comment thread rtl/ibex_lockstep.sv

@andreaskurth andreaskurth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, many thanks @SamuelRiedel for all this work!

@andreaskurth
andreaskurth added this pull request to the merge queue Aug 20, 2026
@andreaskurth
andreaskurth removed this pull request from the merge queue due to a manual request Aug 20, 2026
Integrate the CHERIoT (Capability Hardware Extension to RISC-V for IoT)
extension into Ibex, building on the previously rebased cheriot-ibex
code. This merges CHERIoT logic directly into the core Ibex RTL and
renames all `cheri_*` instances to `cheriot_*`.

New modules:
- `ibex_cheriot_ex.sv`: Standalone CHERIoT execution stage (formerly
  `cheri_ex`). Handles capability arithmetic, bounds checks,
  sealing/unsealing, PCC updates, and LSU capability requests.
- `ibex_cheriot_pkg.sv`: Shared types, constants, and functions matching
  the v1.0 specification (formerly `cheri_pkg.sv`, heavily
  restructured).

Removed cheriot-ibex modules:
- `cheri_pkg.sv`, `cheri_decoder.sv`, `cheri_ex.sv`, `cheri_regfile.sv`,
  `cheri_stkz.sv`, `cheri_tbre.sv`, `cheri_tbre_wrapper.sv`,
  `cheri_trvk_stage.sv`

CHERIoT integration into existing Ibex modules:
- `ibex_decoder.sv`/`ibex_compressed_decoder.sv`: Decode CHERIoT
  instructions. Emit `cheriot_op_t` and selector signals to EX.
- `ibex_id_stage.sv`/`ibex_controller.sv`: Add stall and flush logic
  for capability exceptions and CHERIoT execution cycles.
- `ibex_cs_registers.sv`: Add CHERIoT CSRs/SCRs.
- `ibex_load_store_unit.sv`: Support capability-tagged loads and stores.
- `ibex_register_file_{ff,fpga,latch}.sv`: Store capability metadata
  (`cap_t`) alongside data registers.

ISA selection and runtime control:
- `BaseIsa`: Build parameter (`ibex_pkg::base_isa_e`) to select between
  `BaseIsaRV32I` (RV32I only, CHERIoT logic not synthesized) and
  `BaseIsaRV32IorCHERIoT` (CHERIoT and RV32I synthesized).
- `cheriot_enable_i`: Dynamic runtime switch (`ibex_mubi_t`) to toggle
  the core between plain RV32I and CHERIoT modes.

Revocation:
- Removed the internal revocation engine. The existing `ibex_trvk.sv`
  filter is now connected after the LSU in `ibex_top.sv` and updated to
  use shared `ibex_cheriot_pkg` types.

Key differences from microsoft/cheriot-ibex:
- Renamed all modules (`cheri_*` -> `ibex_cheriot_*` / `ibex_*`).
- Merged all stages (except EX) into existing Ibex modules instead of
  adding parallel pipelines.
- Dropped legacy instruction formats, stack zeroing (`cheri_stkz`), and
  the TBRE engine (`cheri_tbre*`).
- Implemented capability types as packed structs that match the v1.0
  spec bit layout exactly.
- Replaced `CHERIoTEn` and related build parameters with a single
  `BaseIsa` parameter and the `cheriot_enable_i` runtime signal.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
In BaseIsaRV32IorCHERIoT, share the upper half of the regular register
file with the capability registers.

In BaseIsaRV32I, the registerfile is unchanged, except a linter fix in
the RV32E mode and added assertions.

Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
Signed-off-by: Samuel Riedel <sriedel@lowrisc.org>
@SamuelRiedel

Copy link
Copy Markdown
Contributor Author

Thanks for all the quick and helpful feedback! I've just squashed the fixes into their respective commits as a final cleanup.

@SamuelRiedel
SamuelRiedel added this pull request to the merge queue Aug 20, 2026
Merged via the queue into lowRISC:master with commit 8b8ee08 Aug 20, 2026
13 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants