use std::mem::offset_of;
use cranelift_codegen::ir::{InstBuilder, Value, condcodes::IntCC, types};
use super::super::state::State;
use super::alu::{self, ShiftType};
use super::{Attributes, Emitter, Fault, Flag, Size, Translated, Undefined, bits};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Indexing {
index: bool,
add: bool,
wback: bool,
}
const OFFSET: Indexing = Indexing {
index: true,
add: true,
wback: false,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Offset {
Immediate(u32),
Register {
m: u32,
shift: u32,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Step {
IncrementAfter,
DecrementBefore,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Exclusive {
t: u32,
n: u32,
imm32: u32,
size: Size,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Single {
t: u32,
n: u32,
offset: Offset,
indexing: Indexing,
size: Size,
signed: bool,
unprivileged: bool,
}
impl Single {
fn immediate(t: u32, n: u32, imm32: u32, size: Size) -> Self {
Self {
t,
n,
offset: Offset::Immediate(imm32),
indexing: OFFSET,
size,
signed: false,
unprivileged: false,
}
}
fn attributes(self) -> Attributes {
Attributes {
aligned: false,
unprivileged: self.unprivileged,
}
}
}
pub(super) fn load_literal16(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let address = literal(e, true, bits(hw, 7, 0) << 2);
load_literal(e, bits(hw, 10, 8), address, Size::Word, false);
}
pub(super) fn load_store_single16(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
let (t, n, imm5) = (bits(hw, 2, 0), bits(hw, 5, 3), bits(hw, 10, 6));
let load = bits(hw, 11, 11) == 1;
let (single, load) = match bits(hw, 15, 12) {
0b0101 => {
let (size, signed, load) = match bits(hw, 11, 9) {
0b000 => (Size::Word, false, false),
0b001 => (Size::Halfword, false, false),
0b010 => (Size::Byte, false, false),
0b011 => (Size::Byte, true, true),
0b100 => (Size::Word, false, true),
0b101 => (Size::Halfword, false, true),
0b110 => (Size::Byte, false, true),
_ => (Size::Halfword, true, true),
};
let single = Single {
offset: Offset::Register {
m: bits(hw, 8, 6),
shift: 0,
},
signed,
..Single::immediate(t, n, 0, size)
};
(single, load)
}
0b0110 => (Single::immediate(t, n, imm5 << 2, Size::Word), load),
0b0111 => (Single::immediate(t, n, imm5, Size::Byte), load),
0b1000 => (Single::immediate(t, n, imm5 << 1, Size::Halfword), load),
0b1001 => (
Single::immediate(bits(hw, 10, 8), 13, bits(hw, 7, 0) << 2, Size::Word),
load,
),
_ => return Err(Undefined),
};
if load {
load_one(e, single);
} else {
store_one(e, single);
}
Ok(())
}
pub(super) fn store_multiple16(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
store_multiple(
e,
bits(hw, 10, 8),
bits(hw, 7, 0),
true,
Step::IncrementAfter,
)
}
pub(super) fn load_multiple16(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
let (n, registers) = (bits(hw, 10, 8), bits(hw, 7, 0));
load_multiple(
e,
n,
registers,
registers & (1 << n) == 0,
Step::IncrementAfter,
)
}
pub(super) fn push16(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
store_multiple(
e,
13,
bits(hw, 8, 8) << 14 | bits(hw, 7, 0),
true,
Step::DecrementBefore,
)
}
pub(super) fn pop16(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
load_multiple(
e,
13,
bits(hw, 8, 8) << 15 | bits(hw, 7, 0),
true,
Step::IncrementAfter,
)
}
pub(super) fn load_store_multiple(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, list) = (u32::from(hw1), u32::from(hw2));
let n = bits(hw1, 3, 0);
let wback = bits(hw1, 5, 5) == 1;
let (stores, loads) = (list & 0x5FFF, list & 0xDFFF);
match (bits(hw1, 8, 7), bits(hw1, 4, 4)) {
(0b01, 0) => store_multiple(e, n, stores, wback, Step::IncrementAfter),
(0b01, 1) => load_multiple(e, n, loads, wback, Step::IncrementAfter),
(0b10, 0) => store_multiple(e, n, stores, wback, Step::DecrementBefore),
(0b10, 1) => load_multiple(e, n, loads, wback, Step::DecrementBefore),
_ => Err(Undefined),
}
}
pub(super) fn load_store_dual_exclusive_table_branch(
e: &mut Emitter<'_, '_>,
hw1: u16,
hw2: u16,
) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let (n, t) = (bits(hw1, 3, 0), bits(hw2, 15, 12));
let imm8 = bits(hw2, 7, 0);
let exclusive = |imm32, size| Exclusive { t, n, imm32, size };
match (bits(hw1, 8, 7), bits(hw1, 5, 4)) {
(0b00, 0b00) => store_exclusive(e, bits(hw2, 11, 8), exclusive(imm8 << 2, Size::Word)),
(0b00, 0b01) => load_exclusive(e, exclusive(imm8 << 2, Size::Word)),
(0b01, 0b00) => match bits(hw2, 7, 4) {
0b0100 => store_exclusive(e, bits(hw2, 3, 0), exclusive(0, Size::Byte)),
0b0101 => store_exclusive(e, bits(hw2, 3, 0), exclusive(0, Size::Halfword)),
_ => Err(Undefined),
},
(0b01, 0b01) => match bits(hw2, 7, 4) {
0b0000 => {
table_branch(e, n, bits(hw2, 3, 0), Size::Byte);
Ok(())
}
0b0001 => {
table_branch(e, n, bits(hw2, 3, 0), Size::Halfword);
Ok(())
}
0b0100 => load_exclusive(e, exclusive(0, Size::Byte)),
0b0101 => load_exclusive(e, exclusive(0, Size::Halfword)),
_ => Err(Undefined),
},
_ => dual(e, hw1, hw2),
}
}
pub(super) fn load_word(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
load32(e, u32::from(hw1), u32::from(hw2), Size::Word)
}
pub(super) fn load_halfword_hints(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
load32(e, u32::from(hw1), u32::from(hw2), Size::Halfword)
}
pub(super) fn load_byte_hints(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
load32(e, u32::from(hw1), u32::from(hw2), Size::Byte)
}
pub(super) fn store_single(e: &mut Emitter<'_, '_>, hw1: u16, hw2: u16) -> Translated {
let (hw1, hw2) = (u32::from(hw1), u32::from(hw2));
let size = match bits(hw1, 6, 5) {
0b00 => Size::Byte,
0b01 => Size::Halfword,
0b10 => Size::Word,
_ => return Err(Undefined),
};
if bits(hw1, 3, 0) == 15 {
return Err(Undefined);
}
store_one(e, form32(hw1, hw2, size, false)?);
Ok(())
}
pub(super) fn clear_exclusive(e: &mut Emitter<'_, '_>) {
let open = e.constant(0);
e.store_field(offset_of!(State, exclusive_valid), open);
}
fn load32(e: &mut Emitter<'_, '_>, hw1: u32, hw2: u32, size: Size) -> Translated {
let (signed, n, t) = (bits(hw1, 8, 8) == 1, bits(hw1, 3, 0), bits(hw2, 15, 12));
if signed && size == Size::Word {
return Err(Undefined);
}
let hint = t == 15 && size != Size::Word;
if n == 15 {
if hint {
return Ok(());
}
let address = literal(e, bits(hw1, 7, 7) == 1, bits(hw2, 11, 0));
load_literal(e, t, address, size, signed);
return Ok(());
}
let single = form32(hw1, hw2, size, signed)?;
if t == 15 {
if single.unprivileged || (hint && single.indexing.wback) {
return Err(Undefined);
}
if hint {
return Ok(());
}
}
load_one(e, single);
Ok(())
}
fn form32(hw1: u32, hw2: u32, size: Size, signed: bool) -> Result<Single, Undefined> {
let mut single = Single {
signed,
..Single::immediate(bits(hw2, 15, 12), bits(hw1, 3, 0), bits(hw2, 11, 0), size)
};
if bits(hw1, 7, 7) == 1 {
return Ok(single);
}
let op2 = bits(hw2, 11, 6);
if op2 == 0 {
single.offset = Offset::Register {
m: bits(hw2, 3, 0),
shift: bits(hw2, 5, 4),
};
return Ok(single);
}
if op2 >> 5 == 0 {
return Err(Undefined);
}
single.offset = Offset::Immediate(bits(hw2, 7, 0));
let (p, u, w) = (
bits(hw2, 10, 10) == 1,
bits(hw2, 9, 9) == 1,
bits(hw2, 8, 8) == 1,
);
match (p, u, w) {
(true, true, false) => single.unprivileged = true,
(false, _, false) => return Err(Undefined),
_ => {
single.indexing = Indexing {
index: p,
add: u,
wback: w,
};
}
}
Ok(single)
}
fn set(e: &mut Emitter<'_, '_>, n: u32, value: Value) {
let value = if n == 13 {
e.b.ins().band_imm_s(value, !3)
} else {
value
};
e.set_reg(n, value);
}
fn addresses(e: &mut Emitter<'_, '_>, single: Single) -> (Value, Value) {
let base = e.reg(single.n);
let offset = match single.offset {
Offset::Immediate(imm32) => e.constant(imm32),
Offset::Register { m, shift } => {
let rm = e.reg(m);
let carry = e.flag(Flag::C);
alu::shift_c(e, rm, ShiftType::Lsl, shift, carry).0
}
};
let offset_addr = if single.indexing.add {
e.b.ins().iadd(base, offset)
} else {
e.b.ins().isub(base, offset)
};
let address = if single.indexing.index {
offset_addr
} else {
base
};
(address, offset_addr)
}
fn load_one(e: &mut Emitter<'_, '_>, single: Single) {
let (address, offset_addr) = addresses(e, single);
let data = e.load(address, single.size, single.attributes());
let data = extend(e, data, single.size, single.signed);
let rt_first = single.size == Size::Byte;
if rt_first {
set(e, single.t, data);
}
if single.indexing.wback {
set(e, single.n, offset_addr);
}
if rt_first {
return;
}
if single.t == 15 {
e.branch_exchange(data);
} else {
set(e, single.t, data);
}
}
fn store_one(e: &mut Emitter<'_, '_>, single: Single) {
let (address, offset_addr) = addresses(e, single);
let value = e.reg(single.t);
e.store(address, value, single.size, single.attributes());
if single.indexing.wback {
set(e, single.n, offset_addr);
}
}
fn literal(e: &Emitter<'_, '_>, add: bool, imm32: u32) -> u32 {
let base = e.pc().wrapping_add(4) & !3;
if add {
base.wrapping_add(imm32)
} else {
base.wrapping_sub(imm32)
}
}
fn load_literal(e: &mut Emitter<'_, '_>, t: u32, address: u32, size: Size, signed: bool) {
let address = e.constant(address);
let data = e.load(address, size, Attributes::default());
let data = extend(e, data, size, signed);
if t == 15 {
e.branch_exchange(data);
} else {
set(e, t, data);
}
}
fn extend(e: &mut Emitter<'_, '_>, data: Value, size: Size, signed: bool) -> Value {
let narrow = match (signed, size) {
(true, Size::Byte) => types::I8,
(true, Size::Halfword) => types::I16,
_ => return data,
};
let narrow = e.b.ins().ireduce(narrow, data);
e.b.ins().sextend(types::I32, narrow)
}
fn span(e: &mut Emitter<'_, '_>, base: Value, bytes: u32, step: Step) -> (Value, Value) {
let bytes = i64::from(bytes);
match step {
Step::IncrementAfter => (base, e.b.ins().iadd_imm_s(base, bytes)),
Step::DecrementBefore => {
let below = e.b.ins().iadd_imm_s(base, -bytes);
(below, below)
}
}
}
fn load_multiple(
e: &mut Emitter<'_, '_>,
n: u32,
registers: u32,
wback: bool,
step: Step,
) -> Translated {
if wback && n == 15 {
return Err(Undefined);
}
let base = e.reg(n);
let (mut address, written) = span(e, base, 4 * registers.count_ones(), step);
let mut loaded = Vec::new();
for i in (0..16).filter(|i| registers & (1 << i) != 0) {
loaded.push((i, e.load(address, Size::Word, Attributes::ALIGNED)));
address = e.b.ins().iadd_imm_s(address, 4);
}
for &(i, value) in loaded.iter().filter(|&&(i, _)| i < 15) {
set(e, i, value);
}
if wback && registers & (1 << n) == 0 {
set(e, n, written);
}
if let Some(&(15, target)) = loaded.last() {
e.branch_exchange(target);
}
Ok(())
}
fn store_multiple(
e: &mut Emitter<'_, '_>,
n: u32,
registers: u32,
wback: bool,
step: Step,
) -> Translated {
if wback && n == 15 {
return Err(Undefined);
}
let base = e.reg(n);
let (mut address, written) = span(e, base, 4 * registers.count_ones(), step);
for i in (0..15).filter(|i| registers & (1 << i) != 0) {
let value = e.reg(i);
e.store(address, value, Size::Word, Attributes::ALIGNED);
address = e.b.ins().iadd_imm_s(address, 4);
}
if wback {
set(e, n, written);
}
Ok(())
}
fn dual(e: &mut Emitter<'_, '_>, hw1: u32, hw2: u32) -> Translated {
let (n, t, t2) = (bits(hw1, 3, 0), bits(hw2, 15, 12), bits(hw2, 11, 8));
let imm32 = bits(hw2, 7, 0) << 2;
let load = bits(hw1, 4, 4) == 1;
let indexing = Indexing {
index: bits(hw1, 8, 8) == 1,
add: bits(hw1, 7, 7) == 1,
wback: bits(hw1, 5, 5) == 1,
};
if (load && (t == 15 || t2 == 15)) || (!load && indexing.wback && n == 15) {
return Err(Undefined);
}
let (address, offset_addr) = if load && n == 15 {
let address = literal(e, indexing.add, imm32);
(e.constant(address), None)
} else {
let single = Single {
indexing,
..Single::immediate(t, n, imm32, Size::Word)
};
let (address, offset_addr) = addresses(e, single);
(address, indexing.wback.then_some(offset_addr))
};
let second = e.b.ins().iadd_imm_s(address, 4);
if load {
let first_word = e.load(address, Size::Word, Attributes::ALIGNED);
let second_word = e.load(second, Size::Word, Attributes::ALIGNED);
set(e, t, first_word);
set(e, t2, second_word);
} else {
let (first_word, second_word) = (e.reg(t), e.reg(t2));
e.store(address, first_word, Size::Word, Attributes::ALIGNED);
e.store(second, second_word, Size::Word, Attributes::ALIGNED);
}
if let Some(offset_addr) = offset_addr {
set(e, n, offset_addr);
}
Ok(())
}
fn load_exclusive(e: &mut Emitter<'_, '_>, exclusive: Exclusive) -> Translated {
let Exclusive { t, n, imm32, size } = exclusive;
if t == 15 {
return Err(Undefined);
}
let base = e.reg(n);
let address = e.b.ins().iadd_imm_s(base, i64::from(imm32));
let data = e.load(address, size, Attributes::ALIGNED);
let exclusive = e.constant(1);
e.store_field(offset_of!(State, exclusive_valid), exclusive);
set(e, t, data);
Ok(())
}
fn store_exclusive(e: &mut Emitter<'_, '_>, d: u32, exclusive: Exclusive) -> Translated {
let Exclusive { t, n, imm32, size } = exclusive;
if d == 15 {
return Err(Undefined);
}
let base = e.reg(n);
let address = e.b.ins().iadd_imm_s(base, i64::from(imm32));
let value = e.reg(t);
if size != Size::Byte {
let misaligned = e.b.ins().band_imm_s(address, i64::from(size.bytes() - 1));
let misaligned = e.b.ins().icmp_imm_s(IntCC::NotEqual, misaligned, 0);
e.raise_if(misaligned, Fault::Unaligned);
}
let monitor = e.load_field(offset_of!(State, exclusive_valid));
let pass = e.b.create_block();
let join = e.b.create_block();
e.b.append_block_param(join, types::I32);
let failed = e.constant(1);
e.b.ins().brif(monitor, pass, &[], join, &[failed.into()]);
e.b.seal_block(pass);
e.b.switch_to_block(pass);
e.store(address, value, size, Attributes::ALIGNED);
clear_exclusive(e);
let stored = e.constant(0);
e.b.ins().jump(join, &[stored.into()]);
e.b.seal_block(join);
e.b.switch_to_block(join);
let status = e.b.block_params(join)[0];
set(e, d, status);
Ok(())
}
fn table_branch(e: &mut Emitter<'_, '_>, n: u32, m: u32, size: Size) {
let base = e.reg(n);
let index = e.reg(m);
let index = if size == Size::Halfword {
e.b.ins().ishl_imm_s(index, 1)
} else {
index
};
let address = e.b.ins().iadd(base, index);
let halfwords = e.load(address, size, Attributes::default());
let forward = e.b.ins().ishl_imm_s(halfwords, 1);
let pc = e.reg(15);
let target = e.b.ins().iadd(pc, forward);
e.branch(target);
}
#[cfg(test)]
mod tests {
use cranelift_codegen::ir::{self, InstructionData, Opcode, ValueDef};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use super::super::super::family::helpers;
use super::super::testing::{Bench, CODE, DEVICE, RAM};
use super::super::{decode16, decode32};
use super::*;
use crate::emulator::AccessKind;
use crate::emulator::Exit;
use crate::soc::Width;
const BASE: u32 = RAM + 0x2000;
const AT: u32 = CODE + 0x20;
const BYTES: [u8; 4] = [0x81, 0x92, 0xA3, 0xB4];
const WORD: u32 = 0xB4A3_9281;
const HALFWORD: u32 = 0x9281;
const SIGNED_HALFWORD: u32 = 0xFFFF_9281;
const BYTE: u32 = 0x81;
const SIGNED_BYTE: u32 = 0xFFFF_FF81;
const STORED: [u8; 4] = [0x44, 0x33, 0x22, 0x11];
const UNTOUCHED: u8 = 0xA5;
fn ready(code: &[u16], registers: &[(usize, u32)]) -> Bench {
let mut bench = Bench::new(code);
bench.write(BASE - 0x1000, &[UNTOUCHED; 0x3000]);
for &(n, value) in registers {
bench.core.state.r[n] = value;
}
bench
}
fn ready_at(code: &[u16], registers: &[(usize, u32)]) -> Bench {
let mut bench = ready(&[], registers);
bench.place(AT, code);
bench.core.state.r[15] = AT;
bench
}
fn run(bench: &mut Bench, instructions: u32) {
assert_eq!(bench.run(instructions).1, u64::from(instructions));
}
fn put(bench: &mut Bench, address: u32, words: &[u32]) {
let bytes: Vec<u8> = words.iter().flat_map(|word| word.to_le_bytes()).collect();
bench.write(address, &bytes);
}
fn words<const N: usize>(bench: &Bench, address: u32) -> [u32; N] {
std::array::from_fn(|k| bench.word(address + 4 * u32::try_from(k).unwrap()))
}
fn bytes<const N: usize>(bench: &Bench, address: u32) -> [u8; N] {
std::array::from_fn(|k| {
let at = address + u32::try_from(k).unwrap();
bench.word(at & !3).to_le_bytes()[usize::try_from(at & 3).unwrap()]
})
}
fn single16(hw: u16, at: u32) -> (u32, u32) {
let mut bench = ready(&[hw], &[(1, 0x1122_3344), (2, BASE), (3, 8), (13, BASE)]);
put(&mut bench, at, &[0x8081_8283]);
run(&mut bench, 1);
let state = &bench.core.state;
assert_eq!((state.r[2], state.r[3], state.r[13]), (BASE, 8, BASE));
(state.r[1], bench.word(at))
}
fn load(code: [u16; 2], at: u32) -> (u32, u32) {
let mut bench = ready_at(&code, &[(2, BASE), (3, 2)]);
bench.write(at, &BYTES);
run(&mut bench, 1);
(bench.core.state.r[1], bench.core.state.r[2])
}
fn store(code: [u16; 2], at: u32) -> ([u8; 5], u32) {
let mut bench = ready(&code, &[(1, 0x1122_3344), (2, BASE), (3, 2)]);
run(&mut bench, 1);
(bytes(&bench, at), bench.core.state.r[2])
}
fn stored(size: usize) -> [u8; 5] {
std::array::from_fn(|k| if k < size { STORED[k] } else { UNTOUCHED })
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Call {
Read(u32, Attributes),
Write(u32, Attributes),
Raise(u32),
}
const PLAIN: Attributes = Attributes {
aligned: false,
unprivileged: false,
};
const UNPRIVILEGED: Attributes = Attributes {
aligned: false,
unprivileged: true,
};
const ALIGNED: Attributes = Attributes::ALIGNED;
fn translated(code: &[u16]) -> Result<Vec<Call>, Undefined> {
let mut function = ir::Function::new();
function
.signature
.params
.push(ir::AbiParam::new(types::I64));
function
.signature
.returns
.push(ir::AbiParam::new(types::I32));
let mut context = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut function, &mut context);
let entry = builder.create_block();
builder.append_block_params_for_function_params(entry);
builder.switch_to_block(entry);
let mut e = Emitter::new(&mut builder, types::I64);
e.begin(0, CODE, 2 * u32::try_from(code.len()).unwrap(), 0);
match *code {
[hw] => decode16(&mut e, hw),
[hw1, hw2] => decode32(&mut e, hw1, hw2),
_ => panic!("an instruction is one or two halfwords"),
}?;
Ok(calls(builder.func))
}
fn calls(function: &ir::Function) -> Vec<Call> {
let dfg = &function.dfg;
let constant = |value: Value| {
let ValueDef::Result(inst, _) = dfg.value_def(dfg.resolve_aliases(value)) else {
panic!("{value} isn't an instruction's");
};
let InstructionData::UnaryImm { imm, .. } = dfg.insts[inst] else {
panic!("{value} isn't a constant");
};
imm.bits().cast_unsigned()
};
let word = |value| u32::try_from(constant(value) & u64::from(u32::MAX)).unwrap();
let address = |helper: usize| u64::try_from(helper).unwrap();
let read = address(helpers::read as *const () as usize);
let write = address(helpers::write as *const () as usize);
let raise = address(helpers::raise as *const () as usize);
let mut calls = Vec::new();
for block in function.layout.blocks() {
for inst in function.layout.block_insts(block) {
if dfg.insts[inst].opcode() != Opcode::CallIndirect {
continue;
}
let args = dfg.inst_args(inst);
let callee = constant(args[0]);
calls.push(if callee == read {
Call::Read(word(args[3]), Attributes::from_bits(word(args[4])))
} else if callee == write {
Call::Write(word(args[4]), Attributes::from_bits(word(args[5])))
} else if callee == raise {
Call::Raise(word(args[2]))
} else {
panic!("a call to no helper the core has");
});
}
}
calls
}
#[test]
fn ldr_literal_reads_from_the_word_aligned_pc() {
let mut bench = ready(&[0x4B02, 0x4C02], &[]);
put(&mut bench, CODE + 12, &[0xCAFE_F00D]);
run(&mut bench, 2);
assert_eq!(bench.core.state.r[3..5], [0xCAFE_F00D, 0xCAFE_F00D]);
}
#[test]
fn the_16_bit_register_forms_access_rn_plus_rm() {
let (at, same) = (BASE + 8, 0x8081_8283);
assert_eq!(single16(0x50D1, at), (0x1122_3344, 0x1122_3344), "str");
assert_eq!(single16(0x52D1, at), (0x1122_3344, 0x8081_3344), "strh");
assert_eq!(single16(0x54D1, at), (0x1122_3344, 0x8081_8244), "strb");
assert_eq!(single16(0x56D1, at), (0xFFFF_FF83, same), "ldrsb");
assert_eq!(single16(0x58D1, at), (same, same), "ldr");
assert_eq!(single16(0x5AD1, at), (0x8283, same), "ldrh");
assert_eq!(single16(0x5CD1, at), (0x83, same), "ldrb");
assert_eq!(single16(0x5ED1, at), (0xFFFF_8283, same), "ldrsh");
}
#[test]
fn the_16_bit_immediate_forms_scale_their_offset_by_the_size() {
let (r1, same) = (0x1122_3344, 0x8081_8283);
assert_eq!(single16(0x67D1, BASE + 124), (r1, r1), "str r1, [r2, #124]");
assert_eq!(
single16(0x6FD1, BASE + 124),
(same, same),
"ldr r1, [r2, #124]"
);
assert_eq!(
single16(0x77D1, BASE + 28),
(r1, 0x4481_8283),
"strb r1, [r2, #31]"
);
assert_eq!(
single16(0x7FD1, BASE + 28),
(0x80, same),
"ldrb r1, [r2, #31]"
);
assert_eq!(
single16(0x87D1, BASE + 60),
(r1, 0x3344_8283),
"strh r1, [r2, #62]"
);
assert_eq!(
single16(0x8FD1, BASE + 60),
(0x8081, same),
"ldrh r1, [r2, #62]"
);
assert_eq!(
single16(0x91FF, BASE + 1020),
(r1, r1),
"str r1, [sp, #1020]"
);
assert_eq!(
single16(0x99FF, BASE + 1020),
(same, same),
"ldr r1, [sp, #1020]"
);
}
#[test]
fn stm_and_ldm_16_walk_up_from_the_base() {
let mut bench = ready(&[0xC086, 0xC886], &[(0, BASE), (1, 1), (2, 2), (7, 7)]);
run(&mut bench, 1);
assert_eq!(words::<4>(&bench, BASE), [1, 2, 7, 0xA5A5_A5A5]);
assert_eq!(bench.core.state.r[0], BASE + 12);
bench.core.state.r[0] = BASE;
bench.core.state.r[1..8].fill(0);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[0..3], [BASE + 12, 1, 2]);
assert_eq!(bench.core.state.r[7], 7);
let mut bench = ready(&[0xC803], &[(0, BASE)]);
put(&mut bench, BASE, &[0xA, 0xB]);
run(&mut bench, 1);
assert_eq!(
bench.core.state.r[0..2],
[0xA, 0xB],
"the base keeps what it loaded"
);
}
#[test]
fn stm_16_with_its_base_in_the_list_stores_the_base_as_it_was() {
let mut bench = ready(&[0xC106], &[(1, BASE), (2, 0x22)]);
run(&mut bench, 1);
assert_eq!(words::<2>(&bench, BASE), [BASE, 0x22]);
assert_eq!(bench.core.state.r[1], BASE + 8);
let mut bench = ready(&[0xC206], &[(1, 0x11), (2, BASE)]);
run(&mut bench, 1);
assert_eq!(words::<2>(&bench, BASE), [0x11, BASE]);
assert_eq!(bench.core.state.r[2], BASE + 8);
}
#[test]
fn push_and_pop_16_move_the_stack_and_pop_branches() {
let mut bench = ready(&[0xB506, 0xBD06], &[(1, 1), (2, 2), (13, BASE), (14, 0xE)]);
run(&mut bench, 1);
assert_eq!(words::<3>(&bench, BASE - 12), [1, 2, 0xE]);
assert_eq!(bench.core.state.r[13], BASE - 12);
put(&mut bench, BASE - 12, &[0x11, 0x22, 0x101]);
run(&mut bench, 1);
let state = &bench.core.state;
assert_eq!(state.r[1..3], [0x11, 0x22]);
assert_eq!((state.r[13], state.r[15], state.t), (BASE, 0x100, 1));
let mut bench = ready(&[0xB408, 0xBC10], &[(3, 3), (13, BASE)]);
run(&mut bench, 2);
assert_eq!(bench.word(BASE - 4), 3);
assert_eq!((bench.core.state.r[4], bench.core.state.r[13]), (3, BASE));
}
#[test]
fn a_pc_popped_without_bit_0_clears_the_thumb_bit() {
let mut bench = ready(&[0xBD06], &[(13, BASE)]);
put(&mut bench, BASE, &[0, 0, 0x200]);
run(&mut bench, 1);
assert_eq!((bench.core.state.r[15], bench.core.state.t), (0x200, 0));
}
#[test]
fn the_32_bit_load_multiples_walk_from_the_base() {
let cases: [([u16; 2], usize, u32, u32, usize); 5] = [
([0xE8B0, 0x8006], 0, BASE, BASE + 12, 15), ([0xE890, 0x0006], 0, BASE, BASE, 3), ([0xE930, 0x4006], 0, BASE + 12, BASE, 14), ([0xE910, 0x0006], 0, BASE + 8, BASE + 8, 3), ([0xE8BD, 0x8006], 13, BASE, BASE + 12, 15), ];
for (code, n, before, after, third) in cases {
let mut bench = ready(&code, &[(n, before)]);
put(&mut bench, BASE, &[0x11, 0x22, 0x301]);
run(&mut bench, 1);
let state = &bench.core.state;
assert_eq!(state.r[n], after, "{code:04X?}");
assert_eq!(state.r[1..3], [0x11, 0x22], "{code:04X?}");
let third_loaded = match third {
15 => 0x300,
14 => 0x301,
_ => 0,
};
assert_eq!(state.r[third], third_loaded, "{code:04X?}");
}
}
#[test]
fn the_32_bit_store_multiples_walk_from_the_base() {
let cases: [([u16; 2], usize, u32, u32, u32); 5] = [
([0xE8A0, 0x4006], 0, BASE, BASE + 12, BASE), ([0xE880, 0x4006], 0, BASE, BASE, BASE), ([0xE920, 0x4006], 0, BASE + 12, BASE, BASE), ([0xE900, 0x4006], 0, BASE + 12, BASE + 12, BASE), ([0xE92D, 0x4006], 13, BASE + 12, BASE, BASE), ];
for (code, n, before, after, first) in cases {
let mut bench = ready(&code, &[(1, 1), (2, 2), (14, 0xE), (n, before)]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[n], after, "{code:04X?}");
assert_eq!(
words::<4>(&bench, first),
[1, 2, 0xE, 0xA5A5_A5A5],
"{code:04X?}"
);
}
}
#[test]
fn the_32_bit_lists_ignore_the_bits_that_cant_name_a_register() {
let mut bench = ready(&[0xE880, 0xA006], &[(0, BASE), (1, 1), (2, 2)]);
run(&mut bench, 1);
assert_eq!(words::<3>(&bench, BASE), [1, 2, 0xA5A5_A5A5]);
let mut bench = ready(&[0xE890, 0x2006], &[(0, BASE), (13, BASE + 0x100)]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[13], BASE + 0x100);
}
#[test]
fn pop_and_push_of_one_register_are_ldr_and_str() {
let mut bench = ready(&[0xF84D, 0x4D04, 0xF85D, 0x5B04, 0xF85D, 0xFB04], &[(4, 4)]);
bench.core.state.r[13] = BASE;
run(&mut bench, 2);
assert_eq!(bench.word(BASE - 4), 4);
assert_eq!((bench.core.state.r[5], bench.core.state.r[13]), (4, BASE));
bench.core.state.r[13] = BASE - 4;
put(&mut bench, BASE - 4, &[0x41]);
run(&mut bench, 1);
assert_eq!(
(bench.core.state.r[15], bench.core.state.r[13]),
(0x40, BASE)
);
}
#[test]
fn ldr_32_forms_every_address_the_manual_does() {
let cases = [
([0xF8D2, 0x1FFF], BASE + 4095, BASE), ([0xF852, 0x1CFF], BASE - 255, BASE), ([0xF852, 0x1F04], BASE + 4, BASE + 4), ([0xF852, 0x1D04], BASE - 4, BASE - 4), ([0xF852, 0x1B04], BASE, BASE + 4), ([0xF852, 0x1904], BASE, BASE - 4), ([0xF852, 0x1033], BASE + 16, BASE), ([0xF8DF, 0x1008], AT + 4 + 8, BASE), ([0xF85F, 0x1008], AT + 4 - 8, BASE), ([0xF852, 0x1EFF], BASE + 255, BASE), ];
for (code, at, r2) in cases {
assert_eq!(load(code, at), (WORD, r2), "{code:04X?}");
}
}
#[test]
fn ldrh_and_ldrsh_form_every_address_the_manual_does() {
let cases = [
([0xF8B2, 0x1FFF], BASE + 4095, BASE), ([0xF832, 0x1CFF], BASE - 255, BASE), ([0xF832, 0x1F02], BASE + 2, BASE + 2), ([0xF832, 0x1902], BASE, BASE - 2), ([0xF832, 0x1013], BASE + 4, BASE), ([0xF8BF, 0x1008], AT + 4 + 8, BASE), ([0xF83F, 0x1008], AT + 4 - 8, BASE), ([0xF832, 0x1EFF], BASE + 255, BASE), ];
for (code, at, r2) in cases {
assert_eq!(load(code, at), (HALFWORD, r2), "{code:04X?}");
let signed = [code[0] | 0x100, code[1]];
assert_eq!(load(signed, at), (SIGNED_HALFWORD, r2), "{signed:04X?}");
}
}
#[test]
fn ldrb_and_ldrsb_form_every_address_the_manual_does() {
let cases = [
([0xF892, 0x1FFF], BASE + 4095, BASE), ([0xF812, 0x1CFF], BASE - 255, BASE), ([0xF812, 0x1F01], BASE + 1, BASE + 1), ([0xF812, 0x1901], BASE, BASE - 1), ([0xF812, 0x1023], BASE + 8, BASE), ([0xF89F, 0x1008], AT + 4 + 8, BASE), ([0xF81F, 0x1008], AT + 4 - 8, BASE), ([0xF812, 0x1EFF], BASE + 255, BASE), ];
for (code, at, r2) in cases {
assert_eq!(load(code, at), (BYTE, r2), "{code:04X?}");
let signed = [code[0] | 0x100, code[1]];
assert_eq!(load(signed, at), (SIGNED_BYTE, r2), "{signed:04X?}");
}
}
#[test]
fn ldr_to_the_pc_branches() {
for (code, r2) in [([0xF8D2, 0xF004], BASE), ([0xF852, 0xFB04], BASE + 4)] {
let mut bench = ready(&code, &[(2, BASE)]);
put(&mut bench, BASE, &[0x81, 0x81]);
run(&mut bench, 1);
let state = &bench.core.state;
assert_eq!(
(state.r[15], state.t, state.r[2]),
(0x80, 1, r2),
"{code:04X?}"
);
}
let mut bench = ready_at(&[0xF8DF, 0xF008], &[]);
put(&mut bench, AT + 12, &[0x81]);
run(&mut bench, 1);
assert_eq!((bench.core.state.r[15], bench.core.state.t), (0x80, 1));
}
#[test]
fn the_memory_hints_change_nothing() {
let hints = [
[0xF892, 0xFFFF], [0xF812, 0xFCFF], [0xF812, 0xF023], [0xF89F, 0xF008], [0xF81F, 0xF008], [0xF992, 0xFFFF], [0xF912, 0xFCFF], [0xF912, 0xF023], [0xF99F, 0xF008], [0xF91F, 0xF008], [0xF8B2, 0xFFFF], [0xF832, 0xFCFF], [0xF832, 0xF013], [0xF8BF, 0xF008], [0xF9B2, 0xFFFF], [0xF932, 0xFCFF], [0xF932, 0xF013], [0xF93F, 0xF008], ];
for code in hints {
let mut bench = ready_at(&code, &[(2, DEVICE), (3, 2)]);
let before = bench.core.state.clone();
run(&mut bench, 1);
assert!(bench.bus.accesses.is_empty(), "{code:04X?}");
let after = &bench.core.state;
assert_eq!(after.r[..15], before.r[..15], "{code:04X?}");
assert_eq!(after.r[15], AT + 4, "{code:04X?}");
}
}
#[test]
fn the_32_bit_stores_form_every_address_the_manual_does() {
let cases = [
([0xF8C2, 0x1FFF], BASE + 4095, BASE, 4), ([0xF842, 0x1CFF], BASE - 255, BASE, 4), ([0xF842, 0x1F04], BASE + 4, BASE + 4, 4), ([0xF842, 0x1904], BASE, BASE - 4, 4), ([0xF842, 0x1033], BASE + 16, BASE, 4), ([0xF842, 0x1EFF], BASE + 255, BASE, 4), ([0xF8A2, 0x1FFF], BASE + 4095, BASE, 2), ([0xF822, 0x1CFF], BASE - 255, BASE, 2), ([0xF822, 0x1F02], BASE + 2, BASE + 2, 2), ([0xF822, 0x1902], BASE, BASE - 2, 2), ([0xF822, 0x1013], BASE + 4, BASE, 2), ([0xF822, 0x1EFF], BASE + 255, BASE, 2), ([0xF882, 0x1FFF], BASE + 4095, BASE, 1), ([0xF802, 0x1CFF], BASE - 255, BASE, 1), ([0xF802, 0x1F01], BASE + 1, BASE + 1, 1), ([0xF802, 0x1901], BASE, BASE - 1, 1), ([0xF802, 0x1023], BASE + 8, BASE, 1), ([0xF802, 0x1EFF], BASE + 255, BASE, 1), ];
for (code, at, r2, size) in cases {
assert_eq!(store(code, at), (stored(size), r2), "{code:04X?}");
}
}
#[test]
fn ldrd_and_strd_move_two_words_in_every_form() {
let base = BASE;
let cases = [
([0xE9D3, 0x12FF], base + 1020, base), ([0xE953, 0x1202], base - 8, base), ([0xE9F3, 0x1202], base + 8, base + 8), ([0xE873, 0x1202], base, base - 8), ];
for (code, at, r3) in cases {
let mut bench = ready(&code, &[(3, base)]);
put(&mut bench, at, &[0x11, 0x22]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[1..4], [0x11, 0x22, r3], "{code:04X?}");
let strd = [code[0] & !0x10, code[1]];
let mut bench = ready(&strd, &[(1, 1), (2, 2), (3, base)]);
run(&mut bench, 1);
assert_eq!(words::<2>(&bench, at), [1, 2], "{code:04X?}");
assert_eq!(bench.core.state.r[3], r3, "{code:04X?}");
}
}
#[test]
fn ldrd_literal_reads_from_the_pc() {
for (code, at) in [([0xE9DF, 0x1202], AT + 12), ([0xE95F, 0x1204], AT - 12)] {
let mut bench = ready_at(&code, &[]);
put(&mut bench, at, &[0x11, 0x22]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[1..3], [0x11, 0x22], "{code:04X?}");
}
}
#[test]
fn a_store_exclusive_stores_only_after_a_load_exclusive() {
let pairs: [([u16; 2], [u16; 2], usize); 3] = [
([0xE852, 0x1FFF], [0xE842, 0x13FF], 4),
([0xE8D2, 0x1F4F], [0xE8C2, 0x1F43], 1), ([0xE8D2, 0x1F5F], [0xE8C2, 0x1F53], 2), ];
for (ldrex, strex, size) in pairs {
let at = if size == 4 { BASE + 1020 } else { BASE };
let mut bench = ready(&[ldrex[0], ldrex[1], strex[0], strex[1]], &[(2, BASE)]);
bench.write(at, &STORED);
run(&mut bench, 1);
let mask = u32::MAX >> (32 - 8 * size);
assert_eq!(bench.core.state.r[1], 0x1122_3344 & mask, "{ldrex:04X?}");
assert_eq!(bench.core.state.exclusive_valid, 1);
bench.core.state.r[1] = 0x5566_7788;
run(&mut bench, 1);
assert_eq!(bench.core.state.r[3], 0, "{strex:04X?} stores");
assert_eq!(bench.word(at), 0x1122_3344 & !mask | 0x5566_7788 & mask);
assert_eq!(bench.core.state.exclusive_valid, 0);
bench.core.state.r[15] = 4;
bench.core.state.r[1] = 0x99;
run(&mut bench, 1);
assert_eq!(bench.core.state.r[3], 1, "{strex:04X?} doesn't store");
assert_eq!(bench.word(at), 0x1122_3344 & !mask | 0x5566_7788 & mask);
}
}
#[test]
fn a_store_exclusive_anywhere_passes_after_a_load_exclusive() {
let mut bench = ready(&[0xE852, 0x1FFF, 0xE842, 0x4300], &[(2, BASE), (4, 0x1234)]);
run(&mut bench, 2);
assert_eq!((bench.core.state.r[3], bench.word(BASE)), (0, 0x1234));
}
#[test]
fn tbb_and_tbh_branch_forward_by_twice_the_entry() {
for (code, target) in [
([0xE8D1, 0xF002], 4 + 2 * 0x03),
([0xE8D1, 0xF012], 4 + 2 * 0x0403),
] {
let mut bench = ready(&code, &[(1, BASE), (2, 2)]);
bench.write(BASE, &[0, 0, 3, 0, 3, 4]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[15], target, "{code:04X?}");
}
for (code, target) in [
([0xE8DF, 0xF002], 4 + 2 * 0x07),
([0xE8DF, 0xF012], 4 + 2 * 0x0807),
] {
let index = if code[1] & 0x10 == 0 { 2 } else { 1 };
let mut bench = ready(&[code[0], code[1], 0x0605, 0x0807, 0x0A09], &[(2, index)]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[15], target, "{code:04X?}");
}
}
#[test]
fn a_narrow_load_from_the_bus_extends_what_it_reads() {
let mut bench = ready(&[0x5ED1], &[(2, DEVICE), (3, 2)]);
bench.bus.answer = 0x8001;
assert_eq!(bench.run(1), (Exit::Returned, 1));
assert_eq!(bench.core.state.r[1], 0xFFFF_8001);
let access = bench.bus.accesses[0];
assert_eq!(
(access.kind, access.address, access.width),
(AccessKind::Read, u64::from(DEVICE) + 2, Width::W16)
);
let mut bench = ready(&[0x54D1], &[(1, 0x1122_3344), (2, DEVICE), (3, 2)]);
assert_eq!(bench.run(1), (Exit::Returned, 1));
let access = bench.bus.accesses[0];
assert_eq!(
(access.kind, access.address, access.width, access.value),
(AccessKind::Write, u64::from(DEVICE) + 2, Width::W8, 0x44)
);
}
#[test]
fn an_unaligned_access_reaches_the_bus_a_byte_at_a_time() {
let pieces = |code: u16, offset: u32| {
let mut bench = ready(&[code], &[(1, 0x1122_3344), (2, DEVICE), (3, offset)]);
assert_eq!(bench.run(1), (Exit::Returned, 1));
bench
.bus
.accesses
.iter()
.map(|access| {
(
access.address - u64::from(DEVICE),
access.width,
access.value,
)
})
.collect::<Vec<_>>()
};
assert_eq!(
pieces(0x58D1, 2),
[2, 3, 4, 5].map(|byte| (byte, Width::W8, 0)),
);
assert_eq!(
pieces(0x50D1, 3),
[(3, 0x44), (4, 0x33), (5, 0x22), (6, 0x11)].map(|(byte, value)| (
byte,
Width::W8,
value
)),
);
}
#[test]
fn a_failed_access_writes_no_register() {
let cases: [(&[u16], usize); 6] = [
(&[0xC886], 0), (&[0xBD06], 13), (&[0xF850, 0x1F04], 0), (&[0xE870, 0x1202], 0), (&[0xC006], 0), (&[0xE8E0, 0x1202], 0), ];
for (code, n) in cases {
let mut bench = ready(code, &[(n, DEVICE)]);
bench.bus.fails_from = Some(u64::from(DEVICE) + 4);
let before = bench.core.state.clone();
assert_eq!(bench.run(1), (Exit::Returned, 0), "{code:04X?}");
assert_ne!(bench.core.state.ipsr, 0, "{code:04X?} took its fault");
assert_eq!(bench.core.state.r[..13], before.r[..13], "{code:04X?}");
assert_eq!(bench.core.state.r[13], before.r[13] - 0x20, "{code:04X?}");
}
}
#[test]
fn a_load_that_faults_in_an_it_block_keeps_its_itstate() {
let mut bench = ready(&[0xBF08, 0xF850, 0x1F04], &[(0, DEVICE)]);
bench.core.state.z = 1;
bench.bus.fails_from = Some(u64::from(DEVICE) + 4);
assert_eq!(bench.run(2), (Exit::Returned, 1));
let mut stacked = State::default();
stacked.set_xpsr(bench.word(bench.core.state.r[13] + 28));
assert_eq!(stacked.it, 0x08, "ITSTATE 0x08, the LDR's own");
}
#[test]
fn a_load_to_sp_keeps_it_word_aligned() {
let mut bench = ready(&[0xF8D2, 0xD000], &[(2, BASE)]);
put(&mut bench, BASE, &[0x2000_1003]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[13], 0x2000_1000);
}
#[test]
fn a_load_that_writes_back_to_its_own_register_does_what_its_pseudocode_does() {
for (code, r2) in [
([0xF852, 0x2F04], 0x8081_8283),
([0xF812, 0x2F01], BASE + 1),
] {
let mut bench = ready(&code, &[(2, BASE)]);
put(&mut bench, BASE, &[0x8081_8283, 0x8081_8283]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[2], r2, "{code:04X?}");
}
let mut bench = ready(&[0xE8B0, 0x0003], &[(0, BASE)]);
put(&mut bench, BASE, &[0xA, 0xB]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[0..2], [0xA, 0xB]);
let mut bench = ready(&[0xE9D3, 0x1100], &[(3, BASE)]);
put(&mut bench, BASE, &[0xA, 0xB]);
run(&mut bench, 1);
assert_eq!(bench.core.state.r[1], 0xB);
}
#[test]
fn an_empty_list_moves_nothing() {
let mut bench = ready(&[0xC800, 0xC000, 0xBC00, 0xB400], &[(0, BASE), (13, BASE)]);
let before = bench.core.state.clone();
run(&mut bench, 4);
assert_eq!(bench.core.state.r[..15], before.r[..15]);
assert_eq!(words::<2>(&bench, BASE - 4), [0xA5A5_A5A5; 2]);
}
#[test]
fn a_store_of_its_own_base_or_the_pc_does_what_its_pseudocode_does() {
let mut bench = ready(&[0xF842, 0x2F04, 0xF8C2, 0xF004], &[(2, BASE)]);
run(&mut bench, 1);
assert_eq!(
(bench.word(BASE + 4), bench.core.state.r[2]),
(BASE, BASE + 4)
);
run(&mut bench, 1);
assert_eq!(bench.word(BASE + 8), 4 + 4);
}
#[test]
fn each_access_asks_for_the_alignment_and_privilege_a3_2_1_gives_it() {
use Call::{Raise, Read, Write};
let cases: [(&[u16], &[Call]); 14] = [
(&[0xC803], &[Read(4, ALIGNED), Read(4, ALIGNED)]), (&[0xB408], &[Write(4, ALIGNED)]), (&[0xBC10], &[Read(4, ALIGNED)]), (&[0xE9D3, 0x1202], &[Read(4, ALIGNED), Read(4, ALIGNED)]), (&[0xE9C3, 0x1202], &[Write(4, ALIGNED), Write(4, ALIGNED)]), (&[0xE8D2, 0x1F5F], &[Read(2, ALIGNED)]), (
&[0xE842, 0x13FF],
&[Raise(Fault::Unaligned as u32), Write(4, ALIGNED)],
), (&[0xE8C2, 0x1F43], &[Write(1, ALIGNED)]), (&[0xF85D, 0x4B04], &[Read(4, PLAIN)]), (&[0x5ED1], &[Read(2, PLAIN)]), (&[0xE8D1, 0xF012], &[Read(2, PLAIN)]), (&[0xF852, 0x1EFF], &[Read(4, UNPRIVILEGED)]), (&[0xF932, 0x1EFF], &[Read(2, UNPRIVILEGED)]), (&[0xF802, 0x1EFF], &[Write(1, UNPRIVILEGED)]), ];
for (code, calls) in cases {
assert_eq!(translated(code).as_deref(), Ok(calls), "{code:04X?}");
}
}
#[test]
fn the_unallocated_encodings_are_undefined() {
let undefined: [&[u16]; 30] = [
&[0xE810, 0x0006], &[0xE990, 0x0006], &[0xE8C2, 0x1F63], &[0xE8D2, 0x1F2F], &[0xF952, 0x1004], &[0xF9DF, 0x1004], &[0xF852, 0x1804], &[0xF832, 0x1A02], &[0xF852, 0x1040], &[0xF812, 0x1100], &[0xF862, 0x1004], &[0xF8E2, 0x1004], &[0xF842, 0x1040], &[0xF802, 0x1100], &[0xF822, 0x1200], &[0xF8CF, 0x1004], &[0xF84F, 0x1F04], &[0xF842, 0x1804], &[0xF84F, 0x1003], &[0xF84F, 0x1E04], &[0xF88F, 0x1004], &[0xF80F, 0x1F01], &[0xF802, 0x1801], &[0xF80F, 0x1003], &[0xF80F, 0x1E04], &[0xF8AF, 0x1004], &[0xF82F, 0x1F02], &[0xF822, 0x1A02], &[0xF82F, 0x1003], &[0xF82F, 0x1E04], ];
for code in undefined {
assert_eq!(translated(code), Err(Undefined), "{code:04X?}");
}
}
#[test]
fn an_unpredictable_write_of_the_pc_through_r_is_undefined() {
let undefined: [&[u16]; 12] = [
&[0xF852, 0xFEFF], &[0xF812, 0xFF01], &[0xF832, 0xF902], &[0xF932, 0xFEFF], &[0xE852, 0xFFFF], &[0xE842, 0x1FFF], &[0xE9D3, 0xF2FF], &[0xE9D3, 0x1FFF], &[0xE9EF, 0x1202], &[0xE86F, 0x1202], &[0xE8BF, 0x0006], &[0xE8AF, 0x0006], ];
for code in undefined {
assert_eq!(translated(code), Err(Undefined), "{code:04X?}");
}
}
}