1#![forbid(unsafe_code)]
25#![warn(missing_docs)]
26
27use std::fmt;
28
29use ternaria_arith::{Trit, Tryte, Word};
30use ternaria_isa::REG_COUNT;
31use ternaria_mem::{Addressable, MemoryError, WORD_TRYTES};
32
33pub mod csr;
36pub mod mmu;
37
38pub use csr::{Cause, Csrs, Priv};
39pub use mmu::{Access, Entry as PageEntry};
40pub use ternaria_isa::{DecodeError, Imm, Instruction, Opcode, Reg, asm};
41pub use ternaria_mem::MemoryError as MemError;
42
43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
45pub enum Trap {
46 IllegalInstruction {
48 pc: i64,
50 cause: DecodeError,
52 },
53 Memory {
55 pc: i64,
57 cause: MemoryError,
59 },
60 DivideByZero {
62 pc: i64,
64 },
65 Overflow {
67 pc: i64,
69 op: Opcode,
71 },
72 PcOutOfRange {
74 pc: i64,
76 },
77 Ecall {
79 pc: i64,
81 },
82 Privilege {
84 pc: i64,
86 needed: Priv,
88 },
89 PageFault {
91 pc: i64,
93 addr: i64,
95 },
96 StepLimit {
98 pc: i64,
100 steps: u64,
102 },
103}
104
105impl fmt::Display for Trap {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self {
108 Trap::IllegalInstruction { pc, cause } => {
109 write!(f, "illegal instruction at pc={pc}: {cause}")
110 }
111 Trap::Memory { pc, cause } => write!(f, "memory fault at pc={pc}: {cause}"),
112 Trap::DivideByZero { pc } => write!(f, "divide by zero at pc={pc}"),
113 Trap::Overflow { pc, op } => {
114 write!(f, "arithmetic overflow in {} at pc={pc}", op.mnemonic())
115 }
116 Trap::PcOutOfRange { pc } => write!(f, "pc left the address space at pc={pc}"),
117 Trap::Ecall { pc } => write!(f, "ecall at pc={pc} with no trap vector set"),
118 Trap::Privilege { pc, needed } => {
119 write!(f, "instruction at pc={pc} requires {needed:?} privilege")
120 }
121 Trap::PageFault { pc, addr } => {
122 write!(f, "page fault on address {addr} at pc={pc}")
123 }
124 Trap::StepLimit { pc, steps } => {
125 write!(f, "step limit reached after {steps} steps at pc={pc}")
126 }
127 }
128 }
129}
130
131impl std::error::Error for Trap {}
132
133fn describe(trap: Trap) -> (Cause, i64, i64) {
135 match trap {
136 Trap::IllegalInstruction { pc, .. } => (Cause::IllegalInstruction, 0, pc),
137 Trap::Memory { pc, cause } => (Cause::Memory, faulting_address(cause), pc),
138 Trap::DivideByZero { pc } => (Cause::DivideByZero, 0, pc),
139 Trap::Overflow { pc, op } => (Cause::Overflow, op.value(), pc),
140 Trap::PcOutOfRange { pc } => (Cause::PcOutOfRange, pc, pc),
141 Trap::Ecall { pc } => (Cause::Ecall, 0, pc),
142 Trap::Privilege { pc, needed } => {
143 (Cause::PrivilegeViolation, needed.trit() as i8 as i64, pc)
144 }
145 Trap::PageFault { pc, addr } => (Cause::PageFault, addr, pc),
146 Trap::StepLimit { pc, .. } => (Cause::IllegalInstruction, 0, pc),
148 }
149}
150
151fn faulting_address(e: MemoryError) -> i64 {
153 match e {
154 MemoryError::Uninitialised { addr }
155 | MemoryError::Misaligned { addr }
156 | MemoryError::Device { addr, .. }
157 | MemoryError::AddressOverflow { addr, .. } => addr,
158 MemoryError::NotAByte { value } => value,
159 }
160}
161
162#[derive(Clone, Copy, PartialEq, Eq, Debug)]
164pub struct Halted {
165 pub steps: u64,
167}
168
169#[derive(Clone)]
171pub struct Cpu {
172 regs: [Word; REG_COUNT],
173 pc: Word,
174 halted: bool,
175 steps: u64,
176 pub csrs: Csrs,
179 privilege: Priv,
180}
181
182impl Cpu {
183 pub fn new(entry: Word) -> Cpu {
185 Cpu {
186 regs: [Word::ZERO; REG_COUNT],
187 pc: entry,
188 halted: false,
189 steps: 0,
190 csrs: Csrs::default(),
191 privilege: Priv::Machine,
194 }
195 }
196
197 #[inline]
199 pub fn reg(&self, r: Reg) -> Word {
200 if r == Reg::ZERO {
201 Word::ZERO
202 } else {
203 self.regs[r.index()]
204 }
205 }
206
207 #[inline]
211 pub fn set_reg(&mut self, r: Reg, value: Word) {
212 if r != Reg::ZERO {
213 self.regs[r.index()] = value;
214 }
215 }
216
217 pub fn pc(&self) -> Word {
219 self.pc
220 }
221
222 pub fn set_pc(&mut self, pc: Word) {
224 self.pc = pc;
225 }
226
227 pub fn halted(&self) -> bool {
229 self.halted
230 }
231
232 pub fn steps(&self) -> u64 {
234 self.steps
235 }
236
237 pub fn privilege(&self) -> Priv {
239 self.privilege
240 }
241
242 pub fn set_privilege(&mut self, level: Priv) {
247 self.privilege = level;
248 }
249
250 pub fn reg_value(&self, n: i8) -> i64 {
255 self.reg(Reg::new(n).expect("register number out of range"))
256 .value()
257 }
258
259 pub fn load_word<A: Addressable>(&self, virt: i64, mem: &mut A) -> Result<Word, Trap> {
264 let addr = self.access(virt, Access::Read, mem)?;
265 mem.read_word(addr).map_err(|cause| Trap::Memory {
266 pc: self.pc.value(),
267 cause,
268 })
269 }
270
271 fn translate<A: Addressable>(
277 &self,
278 virt: i64,
279 access: Access,
280 mem: &mut A,
281 ) -> Result<i64, Trap> {
282 if self.privilege == Priv::Machine || self.csrs.ptbr == 0 {
283 return Ok(virt);
284 }
285 let pc = self.pc.value();
286 let fault = || Trap::PageFault { pc, addr: virt };
287 let va = Word::new(virt).ok_or_else(fault)?;
288 if !mmu::is_canonical(va) {
289 return Err(fault());
290 }
291
292 let mut table = self.csrs.ptbr;
293 for level in (0..mmu::LEVELS).rev() {
294 let slot = table
295 .checked_add(
296 mmu::index(va, level)
297 .checked_mul(WORD_TRYTES)
298 .ok_or_else(fault)?,
299 )
300 .ok_or_else(fault)?;
301 let word = mem
302 .read_word(Word::new(slot).ok_or_else(fault)?)
303 .map_err(|_| fault())?;
304 let entry = mmu::Entry(word);
305 if !entry.flag(mmu::flag::VALID) {
306 return Err(fault());
307 }
308 if entry.flag(mmu::flag::LEAF) {
309 if !entry.flag(access.flag()) {
310 return Err(fault());
311 }
312 if self.privilege == Priv::User && !entry.flag(mmu::flag::USER) {
313 return Err(fault());
314 }
315 let base = entry
318 .ppn()
319 .checked_mul(mmu::PAGE_TRYTES)
320 .ok_or_else(fault)?;
321 return base
322 .checked_add(mmu::passthrough(va, level))
323 .ok_or_else(fault);
324 }
325 table = entry
326 .ppn()
327 .checked_mul(mmu::PAGE_TRYTES)
328 .ok_or_else(fault)?;
329 }
330 Err(fault())
332 }
333
334 fn access<A: Addressable>(&self, virt: i64, access: Access, mem: &mut A) -> Result<Word, Trap> {
336 let phys = self.translate(virt, access, mem)?;
337 let addr = Word::new(phys).ok_or(Trap::Memory {
338 pc: self.pc.value(),
339 cause: MemoryError::AddressOverflow {
340 addr: phys,
341 len: WORD_TRYTES,
342 },
343 })?;
344 if mem.is_privileged(addr) {
345 self.require(Priv::Supervisor, self.pc.value())?;
346 }
347 Ok(addr)
348 }
349
350 fn require(&self, needed: Priv, pc: i64) -> Result<(), Trap> {
352 if self.privilege >= needed {
353 Ok(())
354 } else {
355 Err(Trap::Privilege { pc, needed })
356 }
357 }
358
359 fn jump_by(&mut self, instructions: i64) -> Result<(), Trap> {
362 let here = self.pc.value();
363 let target = instructions
364 .checked_mul(WORD_TRYTES)
365 .and_then(|d| here.checked_add(d))
366 .ok_or(Trap::PcOutOfRange { pc: here })?;
367 self.pc = Word::new(target).ok_or(Trap::PcOutOfRange { pc: here })?;
368 Ok(())
369 }
370
371 pub fn step<A: Addressable>(&mut self, mem: &mut A) -> Result<bool, Trap> {
382 if self.halted {
383 return Ok(false);
384 }
385
386 self.csrs.ip = mem.interrupts();
390 if let Some(cause) = self.csrs.pending_interrupt()
391 && self.csrs.tvec != 0
392 {
393 self.enter_trap(cause, 0, self.pc.value())?;
394 return Ok(true);
395 }
396
397 match self.step_once(mem) {
398 Ok(()) => {
399 self.steps += 1;
400 Ok(!self.halted)
401 }
402 Err(trap) => {
403 self.steps += 1;
404 self.take(trap).map(|()| !self.halted)
405 }
406 }
407 }
408
409 fn step_once<A: Addressable>(&mut self, mem: &mut A) -> Result<(), Trap> {
411 let here = self.pc.value();
412 let phys = self.translate(here, Access::Execute, mem)?;
413 let word = mem
414 .read_word(Word::from_value(phys))
415 .map_err(|cause| Trap::Memory { pc: here, cause })?;
416 let instr = Instruction::decode(word)
417 .map_err(|cause| Trap::IllegalInstruction { pc: here, cause })?;
418 self.execute(instr, mem)
419 }
420
421 fn take(&mut self, trap: Trap) -> Result<(), Trap> {
423 if self.csrs.tvec == 0 || matches!(trap, Trap::StepLimit { .. }) {
426 return Err(trap);
427 }
428 let (cause, tval, pc) = describe(trap);
429 self.enter_trap(cause, tval, pc)
430 }
431
432 fn enter_trap(&mut self, cause: Cause, tval: i64, pc: i64) -> Result<(), Trap> {
434 self.csrs.tepc = pc;
435 self.csrs.tcause = cause.value();
436 self.csrs.tval = tval;
437 self.csrs.tpriv = self.privilege;
438 self.csrs.tie = self.csrs.ie;
442 self.csrs.ie = Word::ZERO;
443 self.privilege = Priv::Machine;
444 self.pc = Word::new(self.csrs.tvec).ok_or(Trap::PcOutOfRange { pc: self.csrs.tvec })?;
445 Ok(())
446 }
447
448 pub fn run<A: Addressable>(&mut self, mem: &mut A, max_steps: u64) -> Result<Halted, Trap> {
451 while self.steps < max_steps {
452 if !self.step(mem)? {
453 return Ok(Halted { steps: self.steps });
454 }
455 }
456 Err(Trap::StepLimit {
457 pc: self.pc.value(),
458 steps: self.steps,
459 })
460 }
461
462 fn execute<A: Addressable>(&mut self, instr: Instruction, mem: &mut A) -> Result<(), Trap> {
464 let here = self.pc.value();
465 let op = instr.op;
466 let a = self.reg(instr.rs1);
467 let b = self.reg(instr.rs2);
468 let imm = instr.imm.value() as i64;
469
470 let checked =
472 |r: Option<Word>| -> Result<Word, Trap> { r.ok_or(Trap::Overflow { pc: here, op }) };
473
474 match op {
476 Opcode::Nop => {}
477 Opcode::Add => {
478 let v = checked(a.checked_add(b))?;
479 self.set_reg(instr.rd, v);
480 }
481 Opcode::Sub => {
482 let v = checked(a.checked_sub(b))?;
483 self.set_reg(instr.rd, v);
484 }
485 Opcode::Mul => {
486 let v = checked(a.checked_mul(b))?;
487 self.set_reg(instr.rd, v);
488 }
489 Opcode::Div => {
490 let v = a.checked_div(b).ok_or(Trap::DivideByZero { pc: here })?;
491 self.set_reg(instr.rd, v);
492 }
493 Opcode::Rem => {
494 let v = a.checked_rem(b).ok_or(Trap::DivideByZero { pc: here })?;
495 self.set_reg(instr.rd, v);
496 }
497 Opcode::Min => self.set_reg(instr.rd, a.trit_and(b)),
498 Opcode::Max => self.set_reg(instr.rd, a.trit_or(b)),
499 Opcode::Cmp3 => {
500 let t = a.cmp3(b);
501 self.set_reg(instr.rd, Word::from_value(t.value() as i64));
502 }
503 Opcode::Neg => self.set_reg(instr.rd, a.neg()),
505 Opcode::Abs => self.set_reg(instr.rd, a.abs()),
506 Opcode::Addi => {
507 let v = checked(a.checked_add(Word::from_value(imm)))?;
508 self.set_reg(instr.rd, v);
509 }
510 Opcode::Shl => {
512 let v = if imm >= 0 {
513 checked(a.checked_shl_trits(imm as u32))?
514 } else {
515 a.shr_trits((-imm) as u32)
516 };
517 self.set_reg(instr.rd, v);
518 }
519 Opcode::Shr => {
520 let v = if imm >= 0 {
521 a.shr_trits(imm as u32)
522 } else {
523 checked(a.checked_shl_trits((-imm) as u32))?
524 };
525 self.set_reg(instr.rd, v);
526 }
527 Opcode::Lw => {
528 let virt = checked(a.checked_add(Word::from_value(imm)))?;
529 let addr = self.access(virt.value(), Access::Read, mem)?;
530 let v = mem
531 .read_word(addr)
532 .map_err(|cause| Trap::Memory { pc: here, cause })?;
533 self.set_reg(instr.rd, v);
534 }
535 Opcode::Lt => {
536 let virt = checked(a.checked_add(Word::from_value(imm)))?;
537 let addr = self.access(virt.value(), Access::Read, mem)?;
538 let v = mem
539 .read_tryte(addr)
540 .map_err(|cause| Trap::Memory { pc: here, cause })?;
541 self.set_reg(instr.rd, Word::from_value(v.value() as i64));
542 }
543 Opcode::St => {
544 let virt = checked(a.checked_add(Word::from_value(imm)))?;
545 let addr = self.access(virt.value(), Access::Write, mem)?;
546 let v = self.reg(instr.rd);
547 let t = i32::try_from(v.value())
550 .ok()
551 .and_then(Tryte::new)
552 .ok_or(Trap::Overflow { pc: here, op })?;
553 mem.write_tryte(addr, t)
554 .map_err(|cause| Trap::Memory { pc: here, cause })?;
555 }
556 Opcode::Sw => {
557 let virt = checked(a.checked_add(Word::from_value(imm)))?;
558 let addr = self.access(virt.value(), Access::Write, mem)?;
559 let v = self.reg(instr.rd);
560 mem.write_word(addr, v)
561 .map_err(|cause| Trap::Memory { pc: here, cause })?;
562 }
563 Opcode::Jal => {
564 let link = self.next_pc()?;
565 self.set_reg(instr.rd, link);
566 self.jump_by(imm)?;
567 return Ok(());
568 }
569 Opcode::Jalr => {
570 let link = self.next_pc()?;
571 let target = a.value();
574 self.set_reg(instr.rd, link);
575 let dest = imm
576 .checked_mul(WORD_TRYTES)
577 .and_then(|d| target.checked_add(d))
578 .ok_or(Trap::PcOutOfRange { pc: here })?;
579 self.pc = Word::new(dest).ok_or(Trap::PcOutOfRange { pc: here })?;
580 return Ok(());
581 }
582 Opcode::Br3 => {
583 match a.signum() {
585 Trit::Pos => self.jump_by(imm)?,
586 Trit::Neg => self.jump_by(-imm)?,
587 Trit::Zero => self.jump_by(1)?,
588 }
589 return Ok(());
590 }
591 Opcode::Brz => {
592 let taken = a.signum() == Trit::Zero;
593 self.jump_by(if taken { imm } else { 1 })?;
594 return Ok(());
595 }
596 Opcode::Brn => {
599 let taken = a.signum() == Trit::Neg;
600 self.jump_by(if taken { imm } else { 1 })?;
601 return Ok(());
602 }
603 Opcode::Csrr => {
604 self.require(Priv::Supervisor, here)?;
605 let v = self
606 .csrs
607 .read(imm, self.privilege)
608 .ok_or(Trap::IllegalInstruction {
609 pc: here,
610 cause: DecodeError::UnknownOpcode { value: imm },
611 })?;
612 self.set_reg(instr.rd, Word::from_value(v));
613 }
614 Opcode::Csrw => {
615 self.require(Priv::Supervisor, here)?;
616 self.csrs
617 .write(imm, a.value())
618 .ok_or(Trap::IllegalInstruction {
619 pc: here,
620 cause: DecodeError::UnknownOpcode { value: imm },
621 })?;
622 }
623 Opcode::Tret => {
624 self.require(Priv::Supervisor, here)?;
625 self.privilege = self.csrs.tpriv;
626 self.csrs.ie = self.csrs.tie;
627 self.pc =
628 Word::new(self.csrs.tepc).ok_or(Trap::PcOutOfRange { pc: self.csrs.tepc })?;
629 return Ok(());
630 }
631 Opcode::Ecall => return Err(Trap::Ecall { pc: here }),
634 Opcode::Brp => {
635 let taken = a.signum() == Trit::Pos;
636 self.jump_by(if taken { imm } else { 1 })?;
637 return Ok(());
638 }
639 Opcode::Halt => {
640 self.halted = true;
641 self.steps += 1;
642 return Ok(());
643 }
644 }
645
646 self.jump_by(1)
647 }
648
649 fn next_pc(&self) -> Result<Word, Trap> {
651 let here = self.pc.value();
652 here.checked_add(WORD_TRYTES)
653 .and_then(Word::new)
654 .ok_or(Trap::PcOutOfRange { pc: here })
655 }
656}
657
658pub fn load<A: Addressable>(
661 mem: &mut A,
662 at: Word,
663 program: &[Instruction],
664) -> Result<(), MemoryError> {
665 let mut addr = at.value();
666 for instr in program {
667 mem.write_word(Word::from_value(addr), instr.encode())?;
668 addr += WORD_TRYTES;
669 }
670 Ok(())
671}
672
673pub fn assemble_and_load<A: Addressable>(
675 mem: &mut A,
676 at: Word,
677 source: &str,
678) -> Result<Cpu, Box<dyn std::error::Error>> {
679 let program = ternaria_isa::asm::assemble_at(source, at.value())?;
681 load(mem, at, &program.instructions)?;
682 Ok(Cpu::new(at))
683}