#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TritWire(pub u8);
impl TritWire {
pub const NEG: TritWire = TritWire(0b01); pub const POS: TritWire = TritWire(0b10); pub const HOLD: TritWire = TritWire(0b11); pub const FAULT:TritWire = TritWire(0b00);
pub fn from_i8(v: i8) -> Self {
match v {
-1 => Self::NEG,
1 => Self::POS,
0 => Self::HOLD,
_ => Self::FAULT,
}
}
pub fn to_i8(self) -> i8 {
match self.0 {
0b01 => -1,
0b10 => 1,
0b11 => 0,
_ => 0, }
}
pub fn is_hold(self) -> bool { self == Self::HOLD }
pub fn is_pos(self) -> bool { self == Self::POS }
pub fn is_neg(self) -> bool { self == Self::NEG }
}
impl std::fmt::Display for TritWire {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_i8())
}
}
pub fn trit_neg(a: TritWire) -> TritWire {
TritWire(((a.0 & 1) << 1) | ((a.0 >> 1) & 1))
}
pub fn trit_cons(a: TritWire, b: TritWire) -> TritWire {
if a == b { a } else { TritWire::HOLD }
}
pub fn trit_mul(a: TritWire, b: TritWire) -> TritWire {
if a.is_hold() || b.is_hold() { return TritWire::HOLD; }
if a == b { TritWire::POS } else { TritWire::NEG }
}
pub fn trit_add(a: TritWire, b: TritWire) -> (TritWire, TritWire) {
let sum = a.to_i8() + b.to_i8();
match sum {
-2 => (TritWire::POS, TritWire::NEG), -1 => (TritWire::NEG, TritWire::HOLD),
0 => (TritWire::HOLD, TritWire::HOLD),
1 => (TritWire::POS, TritWire::HOLD),
2 => (TritWire::NEG, TritWire::POS), _ => (TritWire::HOLD, TritWire::HOLD),
}
}
pub struct BetRegfile {
regs: [TritWire; 27],
}
impl BetRegfile {
pub fn new() -> Self {
Self { regs: [TritWire::HOLD; 27] } }
pub fn read(&self, addr: u8) -> TritWire {
self.regs.get(addr as usize).copied().unwrap_or(TritWire::HOLD)
}
pub fn write(&mut self, addr: u8, data: TritWire) {
if (addr as usize) < 27 {
self.regs[addr as usize] = data;
}
}
pub fn dump(&self) -> Vec<i8> {
self.regs.iter().map(|t| t.to_i8()).collect()
}
}
pub struct BetPc {
pub pc: u16,
}
impl BetPc {
pub fn new() -> Self { Self { pc: 0 } }
pub fn tick(&mut self, load: bool, next_pc: u16) {
if load { self.pc = next_pc; } else { self.pc = self.pc.wrapping_add(1); }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AluOp { Add = 0, Mul = 1, Neg = 2, Cons = 3 }
pub fn bet_alu(op: AluOp, a: TritWire, b: TritWire) -> (TritWire, TritWire) {
match op {
AluOp::Add => trit_add(a, b),
AluOp::Mul => (trit_mul(a, b), TritWire::HOLD),
AluOp::Neg => (trit_neg(a), TritWire::HOLD),
AluOp::Cons => (trit_cons(a, b), TritWire::HOLD),
}
}
#[derive(Debug, Clone, Copy)]
pub struct ControlSignals {
pub alu_op: AluOp,
pub reg_we: bool, pub pc_load: bool, pub is_push: bool, pub is_halt: bool, pub is_store: bool, pub is_load: bool, pub is_jmp: bool, pub jmp_cond: JmpCond,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JmpCond { Pos, Zero, Neg, Always }
impl ControlSignals {
fn noop() -> Self {
Self {
alu_op: AluOp::Add, reg_we: false, pc_load: false,
is_push: false, is_halt: false, is_store: false, is_load: false,
is_jmp: false, jmp_cond: JmpCond::Always,
}
}
}
pub fn bet_decode(opcode: u8) -> ControlSignals {
let mut c = ControlSignals::noop();
match opcode {
0x00 => { c.is_halt = true; }
0x01 => { c.is_push = true; } 0x02 => { c.alu_op = AluOp::Add; c.reg_we = false; } 0x03 => { c.alu_op = AluOp::Mul; c.reg_we = false; } 0x04 => { c.alu_op = AluOp::Neg; c.reg_we = false; } 0x05 => { c.is_jmp = true; c.jmp_cond = JmpCond::Pos; } 0x06 => { c.is_jmp = true; c.jmp_cond = JmpCond::Zero; } 0x07 => { c.is_jmp = true; c.jmp_cond = JmpCond::Neg; } 0x08 => { c.is_store = true; } 0x09 => { c.is_load = true; } 0x0b => { c.is_jmp = true; c.jmp_cond = JmpCond::Always; } 0x0e => { c.alu_op = AluOp::Cons; c.reg_we = false; } _ => {} }
c
}
#[derive(Debug, Clone)]
pub struct CycleState {
pub cycle: u64,
pub pc: u16,
pub opcode: u8,
pub stack: Vec<i8>, pub carry: i8,
pub regs: Vec<i8>, }
#[derive(Debug)]
pub struct RtlTrace {
pub cycles: u64,
pub halted: bool,
pub cycles_state: Vec<CycleState>,
pub final_regs: Vec<i8>, pub final_stack: Vec<i8>,
}
pub struct BetRtlProcessor {
pub regfile: BetRegfile,
pub pc: BetPc,
pub stack: Vec<TritWire>,
pub carry: TritWire,
pub code: Vec<u8>,
pub halted: bool,
}
impl BetRtlProcessor {
pub fn new(code: Vec<u8>) -> Self {
Self {
regfile: BetRegfile::new(),
pc: BetPc::new(),
stack: Vec::new(),
carry: TritWire::HOLD,
code,
halted: false,
}
}
fn fetch(&self) -> Option<u8> {
self.code.get(self.pc.pc as usize).copied()
}
fn read_u16_at(&self, offset: usize) -> u16 {
let lo = self.code.get(offset).copied().unwrap_or(0) as u16;
let hi = self.code.get(offset + 1).copied().unwrap_or(0) as u16;
lo | (hi << 8)
}
pub fn tick(&mut self) -> bool {
let opcode = match self.fetch() {
Some(op) => op,
None => { self.halted = true; return false; }
};
let ctrl = bet_decode(opcode);
let pc_now = self.pc.pc;
if ctrl.is_halt {
self.halted = true;
return false;
}
let mut pc_load = false;
let mut next_pc = pc_now.wrapping_add(1);
if ctrl.is_push {
let byte = self.code.get(pc_now as usize + 1).copied().unwrap_or(0b11);
let tw = TritWire(byte & 0b11);
let tw = if tw == TritWire::FAULT { TritWire::HOLD } else { tw };
self.stack.push(tw);
self.pc.tick(false, 0);
self.pc.tick(false, 0); return true;
}
if ctrl.is_store {
let reg = self.code.get(pc_now as usize + 1).copied().unwrap_or(0);
let val = self.stack.pop().unwrap_or(TritWire::HOLD);
self.regfile.write(reg, val);
self.pc.tick(false, 0);
self.pc.tick(false, 0);
return true;
}
if ctrl.is_load {
let reg = self.code.get(pc_now as usize + 1).copied().unwrap_or(0);
let val = self.regfile.read(reg);
self.stack.push(val);
self.pc.tick(false, 0);
self.pc.tick(false, 0);
return true;
}
if ctrl.is_jmp {
let addr = self.read_u16_at(pc_now as usize + 1);
let top = self.stack.pop().unwrap_or(TritWire::HOLD);
let take = match ctrl.jmp_cond {
JmpCond::Always => true,
JmpCond::Pos => top.is_pos(),
JmpCond::Zero => top.is_hold(),
JmpCond::Neg => top.is_neg(),
};
if take { pc_load = true; next_pc = addr; }
if ctrl.jmp_cond == JmpCond::Always {
self.stack.push(top);
}
if !pc_load { next_pc = pc_now.wrapping_add(3); }
self.pc.tick(pc_load, next_pc);
return true;
}
match opcode {
0x04 => { let a = self.stack.pop().unwrap_or(TritWire::HOLD);
let (y, _) = bet_alu(AluOp::Neg, a, TritWire::HOLD);
self.stack.push(y);
}
0x02 | 0x03 | 0x0e => { let b = self.stack.pop().unwrap_or(TritWire::HOLD);
let a = self.stack.pop().unwrap_or(TritWire::HOLD);
let op = match opcode {
0x02 => AluOp::Add,
0x03 => AluOp::Mul,
_ => AluOp::Cons,
};
let (y, c) = bet_alu(op, a, b);
self.stack.push(y);
self.carry = c;
}
_ => {} }
self.pc.tick(pc_load, next_pc);
true
}
pub fn run(&mut self, max_cycles: u64) -> RtlTrace {
let mut states = Vec::new();
for cycle in 0..max_cycles {
let snap = CycleState {
cycle,
pc: self.pc.pc,
opcode: self.fetch().unwrap_or(0),
stack: self.stack.iter().map(|t| t.to_i8()).collect(),
carry: self.carry.to_i8(),
regs: self.regfile.regs[..10].iter().map(|t| t.to_i8()).collect(),
};
states.push(snap);
if !self.tick() { break; }
}
RtlTrace {
cycles: states.len() as u64,
halted: self.halted,
cycles_state: states,
final_regs: self.regfile.dump(),
final_stack: self.stack.iter().map(|t| t.to_i8()).collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_trit_wire_encoding() {
assert_eq!(TritWire::NEG.to_i8(), -1);
assert_eq!(TritWire::POS.to_i8(), 1);
assert_eq!(TritWire::HOLD.to_i8(), 0);
assert_eq!(TritWire::from_i8(-1), TritWire::NEG);
assert_eq!(TritWire::from_i8(1), TritWire::POS);
assert_eq!(TritWire::from_i8(0), TritWire::HOLD);
}
#[test]
fn test_trit_neg() {
assert_eq!(trit_neg(TritWire::POS), TritWire::NEG);
assert_eq!(trit_neg(TritWire::NEG), TritWire::POS);
assert_eq!(trit_neg(TritWire::HOLD), TritWire::HOLD);
}
#[test]
fn test_trit_cons() {
assert_eq!(trit_cons(TritWire::POS, TritWire::POS), TritWire::POS);
assert_eq!(trit_cons(TritWire::NEG, TritWire::NEG), TritWire::NEG);
assert_eq!(trit_cons(TritWire::POS, TritWire::NEG), TritWire::HOLD);
assert_eq!(trit_cons(TritWire::POS, TritWire::HOLD), TritWire::HOLD);
assert_eq!(trit_cons(TritWire::HOLD, TritWire::HOLD), TritWire::HOLD);
}
#[test]
fn test_trit_mul() {
assert_eq!(trit_mul(TritWire::POS, TritWire::POS), TritWire::POS);
assert_eq!(trit_mul(TritWire::NEG, TritWire::NEG), TritWire::POS);
assert_eq!(trit_mul(TritWire::POS, TritWire::NEG), TritWire::NEG);
assert_eq!(trit_mul(TritWire::POS, TritWire::HOLD), TritWire::HOLD);
assert_eq!(trit_mul(TritWire::HOLD, TritWire::HOLD), TritWire::HOLD);
}
#[test]
fn test_trit_add() {
let (s, c) = trit_add(TritWire::POS, TritWire::POS);
assert_eq!(s.to_i8(), -1); assert_eq!(c.to_i8(), 1);
let (s, c) = trit_add(TritWire::NEG, TritWire::NEG);
assert_eq!(s.to_i8(), 1); assert_eq!(c.to_i8(), -1);
let (s, c) = trit_add(TritWire::POS, TritWire::NEG);
assert_eq!(s.to_i8(), 0);
assert_eq!(c.to_i8(), 0);
let (s, c) = trit_add(TritWire::HOLD, TritWire::POS);
assert_eq!(s.to_i8(), 1);
assert_eq!(c.to_i8(), 0);
}
#[test]
fn test_rtl_add_pos_neg() {
let code = vec![0x01, 0b10, 0x01, 0b01, 0x02, 0x00];
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted, "should halt");
assert_eq!(trace.final_stack, vec![0], "result should be 0");
assert_eq!(trace.final_regs[0], 0);
}
#[test]
fn test_rtl_neg() {
let code = vec![0x01, 0b10, 0x04, 0x00]; let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted);
assert_eq!(trace.final_stack, vec![-1]);
}
#[test]
fn test_rtl_store_load() {
let code = vec![
0x01, 0b10, 0x08, 0x00, 0x09, 0x00, 0x00, ];
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted);
assert_eq!(trace.final_stack, vec![1]);
assert_eq!(trace.final_regs[0], 1);
}
#[test]
fn test_rtl_conditional_jump_taken() {
let code = vec![
0x01, 0b10, 0x05, 9, 0, 0x01, 0b01, 0x01, 0b11, 0x00, ];
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted);
assert_eq!(trace.final_stack, vec![]);
}
#[test]
fn test_rtl_cons() {
let code = vec![
0x01, 0b10, 0x01, 0b10, 0x0e, 0x00, ];
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted);
assert_eq!(trace.final_stack, vec![1]); }
#[test]
fn test_rtl_cons_conflict_to_hold() {
let code = vec![
0x01, 0b10, 0x01, 0b01, 0x0e, 0x00, ];
let mut proc = BetRtlProcessor::new(code);
let trace = proc.run(100);
assert!(trace.halted);
assert_eq!(trace.final_stack, vec![0]); }
#[test]
fn test_regfile_reset() {
let rf = BetRegfile::new();
for i in 0..27u8 {
assert_eq!(rf.read(i).to_i8(), 0, "reg {} should init to hold", i);
}
}
#[test]
fn test_add_carry_chain() {
let (s, c) = trit_add(TritWire::POS, TritWire::POS);
let (s2, c2) = trit_add(s, c); assert_eq!(s2.to_i8(), 0);
assert_eq!(c2.to_i8(), 0);
}
}