use super::regs::{ADDR_MASK, Mode, field_get, field_mask, field_set, field_width, get_nibble};
fn add_serial(a: u64, b: u64, lo: u8, hi: u8, mode: Mode, carry_in: bool) -> (u64, bool) {
let (lo, hi) = (lo & 0xF, hi & 0xF);
let mut result = a;
let mut carry = carry_in;
for i in lo..=hi {
let s = get_nibble(a, i) + get_nibble(b, i) + u8::from(carry);
let (digit, c) = match mode {
Mode::Hex => (s & 0xF, s > 0xF),
Mode::Dec if s > 9 => ((s + 6) & 0xF, true),
Mode::Dec => (s, false),
};
carry = c;
result = super::regs::set_nibble(result, i, digit);
}
(result, carry)
}
fn sub_serial(a: u64, b: u64, lo: u8, hi: u8, mode: Mode, borrow_in: bool) -> (u64, bool) {
let (lo, hi) = (lo & 0xF, hi & 0xF);
let mut result = a;
let mut borrow = borrow_in;
for i in lo..=hi {
let d = i16::from(get_nibble(a, i)) - i16::from(get_nibble(b, i)) - i16::from(borrow);
let (digit, br) = if d < 0 {
let radix = match mode {
Mode::Hex => 16,
Mode::Dec => 10,
};
(((d + radix) & 0xF) as u8, true)
} else {
((d & 0xF) as u8, false)
};
borrow = br;
result = super::regs::set_nibble(result, i, digit);
}
(result, borrow)
}
pub fn add(a: u64, b: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
add_serial(a, b, lo, hi, mode, false)
}
pub fn sub(a: u64, b: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
sub_serial(a, b, lo, hi, mode, false)
}
pub fn inc(a: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
add_serial(a, 0, lo, hi, mode, true)
}
pub fn dec(a: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
sub_serial(a, 0, lo, hi, mode, true)
}
pub fn add_const(a: u64, c: u8, lo: u8, hi: u8) -> (u64, bool) {
let c = u64::from(c);
let (lo, hi) = (lo & 0xF, hi & 0xF);
if lo == hi {
let rot = 4 * u32::from(lo);
let (sum, carry) = a.rotate_right(rot).overflowing_add(c);
return (sum.rotate_left(rot), carry);
}
let v = u128::from(field_get(a, lo, hi)) + u128::from(c);
let bits = 4 * field_width(lo, hi);
let carry = (v >> bits) != 0;
(field_set(a, lo, hi, v as u64), carry)
}
pub fn sub_const(a: u64, c: u8, lo: u8, hi: u8) -> (u64, bool) {
let c = u64::from(c);
let (lo, hi) = (lo & 0xF, hi & 0xF);
if lo == hi {
let rot = 4 * u32::from(lo);
let (diff, borrow) = a.rotate_right(rot).overflowing_sub(c);
return (diff.rotate_left(rot), borrow);
}
let v = field_get(a, lo, hi);
let borrow = v < c;
(field_set(a, lo, hi, v.wrapping_sub(c)), borrow)
}
pub fn neg(a: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
let (v, _) = sub_serial(a & !field_mask(lo, hi), a, lo, hi, mode, false);
(v, nonzero(a, lo, hi))
}
pub fn not(a: u64, lo: u8, hi: u8, mode: Mode) -> (u64, bool) {
let v = match mode {
Mode::Hex => a ^ field_mask(lo, hi),
Mode::Dec => {
let (lo, hi) = (lo & 0xF, hi & 0xF);
let mut r = a;
for i in lo..=hi {
r = super::regs::set_nibble(r, i, 9u8.wrapping_sub(get_nibble(a, i)) & 0xF);
}
r
}
};
(v, false)
}
pub fn and(a: u64, b: u64, lo: u8, hi: u8) -> u64 {
a & (b | !field_mask(lo, hi))
}
pub fn or(a: u64, b: u64, lo: u8, hi: u8) -> u64 {
a | (b & field_mask(lo, hi))
}
pub fn exchange(a: u64, b: u64, lo: u8, hi: u8) -> (u64, u64) {
let m = field_mask(lo, hi);
((a & !m) | (b & m), (b & !m) | (a & m))
}
pub fn copy(dst: u64, src: u64, lo: u8, hi: u8) -> u64 {
let m = field_mask(lo, hi);
(dst & !m) | (src & m)
}
pub fn clear(a: u64, lo: u8, hi: u8) -> u64 {
a & !field_mask(lo, hi)
}
pub fn shl_nibble(a: u64, lo: u8, hi: u8) -> (u64, bool) {
let v = field_get(a, lo, hi);
(field_set(a, lo, hi, v << 4), false)
}
pub fn shr_nibble(a: u64, lo: u8, hi: u8) -> (u64, bool) {
let v = field_get(a, lo, hi);
(field_set(a, lo, hi, v >> 4), v & 0xF != 0)
}
pub fn rol_nibble(a: u64, lo: u8, hi: u8) -> (u64, bool) {
let n = field_width(lo, hi);
if n == 0 {
return (a, false);
}
let v = field_get(a, lo, hi);
let top = v >> (4 * (n - 1));
(field_set(a, lo, hi, (v << 4) | top), false)
}
pub fn ror_nibble(a: u64, lo: u8, hi: u8) -> (u64, bool) {
let n = field_width(lo, hi);
if n == 0 {
return (a, false);
}
let v = field_get(a, lo, hi);
let low = v & 0xF;
(
field_set(a, lo, hi, (v >> 4) | (low << (4 * (n - 1)))),
low != 0,
)
}
pub fn shr_bit(a: u64, lo: u8, hi: u8) -> (u64, bool) {
let v = field_get(a, lo, hi);
(field_set(a, lo, hi, v >> 1), v & 1 != 0)
}
pub fn eq(a: u64, b: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) == field_get(b, lo, hi)
}
pub fn ne(a: u64, b: u64, lo: u8, hi: u8) -> bool {
!eq(a, b, lo, hi)
}
pub fn zero(a: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) == 0
}
pub fn nonzero(a: u64, lo: u8, hi: u8) -> bool {
!zero(a, lo, hi)
}
pub fn gt(a: u64, b: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) > field_get(b, lo, hi)
}
pub fn ge(a: u64, b: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) >= field_get(b, lo, hi)
}
pub fn lt(a: u64, b: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) < field_get(b, lo, hi)
}
pub fn le(a: u64, b: u64, lo: u8, hi: u8) -> bool {
field_get(a, lo, hi) <= field_get(b, lo, hi)
}
pub fn bit_get(a: u64, n: u8) -> bool {
(a >> (n & 0xF)) & 1 != 0
}
pub fn bit_set(a: u64, n: u8, value: bool) -> u64 {
let m = 1u64 << (n & 0xF);
if value { a | m } else { a & !m }
}
pub fn ptr_add(d: u32, n: u8) -> (u32, bool) {
let s = (d & ADDR_MASK) + u32::from(n);
(s & ADDR_MASK, s > ADDR_MASK)
}
pub fn ptr_sub(d: u32, n: u8) -> (u32, bool) {
let d = d & ADDR_MASK;
let n = u32::from(n);
(d.wrapping_sub(n) & ADDR_MASK, d < n)
}
pub fn p_inc(p: u8) -> (u8, bool) {
let p = p & 0xF;
((p + 1) & 0xF, p == 0xF)
}
pub fn p_dec(p: u8) -> (u8, bool) {
let p = p & 0xF;
(p.wrapping_sub(1) & 0xF, p == 0)
}
pub fn c_plus_p_plus_1(c: u64, p: u8) -> (u64, bool) {
let v = field_get(c, 0, 4) + u64::from(p & 0xF) + 1;
(field_set(c, 0, 4, v), v > u64::from(ADDR_MASK))
}
#[cfg(test)]
#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
const W: (u8, u8) = (0, 15);
const A: (u8, u8) = (0, 4);
const B: (u8, u8) = (0, 1);
const X: (u8, u8) = (0, 2);
const XS: (u8, u8) = (2, 2);
const S: (u8, u8) = (15, 15);
const M: (u8, u8) = (3, 14);
use Mode::{Dec, Hex};
#[test]
fn hex_add_fields() {
assert_eq!(add(1, 2, W.0, W.1, Hex), (3, false));
assert_eq!(add(u64::MAX, 1, W.0, W.1, Hex), (0, true));
let a = 0x1234_5678_9AB0_0001;
let b = 0xFFFF_FFFF_FFFF_FFFF;
assert_eq!(add(a, b, A.0, A.1, Hex), (0x1234_5678_9AB0_0000, true));
assert_eq!(
add(a, 0x0000_0000_0001_0000, A.0, A.1, Hex),
(0x1234_5678_9AB1_0001, false)
);
assert_eq!(add(0x1FF, 0x01, B.0, B.1, Hex), (0x100, true));
assert_eq!(add(0x12E, 0x01, B.0, B.1, Hex), (0x12F, false));
assert_eq!(add(0xA_FFF, 0x001, X.0, X.1, Hex), (0xA_000, true));
assert_eq!(add(0x123, 0x111, X.0, X.1, Hex), (0x234, false));
assert_eq!(add(0xF_FFF, 0x100, XS.0, XS.1, Hex), (0xF_0FF, true));
assert_eq!(add(0x0_5FF, 0x2FF, XS.0, XS.1, Hex), (0x0_7FF, false));
assert_eq!(add(0xF0_0000, 0x10_0000, 5, 5, Hex), (0x00_0000, true));
assert_eq!(add(0x3F_FFFF, 0x10_0000, 5, 5, Hex), (0x4F_FFFF, false));
assert_eq!(add(0x5_FFFF, 0x1, 0, 3, Hex), (0x5_0000, true));
assert_eq!(add(0x5_0FFF, 0x1, 0, 3, Hex), (0x5_1000, false));
assert_eq!(add(0xF << 60, 1 << 60, S.0, S.1, Hex), (0, true));
assert_eq!(
add(0xF_FFFF_FFFF_FFFF, 0x8008, M.0, M.1, Hex),
(0xF_FFFF_FFFF_FFFF + 0x8000, false)
);
}
#[test]
fn dec_add() {
assert_eq!(add(0x9, 0x1, 0, 0, Dec), (0x0, true));
assert_eq!(inc(0x9, 0, 0, Dec), (0x0, true));
assert_eq!(inc(0x9, 0, 0, Hex), (0xA, false));
assert_eq!(add(0xA99, 0x1, B.0, B.1, Dec), (0xA00, true));
assert_eq!(add(0x7_0999, 0x1, 0, 3, Dec), (0x7_1000, false));
assert_eq!(add(0x7_9999, 0x1, 0, 3, Dec), (0x7_0000, true));
assert_eq!(add(0x45, 0x67, B.0, B.1, Dec), (0x12, true));
assert_eq!(add(0xF, 0xF, 0, 0, Dec), (0x4, true));
assert_eq!(add(0xA, 0x0, 0, 0, Dec), (0x0, true));
}
#[test]
fn sub_and_dec() {
assert_eq!(sub(0x1000, 0x1, 0, 3, Dec), (0x0999, false));
assert_eq!(sub(0x5_0000, 0x1, 0, 3, Dec), (0x5_9999, true));
assert_eq!(sub(0x12, 0x34, B.0, B.1, Dec), (0x78, true));
assert_eq!(sub(0x5_0000, 0x1, 0, 3, Hex), (0x5_FFFF, true));
assert_eq!(sub(0x34, 0x12, B.0, B.1, Hex), (0x22, false));
assert_eq!(dec(0x0, A.0, A.1, Hex), (0xF_FFFF, true));
assert_eq!(dec(0x0, A.0, A.1, Dec), (0x9_9999, true));
assert_eq!(dec(0x10, B.0, B.1, Dec), (0x09, false));
assert_eq!(sub(0x0, 0xF, 0, 0, Dec), (0xB, true));
}
#[test]
fn neg_not() {
assert_eq!(neg(0x9_0000_1, A.0, A.1, Hex), (0x9_FFFF_F, true));
assert_eq!(neg(0xAB_0000_0, A.0, A.1, Hex), (0xAB_0000_0, false));
assert_eq!(neg(1, W.0, W.1, Hex), (u64::MAX, true));
assert_eq!(neg(0x0123, 0, 3, Dec), (0x9877, true));
assert_eq!(neg(1, W.0, W.1, Dec), (0x9999_9999_9999_9999, true));
assert_eq!(neg(0x5_0000, 0, 3, Dec), (0x5_0000, false));
assert_eq!(not(0x0, A.0, A.1, Hex), (0xF_FFFF, false));
assert_eq!(not(0x7_12, B.0, B.1, Hex), (0x7_ED, false));
assert_eq!(not(0x7_12, B.0, B.1, Dec), (0x7_87, false));
assert_eq!(not(0x0, B.0, B.1, Dec), (0x99, false));
assert_eq!(not(0xB, 0, 0, Dec), (0xE, false));
}
#[test]
fn logic_copy_exchange() {
let a = 0xFFFF_0000_FFFF_00F0;
let b = 0x0F0F_0F0F_0F0F_0F0F;
assert_eq!(and(a, b, B.0, B.1), 0xFFFF_0000_FFFF_0000);
assert_eq!(and(a, b, W.0, W.1), a & b);
assert_eq!(or(a, b, B.0, B.1), 0xFFFF_0000_FFFF_00FF);
assert_eq!(or(a, b, W.0, W.1), a | b);
assert_eq!(copy(a, b, A.0, A.1), 0xFFFF_0000_FFFF_0F0F);
assert_eq!(
exchange(a, b, XS.0, XS.1),
(0xFFFF_0000_FFFF_0FF0, 0x0F0F_0F0F_0F0F_000F)
);
assert_eq!(clear(a, S.0, S.1), 0x0FFF_0000_FFFF_00F0);
}
#[test]
fn shifts_and_sb() {
let r = 0xAB_1234_5;
assert_eq!(shl_nibble(r, A.0, A.1), (0xAB_2345_0, false));
assert_eq!(shl_nibble(0xF, W.0, W.1), (0xF0, false));
assert_eq!(shl_nibble(0xF << 60, W.0, W.1), (0, false));
assert_eq!(shr_nibble(r, A.0, A.1), (0xAB_0123_4, true));
assert_eq!(shr_nibble(0xAB_1234_0, A.0, A.1), (0xAB_0123_4, false));
assert_eq!(shr_nibble(0x350, 1, 1), (0x300, true));
assert_eq!(shr_bit(0x3, W.0, W.1), (0x1, true));
assert_eq!(shr_bit(0x2, W.0, W.1), (0x1, false));
assert_eq!(shr_bit(u64::MAX, W.0, W.1), (u64::MAX >> 1, true));
assert_eq!(shr_bit(0xF_9FF, XS.0, XS.1), (0xF_4FF, true));
assert_eq!(shr_bit(0xF_8FF, XS.0, XS.1), (0xF_4FF, false));
}
#[test]
fn rotates() {
assert_eq!(
ror_nibble(0x3A8B_B382_000A_F0FC, W.0, W.1),
(0xC3A8_BB38_2000_AF0F, true)
);
assert_eq!(ror_nibble(0x10, W.0, W.1), (0x1, false));
assert_eq!(ror_nibble(0x1, W.0, W.1), (0x1 << 60, true));
assert_eq!(rol_nibble(0xF << 60, W.0, W.1), (0xF, false));
assert_eq!(
rol_nibble(0x0123_4567_89AB_CDEF, W.0, W.1),
(0x1234_5678_9ABC_DEF0, false)
);
assert_eq!(rol_nibble(0x9_1234_5, A.0, A.1), (0x9_2345_1, false));
assert_eq!(ror_nibble(0x9_1234_5, A.0, A.1), (0x9_5123_4, true));
}
#[test]
fn comparisons() {
let a = 0xF000_0000_0000_0001;
let b = 0x0000_0000_0000_0002;
assert!(gt(a, b, W.0, W.1));
assert!(ge(a, b, W.0, W.1));
assert!(!lt(a, b, W.0, W.1));
assert!(!le(a, b, W.0, W.1));
assert!(lt(a, b, A.0, A.1));
assert!(le(a, b, A.0, A.1));
assert!(!gt(a, b, A.0, A.1));
assert!(eq(0xA_0005, 0xB_0005, 0, 3));
assert!(ne(0xA_0005, 0xB_0005, A.0, A.1));
assert!(ge(0x5, 0x5, B.0, B.1));
assert!(le(0x5, 0x5, B.0, B.1));
assert!(zero(0xF_0000, 0, 3));
assert!(nonzero(0xF_0000, A.0, A.1));
assert!(!nonzero(0, W.0, W.1));
}
#[test]
fn bits() {
assert!(bit_get(0x8000, 15));
assert!(!bit_get(0x8000, 14));
assert_eq!(bit_set(0, 15, true), 0x8000);
assert_eq!(bit_set(u64::MAX, 0, false), u64::MAX - 1);
assert_eq!(bit_set(0, 16, true), 1);
}
#[test]
fn pointer_and_p_arithmetic() {
assert_eq!(ptr_add(0xF_FFFF, 1), (0, true));
assert_eq!(ptr_add(0xF_FFF0, 16), (0, true));
assert_eq!(ptr_add(0x1_0000, 16), (0x1_0010, false));
assert_eq!(ptr_sub(0x0, 1), (0xF_FFFF, true));
assert_eq!(ptr_sub(0x5, 5), (0x0, false));
assert_eq!(ptr_sub(0x5, 16), (0xF_FFF5, true));
assert_eq!(p_inc(0xF), (0, true));
assert_eq!(p_inc(0x7), (8, false));
assert_eq!(p_dec(0), (0xF, true));
assert_eq!(p_dec(1), (0, false));
assert_eq!(c_plus_p_plus_1(0xAB_F_FFFE, 0), (0xAB_F_FFFF, false));
assert_eq!(c_plus_p_plus_1(0xAB_F_FFFE, 1), (0xAB_0_0000, true));
assert_eq!(c_plus_p_plus_1(0x0, 0xF), (0x10, false));
}
#[test]
fn add_sub_const_multi_nibble() {
assert_eq!(add_const(0x7_0009, 1, A.0, A.1), (0x7_000A, false));
assert_eq!(add_const(0x7_FFFF_F, 16, A.0, A.1), (0x7_0000_F, true));
assert_eq!(add_const(0xFF, 2, B.0, B.1), (0x01, true));
assert_eq!(sub_const(0x7_0000_1, 2, A.0, A.1), (0x7_FFFF_F, true));
assert_eq!(sub_const(0x7_0001_0, 16, A.0, A.1), (0x7_0000_0, false));
assert_eq!(add_const(u64::MAX, 1, W.0, W.1), (0, true));
}
#[test]
fn const_single_nibble_overrun_pinned() {
assert_eq!(
add_const(0xFFFF_FFFF_FFFF_FEF2, 4, XS.0, XS.1),
(0x0000_0000_0000_02F3, false)
);
assert_eq!(
add_const(0xFFFF_FFFF_FFF1_FEFF, 4, XS.0, XS.1),
(0xFFFF_FFFF_FFF2_02FF, false)
);
assert_eq!(add_const(0x123, 2, XS.0, XS.1), (0x323, false));
assert_eq!(add_const(0xF << 60, 2, S.0, S.1), (0x1 << 60 | 0x1, false));
assert_eq!(add_const(u64::MAX, 1, 7, 7), (0, true));
assert_eq!(sub_const(0x5, 1, 0, 0), (0x4, false));
assert_eq!(
sub_const(0x0000_0000_0000_0105, 2, XS.0, XS.1),
(0xFFFF_FFFF_FFFF_FF04, false)
);
assert_eq!(sub_const(0x0, 2, XS.0, XS.1), (0xFFFF_FFFF_FFFF_FEFF, true));
}
}