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}