Execution Engine Xez Extension

The Xez custom RISC-V extension on the EE — the Z80-flag-model patterns not covered by the EE's other extensions: the Z/C/S/V/P flag register, flag-setting arithmetic and shifts, conditional branches on flags, decrement-and-branch, single-bit test on memory, and a handful of convenience mnemonics. Xez is a settled EE extension, shared by the Ant64's FireStorm and the eZX Spectrum's eZX since both run the one RV64 EE. For the canonical encoding reference see the EE spec.


Overview

Xez is what's left after the EE's other extensions absorb everything they cover well. Add / auto-increment / block / search-memory patterns live in Xcrisp; add-with-carry, rotate-through-carry in Xmath G12; multi-register push/pop in Xstack; contexts in Xctx; load/store swizzles in Xlate; operand-based predication in Xcond. What only Xez provides is the Z80 flag model — the Z/S/V/P condition bits (carry is shared, below), flag-setting arithmetic and shifts, and branches on those flags.

The flag register on a dual-issue core — the design point

The EE is dual-issue and deliberately flagless on its fast path: standard RISC-V arithmetic writes no condition codes, and Xcond predicates on operand values rather than a flag register, precisely so a register that every op writes can't serialise the pipeline. Xez does not change that. ezflags is written only by explicit flag/carry instructions — Xez's *F ops, Xmath's carry ops, and Xcrisp's BMCMP block-compare — never by default arithmetic, Xstack, Xctx, Xlate, Xcond, or the standard B* branches. So:

  • Ordinary EE code, and Xcond predication, dual-issue freely — there is no flag hazard, because they do not write ezflags at all.
  • Only a chain of Xez flag-setting ops serialises on the flag register — the inherent cost of flag-dependent code, paid solely by the Z80-idiom code that opts in. A lone writer (an Xmath carry op, a BMCMP) is a single producer→consumer read once by the next branch, not a chain, so it does not serialise.

This is the reconciliation between "the EE has no general flag register" (true for the fast path) and "Xez needs one" (true, and dormant until used). Reach for Xcond when you want flag-free predication at full dual-issue speed; reach for Xez when the surrounding code already thinks in Z80 flags.


1. Source compatibility note

Xez shrank as the EE extensions absorbed its overlaps. Source that emits the moved mnemonics still assembles — the assembler emits the new home's encoding:

    ADDC    a0, a0, a1      ; -> Xmath  ADDC
    PUSHM   {ra, s0-s7}     ; -> Xstack PUSH
    RL      t0, t0          ; -> Xmath  ROLC

The Z80-flag-model instructions documented below have always been Xez-native and remain so. See §9 for the full lowering table.


2. The ezflags register

ezflags is a 5-bit architectural register of Z80-style condition flags, updated only by explicit flag/carry instructions — Xez's *F ops set Z/S/V/P, Xmath's carry ops set C (via the xcarry alias), and Xcrisp's BMCMP block-compare sets Z/C. Default RV64GC arithmetic, Xcond, Xstack, Xctx, Xlate, and the standard B* branches never touch it.

Bit Symbol Name Set when
0 Z Zero Result is zero
1 C Carry Unsigned carry-out / borrow / shift-out bit
2 S Sign Top bit of the result is 1 (negative)
3 V Overflow Signed overflow
4 P Parity Result has even parity (logical ops only)

ezflags is a CSR at 0x809, adjacent to Xmath xcarry at 0x808, with which it shares its C bit (§2.1). CSRs 0x8000x807 are Xlate translator state.

Width follows XLEN. On the RV64 EE the flags are computed on the full 64-bit result — S is bit 63, C is the carry-out of bit 63. (The earlier RV32 eZX drafts used bit 31; unifying on the RV64 EE makes 64-bit the semantics.) Literal 8-bit Z80 emulation — where flags follow bit 7 — is the job of a Z80 personality core / Z80NG, not Xez; Xez gives RISC-V code the Z80 flag idioms at register width.

2.1 The ezflags.Cxcarry alias

The C bit of ezflags and the Xmath xcarry CSR are the same physical bit, exposed at two addresses.

View Address Width Layout
ezflags 0x809 5 bits [Z, C, S, V, P]
xcarry 0x808 1 bit [C]

A Xez ADDF that sets ezflags.C is observable as xcarry.C == 1 for a following Xmath ADDC, and vice-versa — so multi-precision arithmetic composes across the two extensions. Carry was always the one condition bit the EE carried (for Xmath G12); Xez adds Z/S/V/P around it.

2.2 Trap handling

On trap entry the implementation saves ezflags into mscratch[4:0]; mret restores it. Interrupt handlers use Xez freely without disturbing the interrupted program's flags. (The aliased xcarry rides along.) Per-context save is handled by Xctx — one save/restore covers both views.

2.3 The H and N flags

The Z80's H (half-carry) and N (last-op-was-subtract) flags are not modelled — both exist only for the Z80 DAA instruction, which is not provided.


