RV32IMC
RV32IMC is the 32-bit RISC-V base integer ISA (
I) augmented with the integer multiply/divide extension (M) and the compressed-instruction extension (C). It is the dominant ISA subset for shipping RISC-V microcontrollers, used by Espressif’s ESP32-C3 (a four-stage in-order core clocked at up to 160 MHz with 16 PMP regions) and matched directly by Rust’sriscv32imc-unknown-none-elfbare-metal target (Rust Platform Support). The triplet hits a sweet spot: rich enough for real C and Rust programs (mul/div without software helpers, decent code density), small enough to implement in a few thousand FPGA LUTs, and bounded enough to verify exhaustively. The definitely-not-esp32 project implements exactly this subset (plus Zicsr for traps).
This note covers RV32IMC specifically: what each letter adds, the encoding, the register file, the rationale for the triplet as an embedded baseline, and the cross-references to ESP32-C3 and the Rust toolchain. The general RISC-V ISA design is in RISC-V Instruction Set Architecture; the CSR-access extension typically paired with this subset is in Zicsr Extension.
Mental Model
flowchart LR subgraph I["I: Base Integer ISA (RV32I v2.1)"] I1["~47 instructions"] I2["32 x 32-bit registers (x0..x31)"] I3["6 formats: R, I, S, B, U, J"] I4["Fixed 32-bit encoding"] end subgraph M["M: Multiply/Divide (v2.0)"] M1["mul, mulh, mulhsu, mulhu"] M2["div, divu, rem, remu"] M3["No FPU required"] end subgraph C["C: Compressed (v2.0)"] C1["16-bit encodings"] C2["~25 common ops compressed"] C3["bits[1:0] != 11 marks 16-bit"] C4["~30 percent code size reduction"] end I --> RV32IMC[("rv32imc")] M --> RV32IMC C --> RV32IMC RV32IMC --> ZICSR["(usually paired with Zicsr for traps)"]
The three named pieces of RV32IMC. What it shows: the base I gives 32 registers, fixed-length instructions, six formats, and the load/store discipline; M adds eight multiply/divide opcodes that hardware-accelerate what would otherwise be software loops; C halves the size of common instructions when they fall in the compressible patterns. The insight to take: RV32IMC is a small, well-bounded set, finishable in a small team’s lifetime and large enough to compile real programs.
I: The Base Integer ISA
RV32I version 2.1 is the mandatory foundation. The encoding is fixed at 32 bits, with six instruction formats sharing a tight 7-bit opcode field, three 3-bit funct fields, and three 5-bit register specifiers (RISC-V Unprivileged ISA, 2024-11-26 release).
Register file. Thirty-two integer general-purpose registers, x0 through x31, each 32 bits wide (XLEN=32). x0 is hardwired to zero: writes to it are ignored, reads always return zero. This is a deliberate trick that lets common idioms (clear a register, materialize a small constant, unconditional jump) reuse the standard arithmetic instructions: addi rd, x0, 5 produces 5; add rd, x0, x0 produces 0; jal x0, target is an unconditional jump that discards the return address. The remaining 31 registers have ABI-assigned roles laid out in the calling convention.
Instruction formats. Six are defined (RISC-V Unprivileged ISA):
| Format | Use | Layout (high to low) |
|---|---|---|
| R | register-register arithmetic | funct7[7] | rs2[5] | rs1[5] | funct3[3] | rd[5] | opcode[7] |
| I | register-immediate, loads, system | imm[12] | rs1[5] | funct3[3] | rd[5] | opcode[7] |
| S | stores | imm[7] | rs2[5] | rs1[5] | funct3[3] | imm[5] | opcode[7] |
| B | conditional branches | like S, with imm bits scrambled for sign-extension |
| U | upper immediate | imm[20] | rd[5] | opcode[7] (for lui, auipc) |
| J | jump-and-link | imm[20] | rd[5] | opcode[7] (for jal) |
The register-specifier and funct3 fields are at fixed bit positions across all formats. This is a deliberate design decision so the decoder can read rs1, rs2, rd, and funct3 from the same wires before knowing the format; the immediate-bit reshuffling between S and B (and between U and J) is hidden by a simple shuffle network. The cost of the unconventional immediate layout is that disassemblers see “weird” hex; the win is that the actual hardware decode logic is unusually simple.
ABI register names. The names you see in objdump are the ABI mnemonics, not the architectural xN names (RISC-V Unprivileged ISA):
| Arch | ABI | Role |
|---|---|---|
x0 | zero | hardwired zero |
x1 | ra | return address |
x2 | sp | stack pointer |
x3 | gp | global pointer |
x4 | tp | thread pointer |
x5-x7 | t0-t2 | temporary (caller-saved) |
x8 | s0/fp | saved (callee-saved) / frame pointer |
x9 | s1 | saved |
x10-x11 | a0-a1 | argument and return value |
x12-x17 | a2-a7 | arguments |
x18-x27 | s2-s11 | saved |
x28-x31 | t3-t6 | temporary |
The s0-s1 / s2-s11 block is split because s0 and s1 fall in the x8-x15 range that the compressed extension can encode tightly; putting frame pointer and the most-frequently-saved register there minimizes the spill cost in compressed code.
Instruction inventory. RV32I defines roughly 47 base instructions across these categories (RISC-V Unprivileged ISA):
- Integer compute (R-type, register-register).
add,sub,sll,slt,sltu,xor,srl,sra,or,and(10). - Integer compute (I-type, register-immediate).
addi,slti,sltiu,xori,ori,andi,slli,srli,srai(9). - Upper immediates (U-type).
lui(load upper immediate, materializes a 20-bit constant in the high bits of a register),auipc(add upper immediate to PC, used to form PC-relative addresses) (2). - Loads (I-type, base + 12-bit signed offset).
lb,lh,lw,lbu,lhu(5). - Stores (S-type).
sb,sh,sw(3). - Conditional branches (B-type, PC-relative, 13-bit signed displacement).
beq,bne,blt,bge,bltu,bgeu(6). - Unconditional jumps.
jal(J-type, 21-bit signed displacement),jalr(I-type, indirect jump via register + 12-bit offset) (2). - Memory ordering and system.
fence(memory-ordering barrier),ecall(environment call, used as syscall),ebreak(debugger breakpoint) (3).
That gives 40 directly arithmetic/control instructions plus 7 system/memory; the canonical RV32I count is “47” (RISC-V docs, Unprivileged), depending on whether you count nop (canonically addi x0, x0, 0) and the pseudo-mnemonics. The set is small because the design philosophy is to provide primitives; richer operations are open-coded as sequences. Loading a 32-bit constant takes two instructions (lui + addi). A subtract-from-immediate takes one instruction with the right sign-extended immediate, or two if it does not fit. There is no integer overflow flag because the manual chose to require explicit-check sequences (xor to detect sign mismatch) rather than implicit side effects on a condition-code register.
M: The Multiply/Divide Extension
Without M, every multiply or divide must be open-coded into a software helper (typically a 32-iteration shift-add loop). For real programs this is unacceptably slow. M version 2.0 adds eight instructions, all R-type (RISC-V Unprivileged ISA):
| Instruction | Semantics |
|---|---|
mul rd, rs1, rs2 | rd = (rs1 * rs2)[31:0] (low 32 bits of signed product) |
mulh rd, rs1, rs2 | rd = (rs1 *signed * signed* rs2)[63:32] (high 32 of signed×signed) |
mulhsu rd, rs1, rs2 | high 32 of signed * unsigned |
mulhu rd, rs1, rs2 | high 32 of unsigned * unsigned |
div rd, rs1, rs2 | signed quotient |
divu rd, rs1, rs2 | unsigned quotient |
rem rd, rs1, rs2 | signed remainder |
remu rd, rs1, rs2 | unsigned remainder |
The three mulh* variants exist so the compiler can compute a full 64-bit product as two 32-bit pieces without losing the upper word: pair mul with one of mulh/mulhsu/mulhu and you get the full result. The signed/unsigned/mixed split exists because the sign convention of the high half depends on the sign of both operands.
The division semantics are spec-defined on edge cases: division by zero returns all-ones (-1 signed, 2^32-1 unsigned) in the quotient and the dividend in the remainder; signed overflow (most-negative dividend divided by -1) returns the most-negative number in the quotient and zero in the remainder. No trap is raised. This is unlike x86 (which raises #DE) and is a deliberate simplification: compilers insert explicit divide-by-zero checks where required, and the hardware never has to deal with mid-stream exceptions.
Implementations vary in latency. A small sequential divider takes ~33 cycles for a 32-bit divide; a faster one with a SRT-style algorithm takes 4-8; rare wide implementations do it in one cycle. The ISA is silent on latency; software just stalls until the result is ready. The ESP32-C3 documents a “32-bit multiplier and 32-bit divider” (Datasheet v2.4 §4.1.1.1) but publishes no latency figures for either; see the ESP32-C3 section below.
C: The Compressed Extension
The C extension version 2.0 adds 16-bit encodings for the most-frequently-used 32-bit instructions, recovering CISC-like code density without compromising the load/store discipline (RISC-V Unprivileged ISA).
Encoding. The low two bits of any instruction word are 11 for 32-bit instructions and one of 00, 01, 10 for the three quadrants of 16-bit instructions. The fetch unit reads 16 bits at a time, peeks at those two bits, and either fetches another 16 bits to assemble a full 32-bit instruction or proceeds with the 16-bit one. Compressed and uncompressed instructions interleave freely within a function; the decoder handles both without mode bits.
Compressible instruction set. Roughly 25 of the most common operations have 16-bit forms. Examples:
c.add rd, rs1(whererd == rs1),c.mv rd, rs2c.addi rd, imm(6-bit signed immediate)c.li rd, imm,c.lui rd, immc.j offset,c.jal offset(RV32 only),c.jr rs1,c.jalr rs1c.beqz rs1', offset,c.bnez rs1', offsetc.lw rd', offset(rs1'),c.sw rs2', offset(rs1')c.lwsp rd, offset(sp),c.swsp rs2, offset(sp)c.nop,c.ebreak
Most of these accept any of the 32 architectural registers; a notable subset, the “prime” forms (rd', rs1', rs2'), accept only registers x8 through x15. The prime restriction is what makes the encoding fit in 16 bits: a 3-bit field instead of 5. This is why the ABI puts s0, s1, a0-a5 in that range; the compiler tries to keep hot variables there to maximize compression.
Effective code-size reduction. Empirical studies (and Wikipedia) report roughly 25-30% smaller code from enabling C on typical workloads (Wikipedia, RISC-V). On microcontrollers where every byte of flash is dollars per million units, this is decisive. On larger systems, smaller code also fits more instructions per cache line, improving instruction-cache hit rate and effectively widening fetch bandwidth.
Decoder cost. The decoder must do length decode on every instruction: examine bits [1:0], dispatch to either a 16-bit or 32-bit decode path, and convert the 16-bit form to its 32-bit equivalent for the rest of the pipeline. This is a small cost in gates and one extra layer of multiplexing; modern small cores absorb it without a frequency penalty.
Why This Triplet for Embedded
The choice of exactly I + M + C (without A, F, D, V) is not arbitrary. It is the minimum practical configuration for general-purpose code that compiles efficiently from C and Rust:
- I is mandatory. Cannot omit.
- M is the cheapest sensible addition. Without it, every multiply is a 100+ cycle software loop. With it, multiplies are 1-5 cycles. Total cost is a small multiplier (~5000 gates for a 32-bit) and a state-machine divider (~1000 gates).
- C halves typical code size. The decoder cost is small; the flash-cost win on a microcontroller is large.
- A (atomics) is omitted for single-core MCUs. Atomics matter for SMP coherence; a single-hart microcontroller can disable interrupts to achieve atomicity instead. ESP32-C3 omits A.
- F/D (floating-point) is omitted because most MCU workloads use integer or fixed-point math. Floating-point hardware adds ~20-30k gates and ~15% to die area. If software ever needs floats, the compiler uses soft-float runtime calls.
- V (vectors) is omitted because vector registers are huge. A V implementation typically adds tens to hundreds of kilobits of register state.
So I + M + C is the minimum practical ISA for an embedded MCU; A and F push you into “application” territory. The next standard step up is RV32IMAC, which adds atomics and enables Linux-on-MMU-less or RTOS workloads. ESP32-C6 (a successor to the C3) implements RV32IMAC.
The Rust Toolchain Match
Rust ships a first-party bare-metal target named riscv32imc-unknown-none-elf, a Tier 2 target without host tools as of Rust 1.97.1 (checked 2026-08-08) (Rust platform support). Tier 2 in Rust’s target-tier policy means “guaranteed to build”: the project ships official binary builds of core for it and continuous integration proves every change still compiles for it, but the test suite is not run on it (which for a no-std cross-compiled bare-metal target is not possible anyway). That is a materially stronger guarantee than Tier 3 (“may or may not work”, no official builds) — a Tier 2 target is installable with a plain rustup target add and will not silently rot. The target spec file in the rustc source enables exactly the m and c features for LLVM plus +forced-atomics (which lets the compiler emit single-instruction sequences for core::sync::atomic operations on a chip that has no A extension, by promoting them to interrupt-disable-protected sequences in the runtime) (rust-lang/rust source). The ABI is ILP32 (32-bit int, long, and pointer); the linker defaults to rust-lld with gnu link flavor; no standard library, just core and (optionally) alloc if the program provides a heap.
Resolved 2026-08-08
riscv32imc-unknown-none-elfis Tier 2 (without host tools). Verified two ways against Rust 1.97.1 (the current stable, released 2026-07-16):
- The per-target page
src/doc/rustc/src/platform-support/riscv32-unknown-none-elf.mdat tag1.97.1reads verbatim**Tier: 2**for “RV32I, RV32IM, RV32IMC, RV32IMAFC and RV32IMAC ISAs”, and**Tier: 3**only for the separate RV32IMA target (raw file at tag 1.97.1).- The master table in
platform-support.md— and the rendered stable page at doc.rust-lang.org/rustc/platform-support.html — lists the row forriscv32imc-unknown-none-elfinside the## Tier 2 without Host Toolssection, not## Tier 3.The earlier “master table says Tier 3” claim was a misread. Two different targets whose names begin with the same prefix do sit in the Tier 3 table —
riscv32imc-esp-espidfandriscv32imc-unknown-nuttx-elf— andriscv32ima-unknown-none-elf(withA, withoutC) is likewise Tier 3. None of them is the bare-metalriscv32imc-unknown-none-elfthis note is about. Checked 2026-08-08.
The pairing is intentional. The Rust embedded working group’s riscv-rt crate provides linker scripts and entry-point code for exactly this target; the esp-hal ecosystem builds on it for ESP32-C3 development. A cargo build --target riscv32imc-unknown-none-elf produces an ELF that runs both in QEMU’s qemu-system-riscv32 -machine virt -cpu rv32 and on the ESP32-C3 with the right linker script and bootloader.
The ESP32-C3 as Reference Silicon
Resolved 2026-08-08
Verified against the primary source. The ESP32-C3 Technical Reference Manual v1.4 was downloaded with
curland extracted withpdftotext -layoutinstead of being fetched through a summarizer; its Revision History dates v1.4 to 2026-03-26 (Chapter 1, ESP-RISC-V CPU, last substantively revised in v0.7, 2022-12-16). The relevant chapter is Chapter 1, not “Section 5” as the old callout guessed. Four-stage pipeline, 160 MHz, 16 PMP regions, M+U privilege, and vectored-onlymtvecare all confirmed verbatim; “no MMU” is true of the core’s privileged architecture but needed re-wording, because the SoC does contain a flash-cache MMU that is a different thing entirely. The multiplier/divider latency claim turned out to be unsourced and has been softened. Per-fact citations are inline below.
Espressif’s ESP32-C3 is the canonical commercial RV32IMC chip the definitely-not-esp32 project benchmarks against. Read directly from TRM v1.4 and Datasheet v2.4:
- CPU. Single 32-bit RISC-V hart. TRM §1.1 states it exactly: “The core has 4-stage, in-order, scalar pipeline optimized for area, power and performance.” The TRM never names the four stages, so the common “Fetch / Decode / Execute / Memory+Writeback” gloss is inference; the manual’s only concrete hint is §1.7’s remark that
mepcanddpcare “set to current PC (in decode stage)”, which at least confirms a discrete decode stage. Clock is “up to 160 MHz” (TRM §1.2; Datasheet v2.4 §4.1.1.1). - ISA. RV32IMC.
misais hardwiredMXL = 1,I = 1,M = 1,C = 1, withA = F = D = E = 0(TRM §1.4.2 Register 1.6). Zicsr is implicit in the CSR set of §1.4; the TRM never uses the names “Zicsr” or “Zifencei”, thoughFENCEappears in its documented low-power and interrupt sequences (§1.5.2, Chapter 9). - Multiplier/divider. The Datasheet lists “32-bit multiplier and 32-bit divider” (§4.1.1.1). Neither document states a latency, so the widely repeated “single-cycle multiply, iterative divide” figures are plausible for this core class but are not Espressif claims — do not cite them as such.
- Privilege. Machine and User mode only:
misareportsU = 1,S = 0,N = 0,H = 0, andmstatus.MPPaccepts only “0x0: User mode” and “0x3: Machine mode” (TRM §1.4.2 Registers 1.5, 1.6). The delegation CSRsmedeleg/midelegare absent entirely, as the privileged spec requires for an M/U hart without user-level interrupts — see RISC-V Privilege Modes. - Trap vector.
mtvec.MODEis read-only0x1: “Only vectored mode0x1is available”, withBASE256-byte aligned (TRM §1.4.2 Register 1.7). Direct mode is not offered. - PMP. “Physical memory protection (PMP) for up to 16 configurable regions” (TRM §1.2), restated in §1.8.2 as “It supports 16 regions and a minimum granularity of 4 bytes”, programmable only from M-mode (§1.8.3). Two non-conformances are documented in §1.8.1-§1.8.2: overlapping regions do not get standard static priority (a match against any enabled entry grants access, so contradictory overlaps fail open), and the maximum NAPOT range is 1 GB.
- Interrupts. 31 vectored interrupts, IDs 1-31 — “ID = 0 is unavailable … [it] is reserved for exceptions” — at 15 priority levels, “1 (lowest) to 15 (highest)”, plus a global masking threshold and 8 hardware breakpoints/watchpoints (TRM §1.2, §1.5.1, §1.5.2, Table 1.5-1). Note that Datasheet v2.4 §4.1.1.1 still says “Up to 32 vectored interrupts at seven priority levels” — the TRM is the authority and the datasheet has not been reconciled. See ESP32-C3 for the full interrupt matrix.
- No virtual memory. The core has no
satpCSR, no supervisor mode, and TRM §1.3’s CPU address map is physical, so there is no Sv32 address translation and no per-process address space. Do not confuse this with the SoC’s flash-cache MMU: TRM §3.3.3.1 says “according to the MMU (Memory Management Unit) settings, the cache maps the CPU’s address to the external memory’s physical address”, mapping up to 8 MB of instruction space and 8 MB of read-only data space out of a 16 MB external flash in 64 KB blocks. That is a static physical-to-physical remapper for the cache, not a page-table walker.
The match with the definitely-not-esp32 target is direct: the project ships an RV32IMC core with Zicsr, M+U privilege, PMP-based isolation, and physical addressing as its v1.0 baseline, with the explicit goal of running benchmarks the ESP32-C3 also runs so the two can be compared honestly.
Encoding Density Example
Compare the same simple loop body in plain RV32I and in RVC-enabled assembly.
Plain RV32I (the body of for (i = 0; i < n; i++) sum += arr[i];):
add t0, a0, zero # t0 = arr base (R-type, 4 bytes)
addi t1, zero, 0 # t1 = sum = 0 (I-type, 4 bytes)
addi t2, zero, 0 # t2 = i = 0 (I-type, 4 bytes)
loop:
bge t2, a1, end # if i >= n, exit (B-type, 4 bytes)
slli t3, t2, 2 # t3 = i*4 (I-type, 4 bytes)
add t4, t0, t3 # t4 = &arr[i] (R-type, 4 bytes)
lw t5, 0(t4) # t5 = arr[i] (I-type, 4 bytes)
add t1, t1, t5 # sum += arr[i] (R-type, 4 bytes)
addi t2, t2, 1 # i++ (I-type, 4 bytes)
jal zero, loop # goto loop (J-type, 4 bytes)
end:
add a0, t1, zero # return sum (R-type, 4 bytes)Forty bytes for the loop body. With C enabled the compiler reshapes to compressible forms:
c.mv t0, a0 # (2 bytes)
c.li t1, 0 # (2 bytes)
c.li t2, 0 # (2 bytes)
loop:
bge t2, a1, end # 13-bit branch needs full B-type (4 bytes)
c.slli t3, 2 # (only valid for x8-x15; if t3 is in range) (2 bytes)
c.add t4, t3 # (2 bytes; if both in range)
c.lw t5, 0(t4) # (registers in x8-x15) (2 bytes)
c.add t1, t5 # (2 bytes)
c.addi t2, 1 # (2 bytes)
c.j loop # (2 bytes)
end:
c.mv a0, t1 # (2 bytes)Twenty-two bytes if the compiler successfully allocates the prime-restricted operations to x8-x15. About 45% smaller. Real code rarely hits the theoretical maximum because not every register lives in the compressed range, but 25-30% reduction is typical across compiled C programs.
Failure Modes and Common Misunderstandings
- “RV32IMC includes Zicsr.” No. RV32IMC names exactly the I, M, and C extensions. Any practical implementation also implements Zicsr (no CSRs means no traps means no interrupts means no useful chip), and the full ISA string is
rv32imc_zicsr_zifencei. The shorthand “RV32IMC” elides that, and a strict compliance check would fail without it. - “C lets me put 16-bit and 32-bit instructions anywhere.” True for the fetch logic, but jump targets must still be 2-byte aligned (not 4-byte; that is the relaxation C brings). A
jalto an odd address traps as instruction-address misaligned. - “Division by zero traps.” On RISC-V it does not. Quotient is all-ones; remainder is the dividend. Software must check explicitly. This is a frequent surprise for x86 programmers.
- “
mulis enough; you do not needmulh.” Only if all products fit in 32 bits.mulh/mulhsu/mulhuare needed for full 64-bit results, bignum arithmetic, and computing__umulhfor fast division-by-constant tricks. Compilers emitmulhmore often than people expect. - “Compressed instructions are slower.” Wrong. They map 1-to-1 to a regular 32-bit decode and execute identically; the only cost is one extra cycle of decode logic, which most cores absorb in the same decode stage. There is no performance reason to disable C.
- “You can install Linux on RV32IMC.” Realistically no. Linux-on-RISC-V wants RV32GC or RV64GC with at least atomics (A) and floating-point (F+D) and the supervisor extension (S) plus the privileged spec’s MMU schemes. RV32IMC chips run baremetal or small RTOS workloads.
Alternatives Within the Embedded Profile Family
The cluster of “embedded RISC-V profiles” the toolchain natively supports includes:
rv32i: base only. Useful for the smallest possible cores (FPGA bring-up, sensor controllers); compiled C is unreasonable without M.rv32e: base with 16 registers. Compiler support exists but ecosystem is thin.rv32im: base + multiply/divide, no compression. Same code-size disadvantage as plain RV32I but with reasonable arithmetic speed.rv32imc: the subject of this note.rv32imac: adds atomics. Required for multi-hart embedded chips or Linux-style workloads. ESP32-C6, K210.rv32imafc: adds single-precision float. Used by some control-loop applications.rv32imafdc: adds double-precision float.rv32gc: equivalent torv32imafdcplus Zicsr/Zifencei.
The Rust toolchain ships riscv32i, riscv32im, riscv32imc, riscv32imac, riscv32imafc and the ESP-IDF-flavored riscv32imc-esp-espidf and riscv32imac-esp-espidf (Rust platform support).
Production Notes
The popularity of RV32IMC in shipping silicon is striking: every Espressif ESP32-Cx variant, Bouffalo BL602/BL702, and most academic FPGA cores (PicoRV32, Ibex, Hazard3, VeeR EL2) target this or RV32IMAC. The reasons are practical: it is the smallest configuration that supports a modern toolchain comfortably, has a stable ABI, and runs a real RTOS (FreeRTOS, ESP-IDF, NuttX, embassy/Rust).
For the definitely-not-esp32 project the implications are concrete:
- The RTL implements exactly 47 + 8 + ~25 = ~80 distinct instruction encodings (counting compressed forms separately) plus the Zicsr quartet. This is small enough to verify with directed tests; the compressed forms can be cross-checked by decompressing and re-running through the standard decoder.
- The Rust target
riscv32imc-unknown-none-elfgives instant toolchain support; no custom GCC build needed. - The ESP32-C3 cross-reference means every kernel benchmark can run on real silicon for comparison; performance numbers are not vacuum-measured.
- Atomicity for the microkernel uses interrupt-disable rather than
A-extension AMOs; this matches ESP32-C3’s behavior.
Real-world quirks worth knowing: GCC’s default -march=rv32imc produced different ISA strings in different toolchain versions (sometimes implicitly including _zicsr_zifencei, sometimes not), which matters when linking object files compiled with different toolchain versions. As of GCC 12 the default is rv32imc_zicsr_zifencei. LLVM’s behavior is similar.
See Also
- RISC-V Instruction Set Architecture: the parent ISA family
- Instruction Set Architecture: the general concept
- Zicsr Extension: the always-paired CSR-access extension
- RISC-V Privilege Modes: the M+U privilege model used in this project
- ESP32-C3: the reference commercial chip
- Classic Five-Stage Pipeline: the microarchitecture this project’s core uses
- Computer Architecture MOC: parent map