Skip to main content

logicaffeine_compile/codegen_sva/hart/
model.rs

1//! The executable specification of the hart: an RV64IMAC processor with the Zicsr and Zifencei extensions, machine
2//! and user modes and eight physical-memory-protection entries, one architectural step at a time. The hart's RTL is
3//! held to this model instruction by instruction, and this model is held to the RISC-V architecture tests and to
4//! `qemu-riscv64`.
5//!
6//! What the hart leaves to software, by design: there is no floating-point unit, so every F and D instruction (and
7//! every access to `fflags`, `frm` or `fcsr`) is an illegal instruction the machine-mode firmware emulates; and a
8//! misaligned load, store or atomic traps with its address in `mtval` rather than being split in hardware. Neither
9//! occurs often enough in the compiler to matter: the measured floating-point share of a compile is 0.0002% of its
10//! instructions.
11
12use super::rvc;
13
14/// `misa`: RV64 with the A, C, I, M and U extensions.
15pub const MISA: u64 = (2 << 62) | (1 << 0) | (1 << 2) | (1 << 8) | (1 << 12) | (1 << 20);
16
17/// The number of physical-memory-protection entries the hart implements; entries 8 to 15 read as zero.
18pub const PMP_ENTRIES: usize = 8;
19
20/// A privilege mode.
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum Privilege {
23    User = 0,
24    Machine = 3,
25}
26
27/// A synchronous exception: its `mcause` and the value `mtval` receives.
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct Trap {
30    pub cause: u64,
31    pub tval: u64,
32}
33
34pub const CAUSE_FETCH_ACCESS: u64 = 1;
35pub const CAUSE_ILLEGAL_INSTRUCTION: u64 = 2;
36pub const CAUSE_BREAKPOINT: u64 = 3;
37pub const CAUSE_MISALIGNED_LOAD: u64 = 4;
38pub const CAUSE_LOAD_ACCESS: u64 = 5;
39pub const CAUSE_MISALIGNED_STORE: u64 = 6;
40pub const CAUSE_STORE_ACCESS: u64 = 7;
41pub const CAUSE_USER_ECALL: u64 = 8;
42pub const CAUSE_MACHINE_ECALL: u64 = 11;
43
44/// A physical access the bus refuses (no device at the address).
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub struct BusError;
47
48/// The hart's view of the system: naturally aligned reads and writes of 1, 2, 4 or 8 bytes.
49pub trait Bus {
50    fn read(&mut self, addr: u64, size: u8) -> Result<u64, BusError>;
51    fn write(&mut self, addr: u64, size: u8, value: u64) -> Result<(), BusError>;
52}
53
54/// A range of physical addresses and what it may be used for: its physical memory attributes. An access outside
55/// every region, or one its region does not permit, is an access fault before it reaches the bus — so a device
56/// register is never fetched from, never the target of an atomic, and never read or written in part. These
57/// attributes are the only source of access faults: every access they permit, the bus completes.
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct Region {
60    pub base: u64,
61    pub size: u64,
62    pub read: bool,
63    pub write: bool,
64    pub execute: bool,
65    pub atomic: bool,
66    /// Every access must be exactly this many bytes (device registers are whole words).
67    pub width: Option<u64>,
68}
69
70impl Region {
71    /// Memory: every kind of access.
72    pub const fn memory(base: u64, size: u64) -> Self {
73        Region {
74            base,
75            size,
76            read: true,
77            write: true,
78            execute: true,
79            atomic: true,
80            width: None,
81        }
82    }
83
84    /// Memory that holds data and never code.
85    pub const fn data(base: u64, size: u64) -> Self {
86        Region {
87            base,
88            size,
89            read: true,
90            write: true,
91            execute: false,
92            atomic: true,
93            width: None,
94        }
95    }
96
97    /// Read-only memory that holds code.
98    pub const fn rom(base: u64, size: u64) -> Self {
99        Region {
100            base,
101            size,
102            read: true,
103            write: false,
104            execute: true,
105            atomic: false,
106            width: None,
107        }
108    }
109
110    /// Device registers: whole-word loads and stores only.
111    pub const fn io(base: u64, size: u64) -> Self {
112        Region {
113            base,
114            size,
115            read: true,
116            write: true,
117            execute: false,
118            atomic: false,
119            width: Some(8),
120        }
121    }
122
123    fn holds(&self, addr: u64, size: u64) -> bool {
124        addr >= self.base && addr.wrapping_add(size) <= self.base.wrapping_add(self.size) && addr.wrapping_add(size) > addr
125    }
126}
127
128/// What one step did.
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum Step {
131    /// The instruction completed and retired.
132    Retired,
133    /// The instruction took a trap; the hart is now at `mtvec` in machine mode.
134    Trapped(Trap),
135}
136
137#[derive(Clone, Copy, PartialEq, Eq)]
138enum Access {
139    Read,
140    Write,
141    Execute,
142    Atomic,
143}
144
145/// The architectural state of the hart.
146#[derive(Clone, Debug)]
147pub struct Hart {
148    pub pc: u64,
149    pub x: [u64; 32],
150    pub privilege: Privilege,
151    pub mie_bit: bool,
152    pub mpie: bool,
153    pub mpp: Privilege,
154    pub mprv: bool,
155    pub tw: bool,
156    pub mie: u64,
157    pub mtvec: u64,
158    pub mscratch: u64,
159    pub mepc: u64,
160    pub mcause: u64,
161    pub mtval: u64,
162    pub mcounteren: u64,
163    pub mcountinhibit: u64,
164    pub mcycle: u64,
165    pub minstret: u64,
166    pub pmpcfg: [u8; PMP_ENTRIES],
167    pub pmpaddr: [u64; PMP_ENTRIES],
168    /// The address an `LR` reserved, until an `SC`, a trap or `mret` releases it.
169    pub reservation: Option<u64>,
170    /// The physical memory map.
171    pub pma: Vec<Region>,
172}
173
174/// `mstatus.UXL`: user mode runs at 64 bits.
175const MSTATUS_UXL: u64 = 2 << 32;
176
177const PMP_R: u8 = 1;
178const PMP_W: u8 = 2;
179const PMP_X: u8 = 4;
180const PMP_A: u8 = 3 << 3;
181const PMP_L: u8 = 1 << 7;
182const PMP_TOR: u8 = 1 << 3;
183const PMP_NA4: u8 = 2 << 3;
184const PMP_NAPOT: u8 = 3 << 3;
185/// `pmpaddr` holds bits 55..2 of a 56-bit physical address.
186const PMPADDR_MASK: u64 = (1 << 54) - 1;
187
188fn sext32(v: u64) -> u64 {
189    v as u32 as i32 as i64 as u64
190}
191
192fn illegal(inst: u32) -> Trap {
193    Trap {
194        cause: CAUSE_ILLEGAL_INSTRUCTION,
195        tval: inst as u64,
196    }
197}
198
199impl Hart {
200    /// The hart as reset leaves it: machine mode at `reset_pc`, interrupts off, every protection entry off, over the
201    /// physical memory map `pma`.
202    pub fn new(reset_pc: u64, pma: Vec<Region>) -> Self {
203        Hart {
204            pc: reset_pc,
205            x: [0; 32],
206            privilege: Privilege::Machine,
207            mie_bit: false,
208            mpie: false,
209            mpp: Privilege::User,
210            mprv: false,
211            tw: false,
212            mie: 0,
213            mtvec: 0,
214            mscratch: 0,
215            mepc: 0,
216            mcause: 0,
217            mtval: 0,
218            mcounteren: 0,
219            mcountinhibit: 0,
220            mcycle: 0,
221            minstret: 0,
222            pmpcfg: [0; PMP_ENTRIES],
223            pmpaddr: [0; PMP_ENTRIES],
224            reservation: None,
225            pma,
226        }
227    }
228
229    /// Whether the physical memory map permits the access.
230    fn pma_allows(&self, addr: u64, size: u64, access: Access) -> bool {
231        self.pma.iter().any(|r| {
232            r.holds(addr, size)
233                && r.width.map_or(true, |w| w == size)
234                && match access {
235                    Access::Read => r.read,
236                    Access::Write => r.write,
237                    Access::Execute => r.execute,
238                    Access::Atomic => r.atomic,
239                }
240        })
241    }
242
243    pub fn mstatus(&self) -> u64 {
244        MSTATUS_UXL
245            | ((self.mie_bit as u64) << 3)
246            | ((self.mpie as u64) << 7)
247            | ((self.mpp as u64) << 11)
248            | ((self.mprv as u64) << 17)
249            | ((self.tw as u64) << 21)
250    }
251
252    fn write_mstatus(&mut self, v: u64) {
253        self.mie_bit = v & (1 << 3) != 0;
254        self.mpie = v & (1 << 7) != 0;
255        self.mpp = if (v >> 11) & 3 == 3 {
256            Privilege::Machine
257        } else {
258            Privilege::User
259        };
260        self.mprv = v & (1 << 17) != 0;
261        self.tw = v & (1 << 21) != 0;
262    }
263
264    /// The privilege a data access runs at: `mstatus.MPRV` lends machine-mode loads and stores `MPP`'s.
265    fn data_privilege(&self) -> Privilege {
266        if self.mprv && self.privilege == Privilege::Machine {
267            self.mpp
268        } else {
269            self.privilege
270        }
271    }
272
273    /// The byte range `[lo, hi)` protection entry `i` covers, or `None` when the entry is off.
274    fn pmp_range(&self, i: usize) -> Option<(u64, u64)> {
275        let a = self.pmpcfg[i] & PMP_A;
276        let addr = self.pmpaddr[i];
277        match a {
278            PMP_TOR => {
279                let lo = if i == 0 { 0 } else { self.pmpaddr[i - 1] << 2 };
280                Some((lo, addr << 2))
281            }
282            PMP_NA4 => Some((addr << 2, (addr << 2) + 4)),
283            PMP_NAPOT => {
284                let ones = (!addr).trailing_zeros().min(54) as u64;
285                let size_log2 = ones + 3;
286                let lo = (addr & !((1u64 << ones) - 1)) << 2;
287                Some((lo, lo.wrapping_add(1u64 << size_log2)))
288            }
289            _ => None,
290        }
291    }
292
293    /// Whether an access of `size` bytes at `addr` is permitted to `privilege`.
294    fn pmp_allows(&self, addr: u64, size: u64, access: Access, privilege: Privilege) -> bool {
295        let end = addr.wrapping_add(size);
296        for i in 0..PMP_ENTRIES {
297            let Some((lo, hi)) = self.pmp_range(i) else { continue };
298            if lo >= hi {
299                continue;
300            }
301            let any = addr < hi && end > lo;
302            if !any {
303                continue;
304            }
305            let all = addr >= lo && end <= hi;
306            if !all {
307                return false;
308            }
309            let cfg = self.pmpcfg[i];
310            if privilege == Privilege::Machine && cfg & PMP_L == 0 {
311                return true;
312            }
313            return match access {
314                Access::Read => cfg & PMP_R != 0,
315                Access::Write => cfg & PMP_W != 0,
316                Access::Execute => cfg & PMP_X != 0,
317                Access::Atomic => cfg & PMP_R != 0 && cfg & PMP_W != 0,
318            };
319        }
320        privilege == Privilege::Machine
321    }
322
323    fn load(&mut self, bus: &mut impl Bus, addr: u64, size: u8) -> Result<u64, Trap> {
324        if addr % size as u64 != 0 {
325            return Err(Trap {
326                cause: CAUSE_MISALIGNED_LOAD,
327                tval: addr,
328            });
329        }
330        let fault = Trap {
331            cause: CAUSE_LOAD_ACCESS,
332            tval: addr,
333        };
334        if !self.pmp_allows(addr, size as u64, Access::Read, self.data_privilege())
335            || !self.pma_allows(addr, size as u64, Access::Read)
336        {
337            return Err(fault);
338        }
339        bus.read(addr, size).map_err(|_| fault)
340    }
341
342    fn store(&mut self, bus: &mut impl Bus, addr: u64, size: u8, value: u64) -> Result<(), Trap> {
343        if addr % size as u64 != 0 {
344            return Err(Trap {
345                cause: CAUSE_MISALIGNED_STORE,
346                tval: addr,
347            });
348        }
349        let fault = Trap {
350            cause: CAUSE_STORE_ACCESS,
351            tval: addr,
352        };
353        if !self.pmp_allows(addr, size as u64, Access::Write, self.data_privilege())
354            || !self.pma_allows(addr, size as u64, Access::Write)
355        {
356            return Err(fault);
357        }
358        bus.write(addr, size, value).map_err(|_| fault)
359    }
360
361    /// An atomic memory operation's read half: an AMO is checked as a store, so a refusal is a store fault.
362    fn amo_load(&mut self, bus: &mut impl Bus, addr: u64, size: u8) -> Result<u64, Trap> {
363        let fault = Trap {
364            cause: CAUSE_STORE_ACCESS,
365            tval: addr,
366        };
367        if addr % size as u64 != 0 {
368            return Err(Trap {
369                cause: CAUSE_MISALIGNED_STORE,
370                tval: addr,
371            });
372        }
373        if !self.pmp_allows(addr, size as u64, Access::Atomic, self.data_privilege())
374            || !self.pma_allows(addr, size as u64, Access::Atomic)
375        {
376            return Err(fault);
377        }
378        bus.read(addr, size).map_err(|_| fault)
379    }
380
381    fn fetch_half(&mut self, bus: &mut impl Bus, addr: u64) -> Result<u16, Trap> {
382        let fault = Trap {
383            cause: CAUSE_FETCH_ACCESS,
384            tval: addr,
385        };
386        if !self.pmp_allows(addr, 2, Access::Execute, self.privilege) || !self.pma_allows(addr, 2, Access::Execute) {
387            return Err(fault);
388        }
389        bus.read(addr, 2).map(|v| v as u16).map_err(|_| fault)
390    }
391
392    /// Fetch the instruction at `pc`: its 32-bit form, its length, and its raw encoding (for `mtval`).
393    fn fetch(&mut self, bus: &mut impl Bus) -> Result<(u32, u64, u32), Trap> {
394        let lo = self.fetch_half(bus, self.pc)?;
395        if rvc::is_compressed(lo) {
396            return match rvc::expand(lo) {
397                Some(inst) => Ok((inst, 2, lo as u32)),
398                None => Err(illegal(lo as u32)),
399            };
400        }
401        let hi = self.fetch_half(bus, self.pc.wrapping_add(2))?;
402        let inst = (lo as u32) | ((hi as u32) << 16);
403        Ok((inst, 4, inst))
404    }
405
406    fn take_trap(&mut self, trap: Trap) {
407        self.mepc = self.pc;
408        self.mcause = trap.cause;
409        self.mtval = trap.tval;
410        self.mpie = self.mie_bit;
411        self.mie_bit = false;
412        self.mpp = self.privilege;
413        self.privilege = Privilege::Machine;
414        self.pc = self.mtvec & !3;
415        self.reservation = None;
416    }
417
418    /// Execute one instruction.
419    /// The counters count the instruction under the inhibit it leaves behind: one that writes `mcountinhibit` is
420    /// counted (or not) by the value it writes.
421    pub fn step(&mut self, bus: &mut impl Bus) -> Step {
422        let result = self.execute(bus);
423        let counting_cycles = self.mcountinhibit & 1 == 0;
424        match result {
425            Ok(outcome) => {
426                if counting_cycles && !outcome.wrote_mcycle {
427                    self.mcycle = self.mcycle.wrapping_add(1);
428                }
429                if self.mcountinhibit & 4 == 0 && !outcome.wrote_minstret {
430                    self.minstret = self.minstret.wrapping_add(1);
431                }
432                self.pc = outcome.next_pc;
433                self.x[0] = 0;
434                Step::Retired
435            }
436            Err(trap) => {
437                if counting_cycles {
438                    self.mcycle = self.mcycle.wrapping_add(1);
439                }
440                self.take_trap(trap);
441                Step::Trapped(trap)
442            }
443        }
444    }
445
446    fn execute(&mut self, bus: &mut impl Bus) -> Result<Outcome, Trap> {
447        let (inst, len, raw) = self.fetch(bus)?;
448        let mut out = Outcome {
449            next_pc: self.pc.wrapping_add(len),
450            wrote_mcycle: false,
451            wrote_minstret: false,
452        };
453        let opcode = inst & 0x7f;
454        let rd = ((inst >> 7) & 31) as usize;
455        let funct3 = (inst >> 12) & 7;
456        let rs1 = ((inst >> 15) & 31) as usize;
457        let rs2 = ((inst >> 20) & 31) as usize;
458        let funct7 = inst >> 25;
459        let a = self.x[rs1];
460        let b = self.x[rs2];
461        let imm_i = ((inst as i32) >> 20) as i64 as u64;
462        let imm_s = ((((inst as i32) >> 25) << 5) as i64 as u64) | ((inst >> 7) & 31) as u64;
463        let imm_b = ((((inst as i32) >> 31) << 12) as i64 as u64)
464            | (((inst >> 7) & 1) << 11) as u64
465            | (((inst >> 25) & 0x3f) << 5) as u64
466            | (((inst >> 8) & 0xf) << 1) as u64;
467        let imm_u = (inst & 0xffff_f000) as i32 as i64 as u64;
468        let imm_j = ((((inst as i32) >> 31) << 20) as i64 as u64)
469            | (inst & 0x000f_f000) as u64
470            | (((inst >> 20) & 1) << 11) as u64
471            | (((inst >> 21) & 0x3ff) << 1) as u64;
472        let bad = || illegal(raw);
473        match opcode {
474            0x37 => self.x[rd] = imm_u,
475            0x17 => self.x[rd] = self.pc.wrapping_add(imm_u),
476            0x6f => {
477                self.x[rd] = self.pc.wrapping_add(len);
478                out.next_pc = self.pc.wrapping_add(imm_j);
479            }
480            0x67 => {
481                if funct3 != 0 {
482                    return Err(bad());
483                }
484                let target = a.wrapping_add(imm_i) & !1;
485                self.x[rd] = self.pc.wrapping_add(len);
486                out.next_pc = target;
487            }
488            0x63 => {
489                let taken = match funct3 {
490                    0 => a == b,
491                    1 => a != b,
492                    4 => (a as i64) < (b as i64),
493                    5 => (a as i64) >= (b as i64),
494                    6 => a < b,
495                    7 => a >= b,
496                    _ => return Err(bad()),
497                };
498                if taken {
499                    out.next_pc = self.pc.wrapping_add(imm_b);
500                }
501            }
502            0x03 => {
503                let addr = a.wrapping_add(imm_i);
504                let v = match funct3 {
505                    0 => self.load(bus, addr, 1)? as u8 as i8 as i64 as u64,
506                    1 => self.load(bus, addr, 2)? as u16 as i16 as i64 as u64,
507                    2 => sext32(self.load(bus, addr, 4)?),
508                    3 => self.load(bus, addr, 8)?,
509                    4 => self.load(bus, addr, 1)? as u8 as u64,
510                    5 => self.load(bus, addr, 2)? as u16 as u64,
511                    6 => self.load(bus, addr, 4)? as u32 as u64,
512                    _ => return Err(bad()),
513                };
514                self.x[rd] = v;
515            }
516            0x23 => {
517                let addr = a.wrapping_add(imm_s);
518                match funct3 {
519                    0 => self.store(bus, addr, 1, b & 0xff)?,
520                    1 => self.store(bus, addr, 2, b & 0xffff)?,
521                    2 => self.store(bus, addr, 4, b & 0xffff_ffff)?,
522                    3 => self.store(bus, addr, 8, b)?,
523                    _ => return Err(bad()),
524                }
525            }
526            0x13 => {
527                let shamt = (inst >> 20) & 0x3f;
528                self.x[rd] = match funct3 {
529                    0 => a.wrapping_add(imm_i),
530                    2 => ((a as i64) < (imm_i as i64)) as u64,
531                    3 => (a < imm_i) as u64,
532                    4 => a ^ imm_i,
533                    6 => a | imm_i,
534                    7 => a & imm_i,
535                    1 if inst >> 26 == 0 => a << shamt,
536                    5 if inst >> 26 == 0 => a >> shamt,
537                    5 if inst >> 26 == 0x10 => ((a as i64) >> shamt) as u64,
538                    _ => return Err(bad()),
539                };
540            }
541            0x1b => {
542                let shamt = (inst >> 20) & 0x1f;
543                self.x[rd] = match (funct3, funct7) {
544                    (0, _) => sext32(a.wrapping_add(imm_i)),
545                    (1, 0) => sext32(((a as u32) << shamt) as u64),
546                    (5, 0) => sext32(((a as u32) >> shamt) as u64),
547                    (5, 0x20) => ((a as i32) >> shamt) as i64 as u64,
548                    _ => return Err(bad()),
549                };
550            }
551            0x33 => {
552                self.x[rd] = match (funct7, funct3) {
553                    (0, 0) => a.wrapping_add(b),
554                    (0x20, 0) => a.wrapping_sub(b),
555                    (0, 1) => a << (b & 63),
556                    (0, 2) => ((a as i64) < (b as i64)) as u64,
557                    (0, 3) => (a < b) as u64,
558                    (0, 4) => a ^ b,
559                    (0, 5) => a >> (b & 63),
560                    (0x20, 5) => ((a as i64) >> (b & 63)) as u64,
561                    (0, 6) => a | b,
562                    (0, 7) => a & b,
563                    (1, 0) => a.wrapping_mul(b),
564                    (1, 1) => (((a as i64 as i128) * (b as i64 as i128)) >> 64) as u64,
565                    (1, 2) => (((a as i64 as i128) * (b as u128 as i128)) >> 64) as u64,
566                    (1, 3) => (((a as u128) * (b as u128)) >> 64) as u64,
567                    (1, 4) => {
568                        let (x, y) = (a as i64, b as i64);
569                        if y == 0 {
570                            u64::MAX
571                        } else {
572                            x.wrapping_div(y) as u64
573                        }
574                    }
575                    (1, 5) => a.checked_div(b).unwrap_or(u64::MAX),
576                    (1, 6) => {
577                        let (x, y) = (a as i64, b as i64);
578                        if y == 0 {
579                            a
580                        } else {
581                            x.wrapping_rem(y) as u64
582                        }
583                    }
584                    (1, 7) => a.checked_rem(b).unwrap_or(a),
585                    _ => return Err(bad()),
586                };
587            }
588            0x3b => {
589                let (x, y) = (a as u32, b as u32);
590                self.x[rd] = match (funct7, funct3) {
591                    (0, 0) => sext32(x.wrapping_add(y) as u64),
592                    (0x20, 0) => sext32(x.wrapping_sub(y) as u64),
593                    (0, 1) => sext32((x << (y & 31)) as u64),
594                    (0, 5) => sext32((x >> (y & 31)) as u64),
595                    (0x20, 5) => ((x as i32) >> (y & 31)) as i64 as u64,
596                    (1, 0) => sext32(x.wrapping_mul(y) as u64),
597                    (1, 4) => {
598                        let (p, q) = (x as i32, y as i32);
599                        if q == 0 {
600                            u64::MAX
601                        } else {
602                            p.wrapping_div(q) as i64 as u64
603                        }
604                    }
605                    (1, 5) => {
606                        if y == 0 {
607                            u64::MAX
608                        } else {
609                            sext32((x / y) as u64)
610                        }
611                    }
612                    (1, 6) => {
613                        let (p, q) = (x as i32, y as i32);
614                        if q == 0 {
615                            p as i64 as u64
616                        } else {
617                            p.wrapping_rem(q) as i64 as u64
618                        }
619                    }
620                    (1, 7) => {
621                        if y == 0 {
622                            sext32(x as u64)
623                        } else {
624                            sext32((x % y) as u64)
625                        }
626                    }
627                    _ => return Err(bad()),
628                };
629            }
630            0x2f => self.atomic(bus, inst, rd, a, b, raw)?,
631            0x0f => match funct3 {
632                0 | 1 => {}
633                _ => return Err(bad()),
634            },
635            0x73 => self.system(inst, raw, rd, rs1, funct3, &mut out)?,
636            _ => return Err(bad()),
637        }
638        Ok(out)
639    }
640
641    fn atomic(&mut self, bus: &mut impl Bus, inst: u32, rd: usize, a: u64, b: u64, raw: u32) -> Result<(), Trap> {
642        let funct3 = (inst >> 12) & 7;
643        let funct5 = inst >> 27;
644        let size: u8 = match funct3 {
645            2 => 4,
646            3 => 8,
647            _ => return Err(illegal(raw)),
648        };
649        let widen = |v: u64| if size == 4 { sext32(v) } else { v };
650        match funct5 {
651            0x02 => {
652                if (inst >> 20) & 31 != 0 {
653                    return Err(illegal(raw));
654                }
655                if a % size as u64 != 0 {
656                    return Err(Trap {
657                        cause: CAUSE_MISALIGNED_LOAD,
658                        tval: a,
659                    });
660                }
661                if !self.pma_allows(a, size as u64, Access::Atomic) {
662                    return Err(Trap {
663                        cause: CAUSE_LOAD_ACCESS,
664                        tval: a,
665                    });
666                }
667                let v = self.load(bus, a, size)?;
668                self.reservation = Some(a);
669                self.x[rd] = widen(v);
670            }
671            0x03 => {
672                if a % size as u64 != 0 {
673                    return Err(Trap {
674                        cause: CAUSE_MISALIGNED_STORE,
675                        tval: a,
676                    });
677                }
678                if self.reservation == Some(a) {
679                    if !self.pma_allows(a, size as u64, Access::Atomic) {
680                        return Err(Trap {
681                            cause: CAUSE_STORE_ACCESS,
682                            tval: a,
683                        });
684                    }
685                    let value = if size == 4 { b & 0xffff_ffff } else { b };
686                    self.store(bus, a, size, value)?;
687                    self.x[rd] = 0;
688                } else {
689                    self.x[rd] = 1;
690                }
691                self.reservation = None;
692            }
693            _ => {
694                let old = self.amo_load(bus, a, size)?;
695                let (x, y) = if size == 4 { (sext32(old), sext32(b)) } else { (old, b) };
696                let new = match funct5 {
697                    0x01 => y,
698                    0x00 => x.wrapping_add(y),
699                    0x04 => x ^ y,
700                    0x0c => x & y,
701                    0x08 => x | y,
702                    0x10 => {
703                        if (x as i64) < (y as i64) {
704                            x
705                        } else {
706                            y
707                        }
708                    }
709                    0x14 => {
710                        if (x as i64) > (y as i64) {
711                            x
712                        } else {
713                            y
714                        }
715                    }
716                    // Sign extension preserves unsigned order, so the widened operands compare as the words do.
717                    0x18 => x.min(y),
718                    0x1c => x.max(y),
719                    _ => return Err(illegal(raw)),
720                };
721                let mask = if size == 4 { 0xffff_ffff } else { u64::MAX };
722                self.store(bus, a, size, new & mask)
723                    .map_err(|t| Trap {
724                        cause: CAUSE_STORE_ACCESS,
725                        tval: t.tval,
726                    })?;
727                self.x[rd] = widen(old);
728            }
729        }
730        Ok(())
731    }
732
733    fn system(
734        &mut self,
735        inst: u32,
736        raw: u32,
737        rd: usize,
738        rs1: usize,
739        funct3: u32,
740        out: &mut Outcome,
741    ) -> Result<(), Trap> {
742        if funct3 == 0 {
743            return match inst {
744                0x0000_0073 => Err(Trap {
745                    cause: if self.privilege == Privilege::Machine {
746                        CAUSE_MACHINE_ECALL
747                    } else {
748                        CAUSE_USER_ECALL
749                    },
750                    tval: 0,
751                }),
752                0x0010_0073 => Err(Trap {
753                    cause: CAUSE_BREAKPOINT,
754                    tval: self.pc,
755                }),
756                0x3020_0073 if self.privilege == Privilege::Machine => {
757                    self.privilege = self.mpp;
758                    self.mie_bit = self.mpie;
759                    self.mpie = true;
760                    if self.mpp != Privilege::Machine {
761                        self.mprv = false;
762                    }
763                    self.mpp = Privilege::User;
764                    self.reservation = None;
765                    out.next_pc = self.mepc;
766                    Ok(())
767                }
768                0x1050_0073 if self.privilege == Privilege::Machine || !self.tw => Ok(()),
769                _ => Err(illegal(raw)),
770            };
771        }
772        if funct3 == 4 {
773            return Err(illegal(raw));
774        }
775        let csr = inst >> 20;
776        let operand = if funct3 & 4 != 0 { rs1 as u64 } else { self.x[rs1] };
777        let writes = match funct3 & 3 {
778            1 => true,
779            _ => rs1 != 0,
780        };
781        let old = self.read_csr(csr, raw)?;
782        if writes {
783            if (csr >> 10) & 3 == 3 {
784                return Err(illegal(raw));
785            }
786            let new = match funct3 & 3 {
787                1 => operand,
788                2 => old | operand,
789                _ => old & !operand,
790            };
791            self.write_csr(csr, new, out);
792        }
793        self.x[rd] = old;
794        Ok(())
795    }
796
797    fn counter_permitted(&self, bit: u32) -> bool {
798        self.privilege == Privilege::Machine || self.mcounteren & (1 << bit) != 0
799    }
800
801    fn read_csr(&self, csr: u32, raw: u32) -> Result<u64, Trap> {
802        if (csr >> 8) & 3 > self.privilege as u32 {
803            return Err(illegal(raw));
804        }
805        Ok(match csr {
806            0x300 => self.mstatus(),
807            0x301 => MISA,
808            0x304 => self.mie,
809            0x305 => self.mtvec,
810            0x306 => self.mcounteren,
811            0x30a => 0,
812            0x320 => self.mcountinhibit,
813            0x323..=0x33f => 0,
814            0x340 => self.mscratch,
815            0x341 => self.mepc,
816            0x342 => self.mcause,
817            0x343 => self.mtval,
818            0x344 => 0,
819            0x3a0 => self.pmpcfg.iter().enumerate().map(|(i, &c)| (c as u64) << (8 * i)).sum(),
820            0x3a2 => 0,
821            0x3b0..=0x3bf => {
822                let i = (csr - 0x3b0) as usize;
823                if i < PMP_ENTRIES {
824                    self.pmpaddr[i]
825                } else {
826                    0
827                }
828            }
829            0xb00 => self.mcycle,
830            0xb02 => self.minstret,
831            0xb03..=0xb1f => 0,
832            0xc00 if self.counter_permitted(0) => self.mcycle,
833            0xc01 if self.counter_permitted(1) => self.mcycle,
834            0xc02 if self.counter_permitted(2) => self.minstret,
835            0xc03..=0xc1f if self.counter_permitted(csr - 0xc00) => 0,
836            0xf11..=0xf15 => 0,
837            _ => return Err(illegal(raw)),
838        })
839    }
840
841    fn write_csr(&mut self, csr: u32, v: u64, out: &mut Outcome) {
842        match csr {
843            0x300 => self.write_mstatus(v),
844            0x304 => self.mie = v & ((1 << 3) | (1 << 7) | (1 << 11)),
845            0x305 => self.mtvec = v & !3,
846            0x306 => self.mcounteren = v & 7,
847            0x320 => self.mcountinhibit = v & 5,
848            0x340 => self.mscratch = v,
849            0x341 => self.mepc = v & !1,
850            0x342 => self.mcause = v,
851            0x343 => self.mtval = v,
852            0x3a0 => {
853                for i in 0..PMP_ENTRIES {
854                    if self.pmpcfg[i] & PMP_L != 0 {
855                        continue;
856                    }
857                    let mut cfg = (v >> (8 * i)) as u8 & (PMP_R | PMP_W | PMP_X | PMP_A | PMP_L);
858                    if cfg & PMP_W != 0 && cfg & PMP_R == 0 {
859                        cfg &= !PMP_W;
860                    }
861                    self.pmpcfg[i] = cfg;
862                }
863            }
864            0x3b0..=0x3bf => {
865                let i = (csr - 0x3b0) as usize;
866                if i < PMP_ENTRIES && !self.pmpaddr_locked(i) {
867                    self.pmpaddr[i] = v & PMPADDR_MASK;
868                }
869            }
870            0xb00 => {
871                self.mcycle = v;
872                out.wrote_mcycle = true;
873            }
874            0xb02 => {
875                self.minstret = v;
876                out.wrote_minstret = true;
877            }
878            _ => {}
879        }
880    }
881
882    /// `pmpaddr[i]` is locked by its own entry's lock bit, or by the next entry's when that entry is a locked TOR
883    /// range whose bottom it is.
884    fn pmpaddr_locked(&self, i: usize) -> bool {
885        self.pmpcfg[i] & PMP_L != 0
886            || (i + 1 < PMP_ENTRIES
887                && self.pmpcfg[i + 1] & PMP_L != 0
888                && self.pmpcfg[i + 1] & PMP_A == PMP_TOR)
889    }
890}
891
892struct Outcome {
893    next_pc: u64,
894    wrote_mcycle: bool,
895    wrote_minstret: bool,
896}