3. Flag-setting arithmetic and logic

Standard add/sub/and/or/xor leave ezflags untouched. Their *F counterparts do the same arithmetic and update the flags.

Instruction Operation Flags updated
ADDF rd, rs1, rs2 rd = rs1 + rs2 Z, C (+xcarry), S, V
SUBF rd, rs1, rs2 rd = rs1 - rs2 Z, C (+xcarry), S, V
ANDF rd, rs1, rs2 bitwise AND Z, S, P; clears C (+xcarry), V
ORF rd, rs1, rs2 bitwise OR Z, S, P; clears C (+xcarry), V
XORF rd, rs1, rs2 bitwise XOR Z, S, P; clears C (+xcarry), V
CMP rs1, rs2 compute rs1 - rs2, discard result Z, C (+xcarry), S, V
CMPI rs, imm12 compute rs - imm, discard result Z, C (+xcarry), S, V
NEGF rd, rs rd = -rs Z, C (+xcarry), S, V

CMP / CMPI are the no-write forms used just before a flag branch — the Z80 CP r / CP n shape followed by JR Z. (Add-with-carry / subtract-with-borrow are Xmath G12, reachable from Xez source via the same ADDC/SUBC mnemonics.)


4. Conditional branches on flags

The Z80 condition codes carry across one-to-one:

Instruction Z80 Branch if
BZ / BNZ JR Z / JR NZ ezflags.Z == 1 / == 0
BC / BNC JR C / JR NC ezflags.C == 1 / == 0 (= xcarry.C)
BS / BNS JP M / JP P ezflags.S == 1 / == 0
BV / BNV JP PE / JP PO ezflags.V == 1 / == 0 (after arithmetic)
BPE / BPO JP PE / JP PO ezflags.P == 1 / == 0 (after logical ops)

Displacement is a B-type 13-bit signed immediate (±4 KiB). Xez splits the Z80's overloaded "P/V" bit into separate V (overflow, after arithmetic) and P (parity, after logic) ezflags bits so the mnemonic matches intent. Standard beq/bne/… and Xcrisp BEQM..BGEUM compare registers/memory directly and ignore ezflags — the three branch families don't interact.


5. Decrement-and-branch

The Z80 DJNZ, in clean RISC-V form. These do not update ezflags — the decrement is a side effect on rs only.

Instruction Operation
DBNZ rs, offset rs = rs - 1; branch if rs != 0
DBZ rs, offset rs = rs - 1; branch if rs == 0

Combined with Xcrisp auto-increment loads, a Z80-style tight loop collapses to the shape a Z80 programmer expects (LBUPI load+advance, SBPI store+advance, DBNZ count-and-loop). For pure contiguous copies, Xcrisp BMCPY is shorter still; DBNZ earns its place when the loop body does more than copy.


6. Flag-setting shifts

The Z80 *F flag-setting shifts stay in Xez, and — because Xez brings ezflags to the EE — are now available on the EE too (they were previously eZX-only, since the EE had no flag register to write):

Instruction Z80 Operation
SLAF rd, rs, imm SLA r shift left by imm; high bit → C (+xcarry); update Z, S, P
SRAF rd, rs, imm SRA r arithmetic shift right; low bit → C (+xcarry); update Z, S, P
SRLF rd, rs, imm SRL r logical shift right; low bit → C (+xcarry); update Z, S, P

Plain sll/srl/sra and Zbb rol/ror leave ezflags untouched.


7. Bit test on memory

Instruction Z80 Operation
BTSTM imm3, (rs) BIT n,(HL) test bit imm3 of the byte at [rs]; set ezflags.Z; no write

BTSTM is the only memory-bit instruction in Xez — set/clear/toggle are Xcrisp op-store fusion (ORSB/ANDSB/XORSB). It stays because Xcrisp's compare-mem-branch fuses test and branch, which is no help for the Z80 "test a bit, do other things, branch later on the flag" idiom. The read goes through the address register's Xlate read translator, so a BTSTM against a BREV8-configured register tests the bit-reversed byte. Register-level bit ops are standard Zbs (bset/bclr/binv/bext).


8. Convenience instructions

Single-instruction mnemonics for things Z80 programmers expect as one op:

Instruction Operation
NEG rd, rs rd = -rs — no flag update (NEGF in §3 is the flag-updating form)
CPL rd, rs rd = ~rs — one's complement (Z80 CPL, at register width)
EX rd1, rd2 swap two registers in one instruction
SWAPN rd, rs swap nibbles in the low byte of rs, high bits zeroed (Z80 Next SWAPNIB)
MIRROR rd, rs reverse the bit order of the low byte of rs, high bits zeroed (Z80 Next MIRROR A)

For memory-side bit reversal at load/store time, configure the register with Xlate BREV8 — no hot-loop instruction needed.


9. Assembler lowering

