"Zicond": Integer Conditional Operations

Part III Linux boot: optional Vol. I (Unprivileged) pp. 62–64 · ~3 min read

Zicond is two conditional-zeroing instructions — deliberately not a cmov — from which short sequences recover every conditional select and arithmetic form, branchlessly and (under Zkt) in constant time.

Why this matters

A real cmov needs three sources, breaking the two-read-port register-file discipline the whole integer ISA maintains; conditional zeroing is the largest primitive that fits — and its two-instruction pairs were designed to macro-op fuse.

Unpredictable branches cost pipelines dearly, and secret-dependent branches leak timing. Zicond adds exactly two R-type instructions — conditional zeroing, not a full conditional move — from which branchless selects and conditional arithmetic compose:

00001113125rs22420rs11915101 / 1111412rd117011001160czero.eqz / czero.nez (OP, funct7 0000111)
Click a field for its role.

Both carry syntactic dependencies from both sources to rd (no skipping reads, unlike Zimop), and under Zkt their timing must be independent of the data — the constant-time-crypto guarantee.

The cookbook

Composition patterns (§11.2): neutralize an operand, then combine
SequenceLen
rd = (rc==0) ? rs1+rs2 : rs1czero.nez rd, rs2, rc · add rd, rs1, rd2
rd = (rc!=0) ? rs1+rs2 : rs1czero.eqz rd, rs2, rc · add rd, rs1, rd2
cond-sub / -or / -xorSame shape — zero the second operand under the complementary condition, then sub/or/xor.2
rd = (rc==0) ? rs1&rs2 : rs1and rd, rs1, rs2 · czero.eqz rtmp, rs1, rc · or rd, rd, rtmp — AND has no zero identity, so mask the fallback in instead.3
rd = (rc==0) ? rs1 : rs2 (select)czero.nez rd, rs1, rc · czero.eqz rtmp, rs2, rc · add rd, rd, rtmp — exactly one survives, add merges.3 + 1 temp
Dotted-underlined cells have explanations — click one.
If you remember only three things
  • czero.eqz / czero.nez: rd = (test rs2) ? 0 : rs1 — an OR-reduce on rs2 plus a writeback mux, both of which your datapath already has.
  • The cookbook shape: zero one operand under the complementary condition, then combine with add/sub/or/xor; a full select is three instructions and one temp.
  • Both sources are always read (unlike Zimop), and claiming Zkt means the mux path’s timing must be independent of the data.

Hardware Designer Notes

The datapath is an existence proof of cheapness: the zero-detect on rs2 already exists for branch comparisons, and the writeback mux already exists for load/ALU selection. Wiring them together under two funct3 codes is an afternoon of RTL.

Minimal Linux-boot hart MUST

  • Implement both as single-cycle ALU ops: rd = (condition-test(rs2)) ? 0 : rs1 — an OR-reduce on rs2 and a 2:1 mux on the writeback path
  • If you claim Zkt: verify the mux path has no value-dependent short-circuit timing

MAY simplify / trap-and-emulate

  • Fuse the documented 2-instruction pairs (czero + add/sub/or/xor) into one internal micro-op — the sequences were designed to be fusion-friendly
  • Skip Zicond on a minimal core (compilers fall back to branches) — but it is in RVA23, and the OR-reduce + mux is nearly free

Check yourself — Zicond

1.Express rd = (rc != 0) ? (rs1 + rs2) : rs1 with Zicond.

2.Why does a full conditional select need THREE instructions (two czeros + add)?

3.What extra guarantee do the czero instructions gain when Zkt is implemented?

3 questions