use cranelift_codegen::ir::{InstBuilder, Value, types};
use super::alu::{ShiftType, add_with_carry, decode_imm_shift, shift_c, shift_c_register};
use super::{Emitter, Flag, Translated, Undefined, bits};
#[derive(Clone, Copy)]
struct Writes {
destination: Option<u32>,
setflags: bool,
}
impl Writes {
const FLAGS: Self = Self {
destination: None,
setflags: true,
};
fn register(d: u32, setflags: bool) -> Self {
Self {
destination: Some(d),
setflags,
}
}
}
fn write(e: &mut Emitter<'_, '_>, writes: Writes, result: Value) {
if let Some(d) = writes.destination {
e.set_reg(d, result);
}
if writes.setflags {
e.set_nz(result);
}
}
pub(super) fn shift_add_subtract_move_compare(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let setflags = !e.in_it();
let (low, middle, high) = (bits(hw, 2, 0), bits(hw, 5, 3), bits(hw, 10, 8));
let imm8 = bits(hw, 7, 0);
match bits(hw, 13, 11) {
0b000 if bits(hw, 10, 6) == 0 => {
let result = e.reg(middle);
write(e, Writes::register(low, true), result);
}
shift @ 0b000..=0b010 => {
shift_immediate(e, hw, shift, Writes::register(low, setflags));
}
0b011 => {
let x = e.reg(middle);
let operand = bits(hw, 8, 6);
let y = if bits(hw, 10, 10) == 0 {
e.reg(operand)
} else {
e.constant(operand)
};
let writes = Writes::register(low, setflags);
add_or_subtract(e, writes, x, y, bits(hw, 9, 9) == 1);
}
0b100 => {
let result = e.constant(imm8);
write(e, Writes::register(high, setflags), result);
}
0b101 => {
let (x, y) = (e.reg(high), e.constant(imm8));
add_or_subtract(e, Writes::FLAGS, x, y, true);
}
op => {
let (x, y) = (e.reg(high), e.constant(imm8));
add_or_subtract(e, Writes::register(high, setflags), x, y, op == 0b111);
}
}
}
fn shift_immediate(e: &mut Emitter<'_, '_>, hw: u32, shift: u32, writes: Writes) {
let (shift_type, amount) = decode_imm_shift(shift, bits(hw, 10, 6));
let value = e.reg(bits(hw, 5, 3));
let carry_in = e.flag(Flag::C);
let (result, carry) = shift_c(e, value, shift_type, amount, carry_in);
write(e, writes, result);
if writes.setflags {
e.set_flag(Flag::C, carry);
}
}
fn add_or_subtract(e: &mut Emitter<'_, '_>, writes: Writes, x: Value, y: Value, subtract: bool) {
let (y, carry_in) = if subtract {
(e.b.ins().bnot(y), e.constant(1))
} else {
(y, e.constant(0))
};
add_with_flags(e, writes, x, y, carry_in);
}
fn add_with_flags(e: &mut Emitter<'_, '_>, writes: Writes, x: Value, y: Value, carry_in: Value) {
let (result, carry, overflow) = add_with_carry(e, x, y, carry_in);
write(e, writes, result);
if writes.setflags {
e.set_flag(Flag::C, carry);
e.set_flag(Flag::V, overflow);
}
}
pub(super) fn data_processing(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let (dn, m) = (bits(hw, 2, 0), bits(hw, 5, 3));
let writes = Writes::register(dn, !e.in_it());
let first = e.reg(dn);
let second = e.reg(m);
let (zero, one) = (e.constant(0), e.constant(1));
let result = match bits(hw, 9, 6) {
0b0000 => e.b.ins().band(first, second),
0b0001 => e.b.ins().bxor(first, second),
op @ (0b0010 | 0b0011 | 0b0100 | 0b0111) => {
let shift_type = match op {
0b0010 => ShiftType::Lsl,
0b0011 => ShiftType::Lsr,
0b0100 => ShiftType::Asr,
_ => ShiftType::Ror,
};
shift_register(e, writes, first, shift_type, second);
return;
}
0b0101 => {
let carry = e.flag(Flag::C);
add_with_flags(e, writes, first, second, carry);
return;
}
0b0110 => {
let carry = e.flag(Flag::C);
let inverted = e.b.ins().bnot(second);
add_with_flags(e, writes, first, inverted, carry);
return;
}
0b1000 => {
let result = e.b.ins().band(first, second);
write(e, Writes::FLAGS, result);
return;
}
0b1001 => {
let inverted = e.b.ins().bnot(second);
add_with_flags(e, writes, inverted, zero, one);
return;
}
0b1010 => {
let inverted = e.b.ins().bnot(second);
add_with_flags(e, Writes::FLAGS, first, inverted, one);
return;
}
0b1011 => {
add_with_flags(e, Writes::FLAGS, first, second, zero);
return;
}
0b1100 => e.b.ins().bor(first, second),
0b1101 => e.b.ins().imul(second, first),
0b1110 => e.b.ins().band_not(first, second),
_ => e.b.ins().bnot(second),
};
write(e, writes, result);
}
fn shift_register(
e: &mut Emitter<'_, '_>,
writes: Writes,
value: Value,
shift_type: ShiftType,
by: Value,
) {
let amount = e.b.ins().band_imm_u(by, 0xFF);
let carry_in = e.flag(Flag::C);
let (result, carry) = shift_c_register(e, value, shift_type, amount, carry_in);
write(e, writes, result);
if writes.setflags {
e.set_flag(Flag::C, carry);
}
}
pub(super) fn special_data_branch_exchange(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let m = bits(hw, 6, 3);
let dn = bits(hw, 7, 7) << 3 | bits(hw, 2, 0);
match bits(hw, 9, 8) {
0b00 => {
let (x, y) = (e.reg(dn), e.reg(m));
let result = e.b.ins().iadd(x, y);
alu_write(e, dn, result);
}
0b01 => {
let (x, y) = (e.reg(dn), e.reg(m));
add_or_subtract(e, Writes::FLAGS, x, y, true);
}
0b10 => {
let result = e.reg(m);
alu_write(e, dn, result);
}
_ if bits(hw, 7, 7) == 0 => {
let target = e.reg(m);
e.branch_exchange(target);
}
_ => branch_link_exchange(e, m),
}
}
fn alu_write(e: &mut Emitter<'_, '_>, d: u32, result: Value) {
match d {
15 => e.branch(result),
13 => {
let aligned = e.b.ins().band_imm_s(result, !3);
e.set_reg(13, aligned);
}
_ => e.set_reg(d, result),
}
}
fn branch_link_exchange(e: &mut Emitter<'_, '_>, m: u32) {
let target = e.reg(m);
let link = e.constant(e.next_pc() | 1);
e.set_reg(14, link);
e.blx_write_pc(target);
}
pub(super) fn adr(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let aligned = e.pc().wrapping_add(4) & !3;
let result = e.constant(aligned.wrapping_add(bits(hw, 7, 0) << 2));
e.set_reg(bits(hw, 10, 8), result);
}
pub(super) fn add_sp_immediate(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let sp = e.reg(13);
let result = e.b.ins().iadd_imm_u(sp, i64::from(bits(hw, 7, 0) << 2));
e.set_reg(bits(hw, 10, 8), result);
}
pub(super) fn adjust_sp(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let imm32 = i64::from(bits(hw, 6, 0) << 2);
let sp = e.reg(13);
let by = if bits(hw, 7, 7) == 1 { -imm32 } else { imm32 };
let result = e.b.ins().iadd_imm_s(sp, by);
alu_write(e, 13, result);
}
pub(super) fn extend(e: &mut Emitter<'_, '_>, hw: u16) {
let hw = u32::from(hw);
let value = e.reg(bits(hw, 5, 3));
let result = match bits(hw, 7, 6) {
0b00 => {
let half = e.b.ins().ireduce(types::I16, value);
e.b.ins().sextend(types::I32, half)
}
0b01 => {
let byte = e.b.ins().ireduce(types::I8, value);
e.b.ins().sextend(types::I32, byte)
}
0b10 => e.b.ins().band_imm_u(value, 0xFFFF),
_ => e.b.ins().band_imm_u(value, 0xFF),
};
e.set_reg(bits(hw, 2, 0), result);
}
pub(super) fn reverse(e: &mut Emitter<'_, '_>, hw: u16) -> Translated {
let hw = u32::from(hw);
let value = e.reg(bits(hw, 5, 3));
let result = match bits(hw, 7, 6) {
0b00 => e.b.ins().bswap(value),
0b01 => {
let swapped = e.b.ins().bswap(value);
e.b.ins().rotr_imm_u(swapped, 16)
}
0b11 => {
let half = e.b.ins().ireduce(types::I16, value);
let swapped = e.b.ins().bswap(half);
e.b.ins().sextend(types::I32, swapped)
}
_ => return Err(Undefined),
};
e.set_reg(bits(hw, 2, 0), result);
Ok(())
}
#[cfg(test)]
mod tests {
use cranelift_codegen::ir;
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use super::super::super::state::State;
use super::super::testing::{Bench, RAM, SIZE};
use super::*;
use crate::emulator::Exit;
const LAST_OF_IT_AL: u32 = 0xE8;
const FIRST_OF_ITT_AL: u32 = 0xE4;
fn top_of_stack() -> u32 {
RAM + u32::try_from(SIZE).unwrap()
}
fn run(code: &[u16], set: impl FnOnce(&mut State)) -> State {
let mut bench = Bench::new(code);
set(&mut bench.core.state);
let count = u32::try_from(code.len()).unwrap();
assert_eq!(bench.run(count), (Exit::Budget, u64::from(count)));
bench.core.state.clone()
}
fn nzcv(state: &State) -> [u32; 4] {
[state.n, state.z, state.c, state.v]
}
fn set_nzcv(state: &mut State, [n, z, c, v]: [u32; 4]) {
(state.n, state.z, state.c, state.v) = (n, z, c, v);
}
fn shift_immediate(op: u16, imm5: u16, m: u16, d: u16) -> u16 {
op << 11 | imm5 << 6 | m << 3 | d
}
fn three_register(op: u16, operand: u16, n: u16, d: u16) -> u16 {
0x1800 | op << 9 | operand << 6 | n << 3 | d
}
fn with_imm8(op: u16, dn: u16, imm8: u16) -> u16 {
0x2000 | op << 11 | dn << 8 | imm8
}
fn data(opcode: u16, m: u16, dn: u16) -> u16 {
0x4000 | opcode << 6 | m << 3 | dn
}
fn high(op: u16, dn: u16, m: u16) -> u16 {
0x4400 | op << 8 | (dn >> 3) << 7 | m << 3 | (dn & 7)
}
fn bx(m: u16) -> u16 {
0x4700 | m << 3
}
fn blx(m: u16) -> u16 {
0x4780 | m << 3
}
fn extend_hw(op: u16, m: u16, d: u16) -> u16 {
0xB200 | op << 6 | m << 3 | d
}
fn reverse_hw(op: u16, m: u16, d: u16) -> u16 {
0xBA00 | op << 6 | m << 3 | d
}
fn translated(translate: fn(&mut Emitter<'_, '_>, u16) -> Translated, hw: u16) -> Translated {
let mut function = ir::Function::new();
function
.signature
.params
.push(ir::AbiParam::new(types::I64));
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);
translate(&mut e, hw)
}
#[test]
fn lsl_immediate_shifts_and_sets_n_z_c() {
let state = run(&[shift_immediate(0, 4, 1, 0)], |state| {
state.r[1] = 0x1800_0001;
set_nzcv(state, [0, 1, 0, 1]);
});
assert_eq!(state.r[0], 0x8000_0010);
assert_eq!(nzcv(&state), [1, 0, 1, 1]);
}
#[test]
fn lsr_immediate_of_zero_shifts_by_32() {
let state = run(
&[shift_immediate(1, 0, 1, 0), shift_immediate(1, 1, 3, 2)],
|state| {
state.r[1] = 0x8000_0000;
state.r[3] = 0x8000_0003;
},
);
assert_eq!(state.r[0], 0);
assert_eq!(state.r[2], 0x4000_0001);
assert_eq!(nzcv(&state), [0, 0, 1, 0]);
let state = run(&[shift_immediate(1, 0, 1, 0)], |state| {
state.r[1] = 0x8000_0000;
});
assert_eq!(nzcv(&state), [0, 1, 1, 0]);
}
#[test]
fn asr_immediate_shifts_in_the_sign() {
let state = run(&[shift_immediate(2, 0, 1, 0)], |state| {
state.r[1] = 0x8000_0000;
});
assert_eq!((state.r[0], nzcv(&state)), (0xFFFF_FFFF, [1, 0, 1, 0]));
let state = run(&[shift_immediate(2, 4, 1, 0)], |state| {
state.r[1] = 0x8000_0018;
state.c = 0;
});
assert_eq!((state.r[0], nzcv(&state)), (0xF800_0001, [1, 0, 1, 0]));
}
#[test]
fn movs_register_sets_n_and_z_and_keeps_c_and_v() {
let state = run(&[shift_immediate(0, 0, 1, 0)], |state| {
state.r[0] = 5;
set_nzcv(state, [1, 0, 1, 1]);
});
assert_eq!((state.r[0], nzcv(&state)), (0, [0, 1, 1, 1]));
}
#[test]
fn movs_register_inside_an_it_block_still_sets_the_flags() {
let state = run(&[shift_immediate(0, 0, 1, 0)], |state| {
state.r[1] = 0x8000_0000;
state.it = LAST_OF_IT_AL;
});
assert_eq!((state.r[0], nzcv(&state)), (0x8000_0000, [1, 0, 0, 0]));
}
#[test]
fn add_register_sets_carry_and_overflow() {
let state = run(&[three_register(0, 2, 1, 0)], |state| {
state.r[1] = 0x7FFF_FFFF;
state.r[2] = 1;
});
assert_eq!((state.r[0], nzcv(&state)), (0x8000_0000, [1, 0, 0, 1]));
let state = run(&[three_register(0, 2, 1, 0)], |state| {
state.r[1] = 0xFFFF_FFFF;
state.r[2] = 1;
});
assert_eq!((state.r[0], nzcv(&state)), (0, [0, 1, 1, 0]));
}
#[test]
fn sub_register_sets_carry_as_not_borrow() {
let state = run(&[three_register(1, 2, 1, 0)], |state| {
state.r[1] = 3;
state.r[2] = 5;
});
assert_eq!((state.r[0], nzcv(&state)), (0xFFFF_FFFE, [1, 0, 0, 0]));
let state = run(&[three_register(1, 2, 1, 0)], |state| {
state.r[1] = 0x8000_0000;
state.r[2] = 1;
});
assert_eq!((state.r[0], nzcv(&state)), (0x7FFF_FFFF, [0, 0, 1, 1]));
}
#[test]
fn add_and_sub_take_a_3_bit_immediate() {
let state = run(
&[three_register(2, 7, 1, 0), three_register(3, 7, 3, 2)],
|state| {
state.r[1] = 0xFFFF_FFF9;
state.r[3] = 7;
},
);
assert_eq!((state.r[0], state.r[2]), (0, 0));
assert_eq!(nzcv(&state), [0, 1, 1, 0], "7 - 7 borrows nothing");
}
#[test]
fn mov_immediate_sets_n_and_z_only() {
let state = run(&[with_imm8(0, 3, 0), with_imm8(0, 4, 0xFF)], |state| {
state.r[3] = 9;
set_nzcv(state, [1, 0, 1, 1]);
});
assert_eq!((state.r[3], state.r[4]), (0, 0xFF));
assert_eq!(nzcv(&state), [0, 0, 1, 1]);
}
fn sets_flags_only_outside_it(
hw: u16,
registers: [u32; 3],
preset: [u32; 4],
result: u32,
flags: [u32; 4],
) {
assert!(
preset[0] != flags[0] && preset[1] != flags[1],
"{hw:#06x} has to move N and Z"
);
for (it, expected) in [(0, flags), (LAST_OF_IT_AL, preset)] {
let state = run(&[hw], |state| {
state.r[..3].copy_from_slice(®isters);
set_nzcv(state, preset);
state.it = it;
});
assert_eq!(
(state.r[0], nzcv(&state)),
(result, expected),
"{hw:#06x} with ITSTATE {it:#04x}"
);
}
}
#[test]
fn shift_add_subtract_and_move_set_flags_only_outside_an_it_block() {
let cases = [
(
shift_immediate(0, 1, 1, 0),
[0, 0x8000_0001, 0],
[1, 1, 0, 1],
2,
[0, 0, 1, 1],
),
(
shift_immediate(1, 1, 1, 0),
[0, 3, 0],
[1, 1, 0, 0],
1,
[0, 0, 1, 0],
),
(
shift_immediate(2, 1, 1, 0),
[0, 3, 0],
[1, 1, 0, 0],
1,
[0, 0, 1, 0],
),
(
three_register(0, 2, 1, 0),
[0, 0x8000_0000, 0x8000_0001],
[1, 1, 0, 0],
1,
[0, 0, 1, 1],
),
(
three_register(1, 2, 1, 0),
[0, 0x8000_0000, 1],
[1, 1, 0, 0],
0x7FFF_FFFF,
[0, 0, 1, 1],
),
(
three_register(2, 2, 1, 0),
[0, 0xFFFF_FFFF, 0],
[1, 1, 0, 1],
1,
[0, 0, 1, 0],
),
(
three_register(3, 1, 1, 0),
[0, 0x8000_0000, 0],
[1, 1, 0, 0],
0x7FFF_FFFF,
[0, 0, 1, 1],
),
(with_imm8(0, 0, 5), [0, 0, 0], [1, 1, 1, 1], 5, [0, 0, 1, 1]),
(
with_imm8(2, 0, 2),
[0xFFFF_FFFF, 0, 0],
[1, 1, 0, 1],
1,
[0, 0, 1, 0],
),
(
with_imm8(3, 0, 1),
[0x8000_0000, 0, 0],
[1, 1, 0, 0],
0x7FFF_FFFF,
[0, 0, 1, 1],
),
];
for (hw, registers, preset, result, flags) in cases {
sets_flags_only_outside_it(hw, registers, preset, result, flags);
}
}
#[test]
fn cmp_immediate_sets_the_flags_even_inside_an_it_block() {
let state = run(&[with_imm8(1, 2, 5)], |state| {
state.r[2] = 5;
state.it = LAST_OF_IT_AL;
});
assert_eq!((state.r[2], nzcv(&state)), (5, [0, 1, 1, 0]));
let state = run(&[with_imm8(1, 2, 0xFF)], |state| state.r[2] = 0x8000_0000);
assert_eq!(nzcv(&state), [0, 0, 1, 1]);
}
#[test]
fn add_and_sub_take_an_8_bit_immediate() {
let state = run(&[with_imm8(2, 7, 0xFF)], |state| state.r[7] = 1);
assert_eq!((state.r[7], nzcv(&state)), (0x100, [0, 0, 0, 0]));
let state = run(&[with_imm8(3, 7, 1)], |state| state.r[7] = 0);
assert_eq!((state.r[7], nzcv(&state)), (0xFFFF_FFFF, [1, 0, 0, 0]));
}
#[test]
fn the_logical_operations_set_n_and_z_and_keep_c_and_v() {
let cases = [
(0b0000, 0xFF00_FF00, 0x0F0F_0F0F, 0x0F00_0F00),
(0b0001, 0xFF00_FF00, 0x0F0F_0F0F, 0xF00F_F00F),
(0b1100, 0xFF00_FF00, 0x0F0F_0F0F, 0xFF0F_FF0F),
(0b1110, 0xFF00_FF00, 0x0F0F_0F0F, 0xF000_F000),
(0b1111, 0, 0x0F0F_0F0F, 0xF0F0_F0F0),
(0b0000, 0xFF00_FF00, 0x00FF_00FF, 0),
];
for (opcode, first, second, result) in cases {
let state = run(&[data(opcode, 1, 0)], |state| {
state.r[0] = first;
state.r[1] = second;
set_nzcv(state, [0, 0, 1, 1]);
});
assert_eq!(state.r[0], result, "opcode {opcode:#06b}");
let flags = [result >> 31, u32::from(result == 0), 1, 1];
assert_eq!(nzcv(&state), flags, "opcode {opcode:#06b}");
}
}
#[test]
fn tst_sets_n_and_z_and_writes_nothing() {
let state = run(&[data(0b1000, 1, 0)], |state| {
state.r[0] = 0x8000_0001;
state.r[1] = 0x8000_0000;
state.it = LAST_OF_IT_AL;
});
assert_eq!((state.r[0], nzcv(&state)), (0x8000_0001, [1, 0, 0, 0]));
}
#[test]
fn a_register_shift_takes_the_bottom_byte() {
let cases = [
(0b0010, 0x1800_0001, 0x104, 0x8000_0010, [1, 0, 1, 0]),
(0b0011, 0x8000_0018, 0x104, 0x0800_0001, [0, 0, 1, 0]),
(0b0100, 0x8000_0018, 0x104, 0xF800_0001, [1, 0, 1, 0]),
(0b0111, 0x0000_0018, 0x104, 0x8000_0001, [1, 0, 1, 0]),
];
for (opcode, value, by, result, flags) in cases {
let state = run(&[data(opcode, 1, 0)], |state| {
state.r[0] = value;
state.r[1] = by;
});
assert_eq!((state.r[0], nzcv(&state)), (result, flags), "{opcode:#06b}");
}
}
#[test]
fn a_register_shift_by_0_32_or_more_follows_shift_c() {
let cases = [
(0b0010, 0x8000_0001, 0x100, 0x8000_0001, [1, 0, 1, 1]),
(0b0010, 0x0000_0001, 32, 0, [0, 1, 1, 1]),
(0b0010, 0xFFFF_FFFF, 33, 0, [0, 1, 0, 1]),
(0b0011, 0x8000_0000, 32, 0, [0, 1, 1, 1]),
(0b0011, 0xFFFF_FFFF, 0xFF, 0, [0, 1, 0, 1]),
(0b0100, 0x8000_0000, 0xFF, 0xFFFF_FFFF, [1, 0, 1, 1]),
(0b0100, 0x7FFF_FFFF, 40, 0, [0, 1, 0, 1]),
(0b0111, 0x8000_0000, 32, 0x8000_0000, [1, 0, 1, 1]),
(0b0111, 0x7000_0000, 64, 0x7000_0000, [0, 0, 0, 1]),
];
for (opcode, value, by, result, flags) in cases {
let state = run(&[data(opcode, 1, 0)], |state| {
state.r[0] = value;
state.r[1] = by;
set_nzcv(state, [0, 0, 1, 1]);
});
assert_eq!(
(state.r[0], nzcv(&state)),
(result, flags),
"{opcode:#06b} {value:#x} by {by}"
);
}
}
#[test]
fn adc_adds_the_carry() {
let state = run(&[data(0b0101, 1, 0)], |state| {
state.r[0] = 0xFFFF_FFFF;
state.r[1] = 0;
state.c = 1;
});
assert_eq!((state.r[0], nzcv(&state)), (0, [0, 1, 1, 0]));
let state = run(&[data(0b0101, 1, 0)], |state| {
state.r[0] = 0x7FFF_FFFF;
state.r[1] = 0;
});
assert_eq!((state.r[0], nzcv(&state)), (0x7FFF_FFFF, [0, 0, 0, 0]));
}
#[test]
fn sbc_takes_not_carry_more() {
let state = run(&[data(0b0110, 1, 0)], |state| {
state.r[0] = 5;
state.r[1] = 5;
});
assert_eq!((state.r[0], nzcv(&state)), (0xFFFF_FFFF, [1, 0, 0, 0]));
let state = run(&[data(0b0110, 1, 0)], |state| {
state.r[0] = 5;
state.r[1] = 5;
state.c = 1;
});
assert_eq!((state.r[0], nzcv(&state)), (0, [0, 1, 1, 0]));
}
#[test]
fn rsb_negates() {
let cases = [
(0, 0, [0, 1, 1, 0]),
(1, 0xFFFF_FFFF, [1, 0, 0, 0]),
(0x8000_0000, 0x8000_0000, [1, 0, 0, 1]),
];
for (value, result, flags) in cases {
let state = run(&[data(0b1001, 1, 0)], |state| {
state.r[0] = 0x1234;
state.r[1] = value;
});
assert_eq!((state.r[0], nzcv(&state)), (result, flags), "-{value:#x}");
assert_eq!(state.r[1], value);
}
}
#[test]
fn cmp_and_cmn_register_set_the_flags_and_write_nothing() {
let state = run(&[data(0b1010, 1, 0)], |state| {
state.r[0] = 3;
state.r[1] = 5;
});
assert_eq!((state.r[0], nzcv(&state)), (3, [1, 0, 0, 0]));
let state = run(&[data(0b1011, 1, 0)], |state| {
state.r[0] = 0xFFFF_FFFB;
state.r[1] = 5;
state.it = LAST_OF_IT_AL;
});
assert_eq!((state.r[0], nzcv(&state)), (0xFFFF_FFFB, [0, 1, 1, 0]));
}
#[test]
fn mul_keeps_the_low_word_and_c_and_v() {
let state = run(&[data(0b1101, 1, 0)], |state| {
state.r[0] = 0x1_0001;
state.r[1] = 0xFFFF;
set_nzcv(state, [0, 1, 1, 1]);
});
assert_eq!((state.r[0], nzcv(&state)), (0xFFFF_FFFF, [1, 0, 1, 1]));
let state = run(&[data(0b1101, 1, 0)], |state| {
state.r[0] = 0x1_0000;
state.r[1] = 0x1_0000;
});
assert_eq!((state.r[0], nzcv(&state)), (0, [0, 1, 0, 0]));
}
#[test]
fn data_processing_sets_flags_only_outside_an_it_block() {
let cases = [
(0b0000, [0x0F, 0x0B], [1, 1, 1, 1], 0x0B, [0, 0, 1, 1]),
(0b0001, [0x0F, 0x01], [1, 1, 1, 1], 0x0E, [0, 0, 1, 1]),
(0b0010, [0x8000_0001, 1], [1, 1, 0, 1], 2, [0, 0, 1, 1]),
(0b0011, [3, 1], [1, 1, 0, 0], 1, [0, 0, 1, 0]),
(0b0100, [3, 1], [1, 1, 0, 0], 1, [0, 0, 1, 0]),
(0b0101, [0xFFFF_FFFF, 2], [1, 1, 0, 1], 1, [0, 0, 1, 0]),
(0b0110, [5, 3], [1, 1, 0, 1], 1, [0, 0, 1, 0]),
(0b0111, [6, 1], [1, 1, 1, 0], 3, [0, 0, 0, 0]),
(0b1001, [0x1234, 0xFFFF_FFFF], [1, 1, 1, 1], 1, [0, 0, 0, 0]),
(0b1100, [0x10, 0x01], [1, 1, 1, 1], 0x11, [0, 0, 1, 1]),
(0b1101, [3, 5], [1, 1, 1, 1], 15, [0, 0, 1, 1]),
(0b1110, [0x0F, 0x01], [1, 1, 1, 1], 0x0E, [0, 0, 1, 1]),
(0b1111, [0, 0xFFFF_FFF0], [1, 1, 1, 1], 0x0F, [0, 0, 1, 1]),
];
for (opcode, [first, second], preset, result, flags) in cases {
let registers = [first, second, 0];
sets_flags_only_outside_it(data(opcode, 1, 0), registers, preset, result, flags);
}
}
#[test]
fn add_high_registers_sets_no_flags_and_reads_the_pc_plus_4() {
let state = run(&[high(0, 8, 1), high(0, 0, 15)], |state| {
state.r[8] = 0xFFFF_FFFF;
state.r[1] = 1;
state.r[0] = 0x100;
});
assert_eq!((state.r[8], nzcv(&state)), (0, [0, 0, 0, 0]));
assert_eq!(state.r[0], 0x100 + 2 + 4);
}
#[test]
fn add_sp_plus_register_writes_either_side_and_keeps_sp_aligned() {
let state = run(&[high(0, 1, 13), high(0, 13, 2)], |state| {
state.r[1] = 0x10;
state.r[2] = 0x7;
});
assert_eq!(state.r[1], top_of_stack() + 0x10);
assert_eq!(state.r[13], top_of_stack() + 4);
}
#[test]
fn add_to_the_pc_branches() {
let state = run(&[high(0, 15, 1)], |state| state.r[1] = 0x101);
assert_eq!((state.r[15], state.t), (0x104, 1));
}
#[test]
fn add_pc_to_the_pc_branches_to_the_sum() {
let state = run(&[high(0, 15, 15)], |_| {});
assert_eq!(state.r[15], 8);
}
#[test]
fn cmp_high_registers_sets_the_flags() {
let state = run(&[high(1, 8, 9)], |state| {
state.r[8] = 0x8000_0000;
state.r[9] = 1;
});
assert_eq!(nzcv(&state), [0, 0, 1, 1]);
let state = run(&[high(1, 1, 2)], |state| {
state.r[1] = 7;
state.r[2] = 7;
});
assert_eq!(nzcv(&state), [0, 1, 1, 0]);
let state = run(&[high(1, 15, 0)], |state| state.r[0] = 4);
assert_eq!(nzcv(&state), [0, 1, 1, 0], "the PC reads as 4");
}
#[test]
fn mov_high_registers_sets_no_flags() {
let state = run(&[high(2, 8, 1), high(2, 0, 15), high(2, 13, 2)], |state| {
state.r[1] = 0;
state.r[2] = 0x2000_1003;
set_nzcv(state, [1, 0, 1, 0]);
});
assert_eq!(
(state.r[8], state.r[0], state.r[13]),
(0, 2 + 4, 0x2000_1000)
);
assert_eq!(nzcv(&state), [1, 0, 1, 0]);
}
#[test]
fn mov_to_the_pc_branches() {
let state = run(&[high(2, 15, 1)], |state| state.r[1] = 0x201);
assert_eq!((state.r[15], state.t), (0x200, 1));
}
#[test]
fn a_pc_write_inside_an_it_block_not_last_branches() {
for hw in [high(2, 15, 1), high(0, 15, 1), bx(1), blx(1)] {
let state = run(&[hw], |state| {
state.r[1] = 0x41;
state.it = FIRST_OF_ITT_AL;
});
let at = if hw == high(0, 15, 1) { 0x44 } else { 0x40 };
assert_eq!(state.r[15], at, "{hw:#06x}");
}
}
#[test]
fn bx_takes_t_from_bit_0() {
let state = run(&[bx(1)], |state| state.r[1] = 0x301);
assert_eq!((state.r[15], state.t), (0x300, 1));
let state = run(&[bx(1)], |state| state.r[1] = 0x300);
assert_eq!((state.r[15], state.t), (0x300, 0));
}
#[test]
fn bx_ignores_its_should_be_zero_bits() {
let state = run(&[bx(1) | 0b111], |state| state.r[1] = 0x301);
assert_eq!((state.r[15], state.t, state.r[14]), (0x300, 1, 0));
}
#[test]
fn blx_links_and_branches() {
let state = run(&[high(2, 0, 0), blx(14)], |state| state.r[14] = 0x401);
assert_eq!((state.r[15], state.t, state.r[14]), (0x400, 1, 0x5));
let state = run(&[blx(1) | 0b111], |state| state.r[1] = 0x400);
assert_eq!((state.r[15], state.t, state.r[14]), (0x400, 0, 0x3));
}
#[test]
fn blx_in_handler_mode_is_never_an_exception_return() {
let state = run(&[blx(1)], |state| {
state.r[1] = 0xFFFF_FFF9;
state.ipsr = 3;
});
assert_eq!((state.r[15], state.t, state.r[14]), (0xFFFF_FFF8, 1, 0x3));
}
#[test]
fn blx_of_the_pc_clears_t() {
let state = run(&[blx(15)], |_| {});
assert_eq!((state.r[15], state.t, state.r[14]), (4, 0, 3));
}
#[test]
fn adr_adds_to_the_aligned_pc() {
let state = run(&[0xA000 | 1 << 8, 0xA000 | 2 << 8 | 0xFF], |_| {});
assert_eq!((state.r[1], state.r[2]), (4, 4 + 0x3FC));
}
#[test]
fn add_sp_immediate_adds_a_word_offset() {
let state = run(&[0xA800 | 3 << 8 | 0xFF], |state| {
set_nzcv(state, [1, 1, 1, 1]);
});
assert_eq!(
(state.r[3], nzcv(&state)),
(top_of_stack() + 0x3FC, [1, 1, 1, 1])
);
}
#[test]
fn sp_moves_by_a_word_multiple() {
let state = run(&[0xB000 | 0x7F], |state| set_nzcv(state, [1, 1, 1, 1]));
assert_eq!(
(state.r[13], nzcv(&state)),
(top_of_stack() + 0x1FC, [1, 1, 1, 1])
);
let state = run(&[0xB080 | 0x7F], |state| set_nzcv(state, [1, 1, 0, 1]));
assert_eq!(
(state.r[13], nzcv(&state)),
(top_of_stack() - 0x1FC, [1, 1, 0, 1])
);
let state = run(&[0xB000 | 0x7F, 0xB080 | 1], |_| {});
assert_eq!(state.r[13], top_of_stack() + 0x1FC - 4);
}
#[test]
fn the_extends_take_the_low_byte_or_halfword() {
let cases = [
(0b00, 0x1234_8001, 0xFFFF_8001),
(0b00, 0x8765_7001, 0x7001),
(0b01, 0x1234_5680, 0xFFFF_FF80),
(0b01, 0x8765_437F, 0x7F),
(0b10, 0xFFFF_8001, 0x8001),
(0b11, 0x1234_56F0, 0xF0),
];
for (op, value, result) in cases {
let state = run(&[extend_hw(op, 1, 0)], |state| {
state.r[1] = value;
set_nzcv(state, [0, 1, 0, 1]);
});
assert_eq!(state.r[0], result, "{op:#04b} of {value:#x}");
assert_eq!(nzcv(&state), [0, 1, 0, 1]);
}
}
#[test]
fn the_reverses_swap_bytes() {
let cases = [
(0b00, 0x1234_5678, 0x7856_3412),
(0b01, 0x1234_5678, 0x3412_7856),
(0b11, 0x1234_5680, 0xFFFF_8056),
(0b11, 0x1234_8070, 0x7080),
];
for (op, value, result) in cases {
let state = run(&[reverse_hw(op, 1, 0)], |state| {
state.r[1] = value;
});
assert_eq!(state.r[0], result, "{op:#04b} of {value:#x}");
}
}
#[test]
fn the_gap_among_the_reverses_is_undefined() {
for hw in [reverse_hw(0b10, 0, 0), reverse_hw(0b10, 7, 7)] {
assert_eq!(translated(reverse, hw), Err(Undefined), "{hw:#06x}");
assert_eq!(translated(super::super::decode16, hw), Err(Undefined));
}
assert_eq!(translated(reverse, reverse_hw(0b11, 0, 0)), Ok(()));
}
}