diff --git a/src/cpu.rs b/src/cpu.rs index 803f793..45e9d65 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -64,6 +64,8 @@ impl Processor { dst_address: usize, value: u8, ) -> Result<(), Error> { + // This lint is technically correct but we know this is the only error that can really happen here + #[allow(clippy::match_wildcard_for_single_variants)] self.memory .set(dst_address, value) .map(|_| ()) @@ -880,4 +882,150 @@ mod tests { (register::Flag::Carry, carry_flag), ); } + + #[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0x17; "no carry to register b")] + #[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0x18; "carry to register b")] + #[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0x17; "no carry to register c")] + #[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0x18; "carry to register c")] + #[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0x17; "no carry to register d")] + #[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0x18; "carry to register d")] + #[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0x17; "no carry to register e")] + #[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0x18; "carry to register e")] + #[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0x17; "no carry to register h")] + #[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0x18; "carry to register h")] + #[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0x17; "no carry to register l")] + #[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0x18; "carry to register l")] + fn test_add_with_carry_to_a_value( + opcode: u8, + initial_value: u8, + carry_bit: u8, + operand_register: register::SingleEightBit, + operand: u8, + expected_value: u8, + ) { + let mut processor = Processor::default(); + processor.registers.a = initial_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_bit); + + let data = [opcode, 0x01]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x01]); + processor.run(&ins); + + assert_eq!(expected_value, processor.registers.a); + } + + // these parameters are like impossible to read but I can't think of a nicer way to do this right now + // B register + #[test_case(0x88, 0x12, 0, register::SingleEightBit::B, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register b")] + #[test_case(0x88, 0x12, 1, register::SingleEightBit::B, 0x5, 0, 0, 0; "carry bit set, results in no flags, register b")] + #[test_case(0x88, 0x00, 0, register::SingleEightBit::B, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register b")] + #[test_case(0x88, 0x00, 1, register::SingleEightBit::B, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register b")] + #[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x1, 0, 1, 0; "no carry bit, half carry, register b")] + #[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x1, 0, 1, 0; "carry bit, half carry, register b")] + #[test_case(0x88, 0x0F, 0, register::SingleEightBit::B, 0x0F, 0, 1, 0; "no carry bit, full carry, register b")] + #[test_case(0x88, 0x0F, 1, register::SingleEightBit::B, 0x0F, 0, 1, 0; "carry bit, full carry, register b")] + #[test_case(0x88, 0xFF, 1, register::SingleEightBit::B, 0xFF, 0, 1, 1; "carry bit, both carries, register b")] + // C register + #[test_case(0x89, 0x12, 0, register::SingleEightBit::C, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register c")] + #[test_case(0x89, 0x12, 1, register::SingleEightBit::C, 0x5, 0, 0, 0; "carry bit set, results in no flags, register c")] + #[test_case(0x89, 0x00, 0, register::SingleEightBit::C, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register c")] + #[test_case(0x89, 0x00, 1, register::SingleEightBit::C, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register c")] + #[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x1, 0, 1, 0; "no carry bit, half carry, register c")] + #[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x1, 0, 1, 0; "carry bit, half carry, register c")] + #[test_case(0x89, 0x0F, 0, register::SingleEightBit::C, 0x0F, 0, 1, 0; "no carry bit, full carry, register c")] + #[test_case(0x89, 0x0F, 1, register::SingleEightBit::C, 0x0F, 0, 1, 0; "carry bit, full carry, register c")] + #[test_case(0x89, 0xFF, 1, register::SingleEightBit::C, 0xFF, 0, 1, 1; "carry bit, both carries, register c")] + // D register + #[test_case(0x8A, 0x12, 0, register::SingleEightBit::D, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register d")] + #[test_case(0x8A, 0x12, 1, register::SingleEightBit::D, 0x5, 0, 0, 0; "carry bit set, results in no flags, register d")] + #[test_case(0x8A, 0x00, 0, register::SingleEightBit::D, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register d")] + #[test_case(0x8A, 0x00, 1, register::SingleEightBit::D, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register d")] + #[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x1, 0, 1, 0; "no carry bit, half carry, register d")] + #[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x1, 0, 1, 0; "carry bit, half carry, register d")] + #[test_case(0x8A, 0x0F, 0, register::SingleEightBit::D, 0x0F, 0, 1, 0; "no carry bit, full carry, register d")] + #[test_case(0x8A, 0x0F, 1, register::SingleEightBit::D, 0x0F, 0, 1, 0; "carry bit, full carry, register d")] + #[test_case(0x8A, 0xFF, 1, register::SingleEightBit::D, 0xFF, 0, 1, 1; "carry bit, both carries, register d")] + // E register + #[test_case(0x8B, 0x12, 0, register::SingleEightBit::E, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register e")] + #[test_case(0x8B, 0x12, 1, register::SingleEightBit::E, 0x5, 0, 0, 0; "carry bit set, results in no flags, register e")] + #[test_case(0x8B, 0x00, 0, register::SingleEightBit::E, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register e")] + #[test_case(0x8B, 0x00, 1, register::SingleEightBit::E, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register e")] + #[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x1, 0, 1, 0; "no carry bit, half carry, register e")] + #[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x1, 0, 1, 0; "carry bit, half carry, register e")] + #[test_case(0x8B, 0x0F, 0, register::SingleEightBit::E, 0x0F, 0, 1, 0; "no carry bit, full carry, register e")] + #[test_case(0x8B, 0x0F, 1, register::SingleEightBit::E, 0x0F, 0, 1, 0; "carry bit, full carry, register e")] + #[test_case(0x8B, 0xFF, 1, register::SingleEightBit::E, 0xFF, 0, 1, 1; "carry bit, both carries, register e")] + // H register + #[test_case(0x8C, 0x12, 0, register::SingleEightBit::H, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register h")] + #[test_case(0x8C, 0x12, 1, register::SingleEightBit::H, 0x5, 0, 0, 0; "carry bit set, results in no flags, register h")] + #[test_case(0x8C, 0x00, 0, register::SingleEightBit::H, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register h")] + #[test_case(0x8C, 0x00, 1, register::SingleEightBit::H, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register h")] + #[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x1, 0, 1, 0; "no carry bit, half carry, register h")] + #[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x1, 0, 1, 0; "carry bit, half carry, register h")] + #[test_case(0x8C, 0x0F, 0, register::SingleEightBit::H, 0x0F, 0, 1, 0; "no carry bit, full carry, register h")] + #[test_case(0x8C, 0x0F, 1, register::SingleEightBit::H, 0x0F, 0, 1, 0; "carry bit, full carry, register h")] + #[test_case(0x8C, 0xFF, 1, register::SingleEightBit::H, 0xFF, 0, 1, 1; "carry bit, both carries, register h")] + // L register + #[test_case(0x8D, 0x12, 0, register::SingleEightBit::L, 0x5, 0, 0, 0; "no carry bit set, results in no flags, register l")] + #[test_case(0x8D, 0x12, 1, register::SingleEightBit::L, 0x5, 0, 0, 0; "carry bit set, results in no flags, register l")] + #[test_case(0x8D, 0x00, 0, register::SingleEightBit::L, 0x0, 1, 0, 0; "no carry bit set, zero operands result in zero flag set, register l")] + #[test_case(0x8D, 0x00, 1, register::SingleEightBit::L, 0x0, 0, 0, 0; "carry bit set, zero operands result in no zero flag set, register l")] + #[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x1, 0, 1, 0; "no carry bit, half carry, register l")] + #[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x1, 0, 1, 0; "carry bit, half carry, register l")] + #[test_case(0x8D, 0x0F, 0, register::SingleEightBit::L, 0x0F, 0, 1, 0; "no carry bit, full carry, register l")] + #[test_case(0x8D, 0x0F, 1, register::SingleEightBit::L, 0x0F, 0, 1, 0; "carry bit, full carry, register l")] + #[test_case(0x8D, 0xFF, 1, register::SingleEightBit::L, 0xFF, 0, 1, 1; "carry bit, both carries, register l")] + fn test_add_with_carry_to_a_flags( + opcode: u8, + initial_value: u8, + carry_bit: u8, + operand_register: register::SingleEightBit, + operand: u8, + expected_zero_flag: u8, + expected_half_carry_flag: u8, + expected_carry_flag: u8, + ) { + let mut processor = Processor::default(); + processor.registers.a = initial_value; + processor + .registers + .set_single_8bit_register(operand_register, operand); + + // Set all the register to the opposite we expect to ensure they all get set + set_opposite_of_expected_flags( + &mut processor, + ( + expected_zero_flag, + 0, + expected_half_carry_flag, + expected_carry_flag, + ), + ); + + // ...except for the carry bit, which we must set for the test + processor + .registers + .set_flag_bit(register::Flag::Carry, carry_bit); + + let data = [opcode, 0x01]; + let (ins, extra_data) = + RunnableInstruction::from_data(&data).expect("could not parse instruction"); + assert_eq!(extra_data, &[0x01]); + processor.run(&ins); + + assert_flags_eq!( + processor, + (register::Flag::Zero, expected_zero_flag), + (register::Flag::Subtract, 0), + (register::Flag::HalfCarry, expected_half_carry_flag), + (register::Flag::Carry, expected_carry_flag), + ); + } } diff --git a/src/cpu/instructions/arith8.rs b/src/cpu/instructions/arith8.rs index 26ba7d3..dfa90de 100644 --- a/src/cpu/instructions/arith8.rs +++ b/src/cpu/instructions/arith8.rs @@ -8,4 +8,5 @@ pub enum EightBitArithmeticInstruction { AddSingleRegisterToA { src: register::SingleEightBit }, AddImmediateToA { n: u8 }, AddHLAddressToA, + AddSingleRegisterToAWithCarry { src: register::SingleEightBit }, } diff --git a/src/cpu/parse/arith8/add.rs b/src/cpu/parse/arith8/add.rs index 6b94a16..63d2571 100644 --- a/src/cpu/parse/arith8/add.rs +++ b/src/cpu/parse/arith8/add.rs @@ -21,6 +21,12 @@ impl OpcodeParser for EightBitAddParser { 0x85 => build_add_register_to_a_data(register::SingleEightBit::L, data), 0x86 => build_add_hl_address_to_a_data(data), 0xC6 => build_add_immediate_to_a_data(data), + 0x88 => build_add_register_to_a_with_carry_data(register::SingleEightBit::B, data), + 0x89 => build_add_register_to_a_with_carry_data(register::SingleEightBit::C, data), + 0x8A => build_add_register_to_a_with_carry_data(register::SingleEightBit::D, data), + 0x8B => build_add_register_to_a_with_carry_data(register::SingleEightBit::E, data), + 0x8C => build_add_register_to_a_with_carry_data(register::SingleEightBit::H, data), + 0x8D => build_add_register_to_a_with_carry_data(register::SingleEightBit::L, data), _ => Err(Error::UnknownOpcode(opcode)), } } @@ -42,6 +48,25 @@ fn build_add_register_to_a_data(src: register::SingleEightBit, data: &[u8]) -> P .ok_or(Error::NoData) } +fn build_add_register_to_a_with_carry_data( + src: register::SingleEightBit, + data: &[u8], +) -> ParseResult { + data.get(1..) + .map(|remaining_data| { + ( + RunnableInstruction { + instruction: Instruction::EightBitArithmetic( + EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src }, + ), + cycles: 4, + }, + remaining_data, + ) + }) + .ok_or(Error::NoData) +} + fn build_add_hl_address_to_a_data(data: &[u8]) -> ParseResult { data.get(1..) .map(|remaining_data| { diff --git a/src/cpu/run.rs b/src/cpu/run.rs index 11b3d9e..6b68212 100644 --- a/src/cpu/run.rs +++ b/src/cpu/run.rs @@ -19,6 +19,9 @@ pub enum Error { /// An invalid address was stored in the given register and attempted to be loaded to/from #[error("invalid address stored in register ({0}): {1:X}")] InvalidRegisterAddress(register::SixteenBit, usize), + /// An invalid address was stored in the given register and attempted to be loaded to/from + #[error("invalid value stored in carry flag: {0:X}")] + InvalidCarryFlagValue(u8), #[error("an unknown error occured: {0}")] Unknown(Box), } diff --git a/src/cpu/run/arith8.rs b/src/cpu/run/arith8.rs index 57a42c4..cb886b0 100644 --- a/src/cpu/run/arith8.rs +++ b/src/cpu/run/arith8.rs @@ -1,6 +1,7 @@ use crate::cpu::{instructions::arith8::EightBitArithmeticInstruction, run::Error, Processor}; use crate::{memory, register}; +use super::arithutil; use super::{arithutil::CarryingAdd, InstructionRunner}; pub(super) struct EightBitArithmeticRunner; @@ -76,6 +77,26 @@ impl InstructionRunner for EightBitArithmeticRunn Ok(()) } + + EightBitArithmeticInstruction::AddSingleRegisterToAWithCarry { src } => { + let src_register_value = processor.registers.get_single_8bit_register(src); + + let carried_operand = arithutil::CarriedNumber::new( + src_register_value, + processor.registers.get_flag_bit(register::Flag::Carry), + ) + .map_err(|err| match err { + arithutil::Error::InvalidCarryBit(value) => Error::InvalidCarryFlagValue(value), + })?; + + let (result, half_carry, carry) = + processor.registers.a.add_with_carry(carried_operand); + + processor.registers.a = result; + set_addition_flags(processor, result, half_carry, carry); + + Ok(()) + } } } } diff --git a/src/cpu/run/arithutil.rs b/src/cpu/run/arithutil.rs index ba9d551..3005bf4 100644 --- a/src/cpu/run/arithutil.rs +++ b/src/cpu/run/arithutil.rs @@ -1,4 +1,57 @@ -//! arithutil contains utilities to generalize the implementation of arithmetic +//! arithutil contains utilities to generalize the implementation of arithmetic instructions + +use std::{marker::PhantomData, ops::Add}; + +use thiserror::Error; + +use self::upgrade::UpgradableNumber; +mod upgrade; + +#[derive(Error, Debug)] +pub enum Error { + #[error("invalid carry bit value {0}. Must be zero or one.")] + InvalidCarryBit(u8), +} + +/// `CarriedNumber` is a number combined with its carry bit, intended to be used to perform carry-based arithmetic +/// operations such as ADC. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct CarriedNumber { + num: N, + carry_bit: u8, + _phantom: PhantomData, +} + +impl CarriedNumber +where + N: Copy + Clone + PartialEq + UpgradableNumber, + V: From + From + Add, +{ + /// Create a new [`CarriedNumber`] with a raw value and its carry bit. Note that the carry bit must be 0 or 1, + /// or an [`Error::InvalidCarryBit`] will be returned + pub fn new(num: N, carry_bit: u8) -> Result { + if carry_bit > 1 { + return Err(Error::InvalidCarryBit(carry_bit)); + } + + Ok(Self { + num, + carry_bit, + _phantom: PhantomData, + }) + } + + /// Get the combination between the value and the carry bit. The return value is the "upgraded" version of `N`. + /// See [`upgrade::UpgradableNumber`] for more details + pub fn value(self) -> V { + self.num.upgrade() + V::from(self.carry_bit) + } + + /// Get the number and its carry bit directly + pub fn raw_values(self) -> (N, u8) { + (self.num, self.carry_bit) + } +} /// `CarryingAdd` describes a type that can perform addition to produce the carry flags needed for the operation /// of the gameboy @@ -44,12 +97,38 @@ impl CarryingAdd for u16 { } } +impl CarryingAdd, u8> for u8 { + fn add_with_carry(self, rhs: CarriedNumber) -> (u8, bool, bool) { + let total = rhs.value() + u16::from(self); + // Given this is adding two u8s, the largest value we can produce is 0x01FF (both operands 0xFF, plus a carry bit) + // which can never wrap, so we just take the lower byte to convert back to u8 + let [_, smallsized_total] = total.to_be_bytes(); + + ( + smallsized_total, + did_8bit_half_carry_including_carry_bit(self, rhs.num, rhs.carry_bit), + did_8bit_full_carry_including_carry_bit(self, rhs.num, rhs.carry_bit), + ) + } +} + /// `did_8_bit_half_carry` checks whether or not the given addition would have done an 8 bit half carry. // NOTE: These generics are not as generic as they could be. The Into is just a shortcut because we // only use up to u16s fn did_8bit_half_carry, R: Into>(lhs: L, rhs: R) -> bool { + did_8bit_half_carry_including_carry_bit(lhs, rhs, 0) +} + +fn did_8bit_half_carry_including_carry_bit, R: Into>( + lhs: L, + rhs: R, + carry_bit: u8, +) -> bool { + // This should be validated far before use + assert!(carry_bit <= 1, "carry bit must be zero or one"); + // https://stackoverflow.com/a/7261149/1103734 - (lhs.into() & 0xf) + (rhs.into() & 0xf) > 0xf + (lhs.into() & 0xf) + (rhs.into() & 0xf) + u16::from(carry_bit) > 0xf } /// `did_8_bit_full_carry` checks whether or not the given addition would have done an 8 bit carry. @@ -60,6 +139,18 @@ fn did_8bit_full_carry, R: Into>(lhs: L, rhs: R) -> bool { ((lhs.into() & 0xff) + (rhs.into() & 0xff)) > 0xff } +fn did_8bit_full_carry_including_carry_bit, R: Into>( + lhs: L, + rhs: R, + carry_bit: u8, +) -> bool { + // This should be validated far before use + assert!(carry_bit <= 1, "carry bit must be zero or one"); + + // https://stackoverflow.com/a/7261149/1103734 + ((lhs.into() & 0xff) + (rhs.into() & 0xff) + u16::from(carry_bit)) > 0xff +} + #[cfg(test)] mod tests { use super::*; @@ -68,6 +159,7 @@ mod tests { #[test_case(0x00, 0x00, 0, false, false; "no addition")] #[test_case(0xFF, 0x10, 15, false, true; "overflow")] #[test_case(0xC0, 0x7F, 63, false, true; "overflow 2")] + #[test_case(0xFE, 0x01, 0xFF, false, false; "hits max, no overflow")] #[test_case(0xFF, 0x01, 0, true, true; "overflow with half carry")] #[test_case(0x0A, 0x0C, 22, true, false; "half carry")] fn test_u8_carrying_add(a: u8, b: u8, expected: u8, half_carry: bool, full_carry: bool) { @@ -110,4 +202,39 @@ mod tests { assert_eq!((expected, half_carry, full_carry), a.add_with_carry(b)); } } + + #[test] + fn can_make_carried_number() { + let n = CarriedNumber::new(10, 1).expect("failed to construct CarriedNumber"); + assert_eq!((10, 1), n.raw_values()); + } + + #[test] + fn cannot_construct_carried_number_with_bad_carry_bit() { + assert!(CarriedNumber::new(10, 20).is_err()); + } + + #[test_case(0x12, 1, 0x13)] + #[test_case(0x12, 0, 0x12)] + #[test_case(0xFF, 1, 0x100)] + #[test_case(0xFF, 0, 0xFF)] + fn test_get_value_from_carried_number(value: u8, carry_bit: u8, expected_value: u16) { + let carried_number = CarriedNumber::new(value, carry_bit) + .expect("should have been able construct CarriedNumber"); + assert_eq!(expected_value, carried_number.value()); + } + + #[test_case(0x12, CarriedNumber::new(0x01, 1).unwrap(), (0x14, false, false))] + #[test_case(0x80, CarriedNumber::new(0x81, 1).unwrap(), (0x02, false, true))] + #[test_case(0xFD, CarriedNumber::new(0x01, 1).unwrap(), (0xFF, false, false))] + #[test_case(0x7F, CarriedNumber::new(0x01, 1).unwrap(), (0x81, true, false))] + #[test_case(0xFF, CarriedNumber::new(0x01, 1).unwrap(), (0x01, true, true))] + #[test_case(0xFF, CarriedNumber::new(0xFF, 1).unwrap(), (0xFF, true, true))] + fn test_add_carried_number_to_u8( + value: u8, + carried_number: CarriedNumber, + expected: (u8, bool, bool), + ) { + assert_eq!(expected, value.add_with_carry(carried_number)); + } } diff --git a/src/cpu/run/arithutil/upgrade.rs b/src/cpu/run/arithutil/upgrade.rs new file mode 100644 index 0000000..ae290f9 --- /dev/null +++ b/src/cpu/run/arithutil/upgrade.rs @@ -0,0 +1,54 @@ +/// `UpgradableNumber` represents an number that can be "upgraded" to its next largest type. +/// This is slightly different than `From`, in that it only allows you to upgrade "one step up". +/// You cannot, for instance, upgrade a `u8` to a `u64` directly. +pub trait UpgradableNumber> +where + Self: Sized, +{ + fn upgrade(self) -> U { + self.into() + } +} + +impl UpgradableNumber for u8 {} +impl UpgradableNumber for u16 {} +impl UpgradableNumber for u32 {} + +impl UpgradableNumber for i8 {} +impl UpgradableNumber for i16 {} +impl UpgradableNumber for i32 {} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! upgrade_test { + ($test_name: ident, $input_type:ty, $output_type:ty, $value:expr) => { + #[test] + fn $test_name() { + let input: $input_type = $value; + let expected: $output_type = $value.into(); + + assert_eq!(expected, input.upgrade()); + } + }; + } + + upgrade_test!(upgrade_u8_to_u16, u8, u16, 64_u8); + upgrade_test!(upgrade_u8_max_to_u16, u8, u16, u8::MAX); + + upgrade_test!(upgrade_u16_u32, u16, u32, 325_u16); + upgrade_test!(upgrade_u16_max_to_u32, u16, u32, u16::MAX); + + upgrade_test!(upgrade_u32_u32, u32, u64, 2_u32.pow(26)); + upgrade_test!(upgrade_u32_max_to_u32, u32, u64, u32::MAX); + + upgrade_test!(upgrade_i8_to_i16, i8, i16, 64_i8); + upgrade_test!(upgrade_i8_max_to_i16, i8, i16, i8::MAX); + + upgrade_test!(upgrade_i16_i32, i16, i32, 325_i16); + upgrade_test!(upgrade_i16_max_to_i32, i16, i32, i16::MAX); + + upgrade_test!(upgrade_i32_i32, i32, i64, 2_i32.pow(26)); + upgrade_test!(upgrade_i32_max_to_i32, i32, i64, i32::MAX); +}