Skip to main content

ternaria_vm/
csr.rs

1//! Control and status registers, privilege levels, and trap causes.
2//!
3//! These are the machine state an operating system owns and a user program
4//! cannot reach. They are addressed by number through `csrr` and `csrw` rather
5//! than mapped into memory, so that reaching them is a privilege check on one
6//! instruction instead of a check on every load and store.
7
8use ternaria_arith::{Trit, Word};
9
10// The trit positions belong to the bus interface, not to either side of it.
11pub use ternaria_mem::irq;
12
13/// Privilege level, held in one trit.
14///
15/// Three levels in one trit is the encoding a ternary machine gets for free. A
16/// binary machine spends two bits on three levels and leaves one combination
17/// meaning nothing.
18#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
19pub enum Priv {
20    /// Unprivileged. No control registers, no device addresses.
21    User,
22    /// The operating system.
23    Supervisor,
24    /// Firmware. Address translation does not apply.
25    #[default]
26    Machine,
27}
28
29impl Priv {
30    /// The trit encoding: user is -1, supervisor 0, machine +1.
31    pub const fn trit(self) -> Trit {
32        match self {
33            Priv::User => Trit::Neg,
34            Priv::Supervisor => Trit::Zero,
35            Priv::Machine => Trit::Pos,
36        }
37    }
38
39    /// The level a trit encodes.
40    pub const fn from_trit(t: Trit) -> Priv {
41        match t {
42            Trit::Neg => Priv::User,
43            Trit::Zero => Priv::Supervisor,
44            Trit::Pos => Priv::Machine,
45        }
46    }
47
48    /// The level a value encodes, saturating outside -1..=1.
49    pub const fn from_value(v: i64) -> Priv {
50        if v < 0 {
51            Priv::User
52        } else if v == 0 {
53            Priv::Supervisor
54        } else {
55            Priv::Machine
56        }
57    }
58}
59
60/// Why a trap was taken.
61///
62/// Causes are positive so that zero means "no trap", which is the state a
63/// freshly reset machine is in.
64#[derive(Clone, Copy, PartialEq, Eq, Debug)]
65#[repr(i64)]
66pub enum Cause {
67    /// The word at `pc` did not decode.
68    IllegalInstruction = 1,
69    /// A load or store failed. `tval` carries the address.
70    Memory = 2,
71    /// `div` or `rem` by zero.
72    DivideByZero = 3,
73    /// An arithmetic result left the word range.
74    Overflow = 4,
75    /// Control flow left the address space.
76    PcOutOfRange = 5,
77    /// An `ecall`. The system-call door.
78    Ecall = 6,
79    /// An instruction needed a privilege the machine was not at. `tval`
80    /// carries the level that was required.
81    PrivilegeViolation = 7,
82    /// Address translation failed. `tval` carries the virtual address.
83    PageFault = 8,
84    /// The timer reached its compare value.
85    TimerInterrupt = 9,
86    /// A device has input waiting.
87    ConsoleInterrupt = 10,
88}
89
90impl Cause {
91    /// The numeric code written to `tcause`.
92    pub const fn value(self) -> i64 {
93        self as i64
94    }
95
96    /// True for causes raised by something other than the current instruction.
97    pub const fn is_interrupt(self) -> bool {
98        matches!(self, Cause::TimerInterrupt | Cause::ConsoleInterrupt)
99    }
100}
101
102/// Register numbers accepted by `csrr` and `csrw`.
103pub mod num {
104    /// Trap vector base, a tryte address. Zero means traps are fatal.
105    pub const TVEC: i64 = 0;
106    /// Cause of the last trap.
107    pub const TCAUSE: i64 = 1;
108    /// Program counter saved when the last trap was taken.
109    pub const TEPC: i64 = 2;
110    /// Value associated with the last trap: a faulting address, usually.
111    pub const TVAL: i64 = 3;
112    /// Privilege level saved when the last trap was taken.
113    pub const TPRIV: i64 = 4;
114    /// Current privilege level. Read-only.
115    pub const PRIV: i64 = 5;
116    /// Interrupt enable, one trit per source.
117    pub const IE: i64 = 6;
118    /// Interrupt pending, one trit per source. Read-only.
119    pub const IP: i64 = 7;
120    /// Page table base, a tryte address. Zero means translation is off.
121    pub const PTBR: i64 = 8;
122    /// Free for the trap handler, which has no stack of its own on entry.
123    pub const SCRATCH: i64 = 9;
124    /// Interrupt enable saved when the last trap was taken.
125    pub const TIE: i64 = 10;
126    /// Highest defined register number.
127    pub const MAX: i64 = TIE;
128}
129
130/// The control and status registers.
131#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
132pub struct Csrs {
133    /// Trap vector base. Zero leaves traps fatal, which is the reset state.
134    pub tvec: i64,
135    /// Cause of the last trap.
136    pub tcause: i64,
137    /// Program counter saved on the last trap.
138    pub tepc: i64,
139    /// Value associated with the last trap.
140    pub tval: i64,
141    /// Privilege saved on the last trap.
142    pub tpriv: Priv,
143    /// Interrupt enable mask, one trit per source.
144    pub ie: Word,
145    /// Interrupt pending mask, one trit per source.
146    pub ip: Word,
147    /// Interrupt enable saved when the last trap was taken.
148    ///
149    /// Taking a trap clears `ie` and stores the old value here; `tret`
150    /// restores it. Without this a level-triggered source re-fires before the
151    /// handler's first instruction and the handler never runs.
152    pub tie: Word,
153    /// Page table base. Zero disables translation.
154    pub ptbr: i64,
155    /// Scratch space for the handler.
156    pub scratch: i64,
157}
158
159impl Csrs {
160    /// Reads a register by number. `None` if the number is not defined.
161    pub fn read(&self, n: i64, current: Priv) -> Option<i64> {
162        Some(match n {
163            num::TVEC => self.tvec,
164            num::TCAUSE => self.tcause,
165            num::TEPC => self.tepc,
166            num::TVAL => self.tval,
167            num::TPRIV => self.tpriv.trit() as i8 as i64,
168            num::PRIV => current.trit() as i8 as i64,
169            num::IE => self.ie.value(),
170            num::IP => self.ip.value(),
171            num::TIE => self.tie.value(),
172            num::PTBR => self.ptbr,
173            num::SCRATCH => self.scratch,
174            _ => return None,
175        })
176    }
177
178    /// Writes a register by number.
179    ///
180    /// `Some(false)` for a register that exists but is read-only, `None` for
181    /// one that does not exist. Writing a read-only register is silently
182    /// ignored rather than trapping, matching the way `r0` discards writes.
183    pub fn write(&mut self, n: i64, v: i64) -> Option<bool> {
184        match n {
185            num::TVEC => self.tvec = v,
186            num::TCAUSE => self.tcause = v,
187            num::TEPC => self.tepc = v,
188            num::TVAL => self.tval = v,
189            num::TPRIV => self.tpriv = Priv::from_value(v),
190            num::IE => self.ie = Word::from_value(v),
191            num::TIE => self.tie = Word::from_value(v),
192            num::PTBR => self.ptbr = v,
193            num::SCRATCH => self.scratch = v,
194            num::PRIV | num::IP => return Some(false),
195            _ => return None,
196        }
197        Some(true)
198    }
199
200    /// The interrupt to take, if any.
201    ///
202    /// A source fires when it is both pending and enabled, which is a per-trit
203    /// minimum: Kleene AND over the two masks (D-06). The lowest such trit
204    /// wins, so a lower-numbered source has priority.
205    pub fn pending_interrupt(&self) -> Option<Cause> {
206        let active = self.ip.trit_and(self.ie);
207        let trits = active.trits();
208        for (source, &trit) in trits.iter().enumerate().take(irq::COUNT) {
209            if trit == Trit::Pos {
210                return Some(match source {
211                    irq::TIMER => Cause::TimerInterrupt,
212                    _ => Cause::ConsoleInterrupt,
213                });
214            }
215        }
216        None
217    }
218
219    /// Sets or clears one trit of the pending mask.
220    pub fn set_pending(&mut self, source: usize, on: bool) {
221        let mut trits = self.ip.trits();
222        trits[source] = if on { Trit::Pos } else { Trit::Zero };
223        self.ip = Word::from_trits(trits);
224    }
225}
226
227/// Assembler `.equ` lines naming every control register, cause and flag.
228///
229/// The counterpart to the device prelude: prepend it so a supervisor written
230/// in assembly names `TVEC` and `CAUSE_ECALL` instead of 0 and 6.
231pub fn prelude() -> String {
232    let entries: [(&str, i64); 23] = [
233        ("TVEC", num::TVEC),
234        ("TCAUSE", num::TCAUSE),
235        ("TEPC", num::TEPC),
236        ("TVAL", num::TVAL),
237        ("TPRIV", num::TPRIV),
238        ("PRIV", num::PRIV),
239        ("IE", num::IE),
240        ("IP", num::IP),
241        ("PTBR", num::PTBR),
242        ("SCRATCH", num::SCRATCH),
243        ("TIE", num::TIE),
244        ("CAUSE_ILLEGAL", Cause::IllegalInstruction.value()),
245        ("CAUSE_MEMORY", Cause::Memory.value()),
246        ("CAUSE_DIVZERO", Cause::DivideByZero.value()),
247        ("CAUSE_OVERFLOW", Cause::Overflow.value()),
248        ("CAUSE_PC_RANGE", Cause::PcOutOfRange.value()),
249        ("CAUSE_ECALL", Cause::Ecall.value()),
250        ("CAUSE_PRIVILEGE", Cause::PrivilegeViolation.value()),
251        ("CAUSE_PAGE_FAULT", Cause::PageFault.value()),
252        ("CAUSE_TIMER", Cause::TimerInterrupt.value()),
253        ("CAUSE_CONSOLE", Cause::ConsoleInterrupt.value()),
254        ("PRIV_USER", -1),
255        ("PRIV_MACHINE", 1),
256    ];
257    let mut out = String::new();
258    for (name, value) in entries {
259        out.push_str(&format!(".equ {name}, {value}\n"));
260    }
261    // The mask value is a power of three rather than of two, because a source
262    // is a trit position.
263    out.push_str(&format!(".equ IRQ_TIMER, {}\n", irq::mask(irq::TIMER)));
264    out.push_str(&format!(".equ IRQ_CONSOLE, {}\n", irq::mask(irq::CONSOLE)));
265    out
266}