use super::instr::{Cmp, DatSize, Field, Instruction, OnTrue, Ptr, Reg, Scratch};
use super::regs::ADDR_MASK;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Decoded {
pub instr: Instruction,
pub len: u8,
}
pub fn decode(fetch: impl FnMut(u32) -> u8, pc: u32) -> Decoded {
let mut r = Reader {
fetch,
pc: pc & ADDR_MASK,
len: 0,
seen: [0; 8],
};
let instr = decode_inner(&mut r);
Decoded { instr, len: r.len }
}
struct Reader<F> {
fetch: F,
pc: u32,
len: u8,
seen: [u8; 8],
}
impl<F: FnMut(u32) -> u8> Reader<F> {
fn nib(&mut self) -> u8 {
let addr = self.pc.wrapping_add(u32::from(self.len)) & ADDR_MASK;
let n = (self.fetch)(addr) & 0xF;
if let Some(slot) = self.seen.get_mut(usize::from(self.len)) {
*slot = n;
}
self.len += 1;
n
}
fn imm(&mut self, count: u8) -> u64 {
let mut v = 0u64;
for i in 0..count {
v |= u64::from(self.nib()) << (4 * u32::from(i));
}
v
}
fn here(&self) -> u32 {
self.pc.wrapping_add(u32::from(self.len)) & ADDR_MASK
}
fn rel(&mut self, count: u8, base: u32) -> (i32, u32) {
let raw = self.imm(count);
let bits = 4 * u32::from(count);
let shift = 64 - bits;
let off = ((raw << shift) as i64 >> shift) as i32;
(off, base.wrapping_add_signed(off) & ADDR_MASK)
}
fn invalid(&self) -> Instruction {
Instruction::Invalid {
nibbles: self.seen,
len: self.len,
}
}
fn on_true(&mut self) -> OnTrue {
let base = self.here();
let (off, target) = self.rel(2, base);
if off == 0 {
OnTrue::RtnYes
} else {
OnTrue::GoYes(target)
}
}
}
fn decode_inner<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
let first = r.nib();
match first {
0x0 => decode_0(r),
0x1 => decode_1(r),
0x2 => Instruction::PSet { n: r.nib() },
0x3 => {
let n = r.nib() + 1;
Instruction::LoadConst {
reg: Reg::C,
nibbles: n,
value: r.imm(n),
}
}
0x4 | 0x5 => {
let carry = first == 4;
let base = r.here();
let (off, target) = r.rel(2, base);
match (carry, off) {
(true, 0) => Instruction::RtnC,
(false, 0) => Instruction::RtnNc,
(true, _) => Instruction::Goc { target },
(false, _) => Instruction::Gonc { target },
}
}
0x6 => {
let base = r.here();
let (off, target) = r.rel(3, base);
match off {
3 => Instruction::Nop4,
4 => {
let save = (r.len, r.seen);
if r.nib() == 0 {
Instruction::Nop5
} else {
(r.len, r.seen) = save;
Instruction::Goto { target }
}
}
_ => Instruction::Goto { target },
}
}
0x7 => {
let (off, _) = r.rel(3, 0);
let target = r.here().wrapping_add_signed(off) & ADDR_MASK;
Instruction::Gosub { target }
}
0x8 => decode_8(r),
0x9 => {
let f = r.nib();
let x = r.nib();
let field = field_a_or_b(f);
let yes = r.on_true();
if f < 8 {
test_eq_group(x, field, yes)
} else {
test_order_group(x, field, yes)
}
}
0xA => {
let f = r.nib();
let x = r.nib();
let field = field_a_or_b(f);
if f < 8 {
arith_a_group(x, field)
} else {
arith_b_group(x, field)
}
}
0xB => {
let f = r.nib();
let x = r.nib();
let field = field_a_or_b(f);
if f < 8 {
arith_c_group(x, field)
} else {
arith_d_group(x, field)
}
}
0xC => arith_a_group(r.nib(), Field::A),
0xD => arith_b_group(r.nib(), Field::A),
0xE => arith_c_group(r.nib(), Field::A),
_ => arith_d_group(r.nib(), Field::A),
}
}
fn field_a_or_b(code: u8) -> Field {
match code & 7 {
0 => Field::P,
1 => Field::Wp,
2 => Field::Xs,
3 => Field::X,
4 => Field::S,
5 => Field::M,
6 => Field::B,
_ => Field::W,
}
}
const fn pair_x(x: u8) -> (Reg, Reg) {
match x & 3 {
0 => (Reg::A, Reg::B),
1 => (Reg::B, Reg::C),
2 => (Reg::C, Reg::A),
_ => (Reg::D, Reg::C),
}
}
const fn pair_y(x: u8) -> (Reg, Reg) {
match x & 3 {
0 => (Reg::B, Reg::A),
1 => (Reg::C, Reg::B),
2 => (Reg::A, Reg::C),
_ => (Reg::C, Reg::D),
}
}
fn decode_0<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
match r.nib() {
0x0 => Instruction::RtnSxm,
0x1 => Instruction::Rtn,
0x2 => Instruction::RtnSc,
0x3 => Instruction::RtnCc,
0x4 => Instruction::SetHex,
0x5 => Instruction::SetDec,
0x6 => Instruction::RstkEqC,
0x7 => Instruction::CEqRstk,
0x8 => Instruction::ClrSt,
0x9 => Instruction::CEqSt,
0xA => Instruction::StEqC,
0xB => Instruction::CStEx,
0xC => Instruction::PInc,
0xD => Instruction::PDec,
0xE => {
let Some(field) = Field::from_f_code(r.nib()) else {
return r.invalid();
};
let x = r.nib();
let (dst, src) = if x & 4 == 0 { pair_x(x) } else { pair_y(x) };
if x < 8 {
Instruction::And { dst, src, field }
} else {
Instruction::Or { dst, src, field }
}
}
_ => Instruction::Rti,
}
}
fn scratch_operand(x: u8) -> Option<(Reg, Scratch)> {
let reg = if x & 8 == 0 { Reg::A } else { Reg::C };
Scratch::from_index(x & 7).map(|ss| (reg, ss))
}
fn decode_1<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
let op = r.nib();
match op {
0x0..=0x2 => {
let Some((reg, ss)) = scratch_operand(r.nib()) else {
return r.invalid();
};
scratch_op(op, reg, ss, None)
}
0x3 => decode_13(r.nib()),
0x4 => {
let x = r.nib();
let field = if x < 8 { Field::A } else { Field::B };
dat_op(x, DatSize::Field(field))
}
0x5 => {
let x = r.nib();
let y = r.nib();
if x < 8 {
match Field::from_a_code(y) {
Some(field) => dat_op(x, DatSize::Field(field)),
None => r.invalid(),
}
} else {
dat_op(x, DatSize::Nibbles(y + 1))
}
}
0x6 => Instruction::PtrAdd {
ptr: Ptr::D0,
n: r.nib() + 1,
},
0x7 => Instruction::PtrAdd {
ptr: Ptr::D1,
n: r.nib() + 1,
},
0x8 => Instruction::PtrSub {
ptr: Ptr::D0,
n: r.nib() + 1,
},
0xC => Instruction::PtrSub {
ptr: Ptr::D1,
n: r.nib() + 1,
},
_ => {
let ptr = if op < 0xC { Ptr::D0 } else { Ptr::D1 };
let nibbles = match op & 3 {
1 => 2,
2 => 4,
_ => 5,
};
Instruction::PtrLoad {
ptr,
nibbles,
value: r.imm(nibbles) as u32,
}
}
}
}
fn scratch_op(op: u8, reg: Reg, ss: Scratch, field: Option<Field>) -> Instruction {
match op {
0 => Instruction::ScratchFromReg {
ss,
src: reg,
field,
},
1 => Instruction::RegFromScratch {
dst: reg,
ss,
field,
},
_ => Instruction::RegScratchEx { reg, ss, field },
}
}
fn decode_13(x: u8) -> Instruction {
let ptr = if x & 1 == 0 { Ptr::D0 } else { Ptr::D1 };
let reg = if x & 4 == 0 { Reg::A } else { Reg::C };
let short = x & 8 != 0;
if x & 2 == 0 {
Instruction::PtrFromReg {
ptr,
src: reg,
short,
}
} else {
Instruction::PtrRegEx { ptr, reg, short }
}
}
fn dat_op(x: u8, size: DatSize) -> Instruction {
let ptr = if x & 1 == 0 { Ptr::D0 } else { Ptr::D1 };
let reg = if x & 4 == 0 { Reg::A } else { Reg::C };
if x & 2 == 0 {
Instruction::DatWrite {
ptr,
src: reg,
size,
}
} else {
Instruction::DatRead {
dst: reg,
ptr,
size,
}
}
}
fn decode_8<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
let op = r.nib();
match op {
0x0 => decode_80(r),
0x1 => decode_81(r),
0x2 => match r.nib() {
0 => Instruction::Nop3,
mask => Instruction::HsClear { mask },
},
0x3 => {
let mask = r.nib();
let yes = r.on_true();
Instruction::TestHs { mask, yes }
}
0x4 => Instruction::StClear { bit: r.nib() },
0x5 => Instruction::StSet { bit: r.nib() },
0x6 | 0x7 => {
let set = op == 0x7;
let bit = r.nib();
let yes = r.on_true();
Instruction::TestSt { bit, set, yes }
}
0x8 | 0x9 => {
let eq = op == 0x9;
let n = r.nib();
let yes = r.on_true();
Instruction::TestP { n, eq, yes }
}
0xA => {
let x = r.nib();
let yes = r.on_true();
test_eq_group(x, Field::A, yes)
}
0xB => {
let x = r.nib();
let yes = r.on_true();
test_order_group(x, Field::A, yes)
}
0xC => {
let base = r.here();
let (_, target) = r.rel(4, base);
Instruction::GoLong { target }
}
0xE => {
let (off, _) = r.rel(4, 0);
let target = r.here().wrapping_add_signed(off) & ADDR_MASK;
Instruction::GosubL { target }
}
_ => {
let target = r.imm(5) as u32;
if op == 0xD {
Instruction::GoVLong { target }
} else {
Instruction::GosbVL { target }
}
}
}
}
fn decode_80<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
match r.nib() {
0x0 => Instruction::OutCs,
0x1 => Instruction::OutC,
0x2 => Instruction::In { dst: Reg::A },
0x3 => Instruction::In { dst: Reg::C },
0x4 => Instruction::Uncnfg,
0x5 => Instruction::Config,
0x6 => Instruction::CId,
0x7 => Instruction::Shutdn,
0x8 => decode_808(r),
0x9 => Instruction::CPlusPPlus1,
0xA => Instruction::Reset,
0xB => Instruction::BusCc,
0xC => Instruction::CEqP { n: r.nib() },
0xD => Instruction::PEqC { n: r.nib() },
0xE => Instruction::Sreq,
_ => Instruction::CpEx { n: r.nib() },
}
}
fn decode_808<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
let op = r.nib();
let reg = if op < 8 { Reg::A } else { Reg::C };
match op {
0x0 => Instruction::IntOn,
0x1 => match r.nib() {
0 => Instruction::Rsi,
_ => r.invalid(),
},
0x2 => {
let n = r.nib() + 1;
Instruction::LoadConst {
reg: Reg::A,
nibbles: n,
value: r.imm(n),
}
}
0x3 => Instruction::BusCb,
0x4 | 0x8 => Instruction::BitClear { reg, bit: r.nib() },
0x5 | 0x9 => Instruction::BitSet { reg, bit: r.nib() },
0x6 | 0x7 | 0xA | 0xB => {
let set = op & 1 == 1;
let bit = r.nib();
let yes = r.on_true();
Instruction::TestBit { reg, bit, set, yes }
}
0xC => Instruction::PcEqInd { reg: Reg::A },
0xD => Instruction::BusCd,
0xE => Instruction::PcEqInd { reg: Reg::C },
_ => Instruction::IntOff,
}
}
fn decode_81<F: FnMut(u32) -> u8>(r: &mut Reader<F>) -> Instruction {
let op = r.nib();
match op {
0x0..=0x3 => Instruction::Slc {
reg: Reg::from_index(op),
},
0x4..=0x7 => Instruction::Src {
reg: Reg::from_index(op),
},
0x8 => {
let Some(field) = Field::from_f_code(r.nib()) else {
return r.invalid();
};
let x = r.nib();
if x & 4 != 0 {
return r.invalid();
}
let reg = Reg::from_index(x);
let n = r.nib() + 1;
if x < 8 {
Instruction::AddConst { reg, field, n }
} else {
Instruction::SubConst { reg, field, n }
}
}
0x9 => {
let Some(field) = Field::from_f_code(r.nib()) else {
return r.invalid();
};
let x = r.nib();
if x > 3 {
return r.invalid();
}
Instruction::Srb {
reg: Reg::from_index(x),
field: Some(field),
}
}
0xA => {
let Some(field) = Field::from_f_code(r.nib()) else {
return r.invalid();
};
let sub = r.nib();
if sub > 2 {
return r.invalid();
}
match scratch_operand(r.nib()) {
Some((reg, ss)) => scratch_op(sub, reg, ss, Some(field)),
None => r.invalid(),
}
}
0xB => {
let x = r.nib();
let reg = if x & 1 == 0 { Reg::A } else { Reg::C };
match x {
0x2 | 0x3 => Instruction::PcEqReg { reg },
0x4 | 0x5 => Instruction::RegEqPc { reg },
0x6 | 0x7 => Instruction::RegPcEx { reg },
_ => r.invalid(),
}
}
_ => Instruction::Srb {
reg: Reg::from_index(op),
field: None,
},
}
}
fn test_eq_group(x: u8, field: Field, yes: OnTrue) -> Instruction {
let op = if x & 4 == 0 { Cmp::Eq } else { Cmp::Ne };
if x < 8 {
let (lhs, rhs) = match x & 3 {
0 => (Reg::A, Reg::B),
1 => (Reg::B, Reg::C),
2 => (Reg::A, Reg::C),
_ => (Reg::C, Reg::D),
};
Instruction::TestCmp {
op,
lhs,
rhs,
field,
yes,
}
} else {
Instruction::TestZero {
op,
reg: Reg::from_index(x),
field,
yes,
}
}
}
fn test_order_group(x: u8, field: Field, yes: OnTrue) -> Instruction {
let op = match x >> 2 {
0 => Cmp::Gt,
1 => Cmp::Lt,
2 => Cmp::Ge,
_ => Cmp::Le,
};
let (lhs, rhs) = pair_x(x);
Instruction::TestCmp {
op,
lhs,
rhs,
field,
yes,
}
}
fn arith_a_group(x: u8, field: Field) -> Instruction {
match x {
0x0..=0x3 => {
let (dst, src) = pair_x(x);
Instruction::Add { dst, src, field }
}
0x4..=0x7 => {
let reg = Reg::from_index(x);
Instruction::Add {
dst: reg,
src: reg,
field,
}
}
0x8..=0xB => {
let (dst, src) = pair_y(x);
Instruction::Add { dst, src, field }
}
_ => Instruction::Dec {
reg: Reg::from_index(x),
field,
},
}
}
fn arith_b_group(x: u8, field: Field) -> Instruction {
match x {
0x0..=0x3 => Instruction::Zero {
reg: Reg::from_index(x),
field,
},
0x4..=0x7 => {
let (dst, src) = pair_x(x);
Instruction::Copy { dst, src, field }
}
0x8..=0xB => {
let (dst, src) = pair_y(x);
Instruction::Copy { dst, src, field }
}
_ => {
let (a, b) = match x & 3 {
0 => (Reg::A, Reg::B),
1 => (Reg::B, Reg::C),
2 => (Reg::A, Reg::C),
_ => (Reg::C, Reg::D),
};
Instruction::Exch { a, b, field }
}
}
}
fn arith_c_group(x: u8, field: Field) -> Instruction {
match x {
0x0..=0x3 => {
let (dst, src) = pair_x(x);
Instruction::Sub { dst, src, field }
}
0x4..=0x7 => Instruction::Inc {
reg: Reg::from_index(x),
field,
},
0x8..=0xB => {
let (dst, src) = pair_y(x);
Instruction::Sub { dst, src, field }
}
_ => {
let (dst, src) = pair_x(x);
Instruction::SubRev { dst, src, field }
}
}
}
fn arith_d_group(x: u8, field: Field) -> Instruction {
let reg = Reg::from_index(x);
match x >> 2 {
0 => Instruction::Sl { reg, field },
1 => Instruction::Sr { reg, field },
2 => Instruction::Neg { reg, field },
_ => Instruction::Not { reg, field },
}
}
#[cfg(test)]
#[path = "decode_tests.rs"]
mod tests;