The Xez source mnemonics that moved upstream lower as follows (source needs no edits; bytes differ, semantics match):

Xez source Lowers to New home
ADDC / SUBC Xmath G12 ADDC / SUBC ee_xmath
RL / RR Xmath G12 ROLC / RORC ee_xmath
RLC / RRC Zbb rol / ror by 1 standard Zbb
PUSHM / POPM Xstack PUSH / POP ee_xstack
SCF / CCF csrrsi / csrrci on xcarry direct CSR op

The mask-to-rlist conversion for PUSHM/POPM is the assembler's: it picks the smallest Xstack rlist covering the mask and warns on an irregular pattern.


10. Worked example

Counting zero bytes in a buffer — mixing the extensions naturally:

;   x10 = buffer ptr, x11 = count, x12 = result

    li      x12, 0
loop:
    LBUPI   x13, 1(x10)     ; load byte, advance ptr   [Xcrisp]
    CMPI    x13, 0          ; compare to zero          [Xez]
    BNZ     skip            ; not zero, skip           [Xez]
    addi    x12, x12, 1     ; increment count          [standard]
skip:
    DBNZ    x11, loop       ; decrement total, loop    [Xez]

Side by side with the Z80 original (LD A,(HL) / INC HL / OR A / JR NZ / INC C / DJNZ) the shape is identical — different mnemonics, same instinct.


11. Encoding

Xez claims opcode 0x77 — a reserved-for-custom slot in the base ISA, free on both RV32 and RV64. It is not one of the four custom opcodes (those are full — Xcrisp on 0x0B/0x2B/0x7B, and Xcrisp/Xstack/Xctx sharing every funct3 slot of 0x5B), and it is not one of RV64's added word-instruction opcodes (0x1B OP-IMM-32, 0x3B OP-32), so the allocation carries onto the RV64 EE unchanged.

EE opcode landscape:
  0x0B custom-0    -> Xcrisp auto-inc loads
  0x2B custom-1    -> Xcrisp auto-inc stores
  0x5B custom-2    -> Xcrisp / Xstack / Xctx (all funct3 slots used)
  0x77 reserved    -> Xez  <- THIS
  0x7B custom-3    -> Xcrisp compare-mem-branch
  0x57 / 0x6B      -> Xmath;   0x7F -> Xcrisp PIC / Xwide escape

11.1 Funct3 sub-encoding

funct3 Format Family Instructions
000 R Flag arithmetic ADDF (funct7 0000000), SUBF (0100000), NEGF (0000001, rs2=x0)
001 R Flag logic ANDF (0000000), ORF (0000001), XORF (0000010)
010 R Compare CMP (0000000, rd=x0)
011 I Flag shifts SLAF (funct7[6:1] 000000), SRAF (010000), SRLF (000001); imm = shift amount
100 I Bit test mem BTSTM (imm[2:0] = bit index 0–7)
101 R Convenience NEG (0000000,rs2=x0), CPL (0000001,rs2=x0), EX (0000010), SWAPN (0000011,rs2=x0), MIRROR (0000100,rs2=x0)
110 I Compare imm CMPI (rd=x0; imm12)
111 B Branch flag branches + DBNZ/DBZ; condition in rs2 (see §11.2)

11.2 Flag branches and decrement-and-branch (funct3 = 111)

All share 0x77 funct3 = 111; the B-type rs2 field carries the condition code (a small integer, not a register): 0000001001 = BZ/BNZ/BC/BNC/BS/BNS/BV/BNV/BPE/BPO (rs1 ignored, assembler emits x0); 10000/10001 = DBNZ/DBZ (rs1 meaningful, decremented); others reserved. Standard B-type immediate layout, ±4 KiB. The implementation reads ezflags (or decrements rs1) in execute and resolves via the same datapath as standard B*.

11.3 Conflict-free composition

The 0x77 allocation is independent of every other EE extension — it needs no funct3/funct7 negotiation against Xcrisp, Xmath, Xstack/Xctx, Xcond (which overlays the standard OP opcode, not a custom one), or standard RV64GC + Zbb/Zbs/Zicond.


12. Status

  • Per-context save. ezflags/xcarry (one physical bit set) is saved by Xctx as part of per-context state — one save/restore covers both views.
  • DAA. Z80 BCD decimal-adjust is niche; not in v1, trivial to add if a Z80-emulator workload needs it.
  • Block I/O. Z80 INI/OUTI-style port-block ops aren't in Xez — the chipset is memory-mapped; DMA channels handle that shape.
  • BTSTM range. v1 tests bits 0–7 of a memory byte; wider in-memory bit access is an obvious later extension.
  • Dual-issue note (see Overview). Because ezflags serialises on a dual-issue core, a compiler targeting the EE prefers Xcond for predication and reserves Xez flag chains for genuinely Z80-shaped code.

Important: The Ant64 family of home computers are at early design/prototype stage, everything you see here is subject to change.