diff --git a/src/main.rs b/src/main.rs index 81f6df9..96c4c5c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,7 +59,7 @@ fn run_bin( trace: bool, option_trace_start: Option, itm_file: Option>, -) -> Result<()> { +) -> Result { let res = Object::parse(buffer).unwrap(); let elf = match res { @@ -196,7 +196,7 @@ fn run_bin( cycles_per_sec, cycles_per_sec / 1_000_000.0, ); - Ok(()) + Ok(statistics.exit_code) } fn open_itm_file(filename: &str) -> Option> { @@ -208,8 +208,8 @@ fn open_itm_file(filename: &str) -> Option> { } } -fn run(args: &ArgMatches) -> Result<()> { - match args.subcommand() { +fn run(args: &ArgMatches) -> Result { + let exit_code = match args.subcommand() { Some(("run", run_matches)) => { let filename = run_matches .get_one::("EXECUTABLE") @@ -234,13 +234,13 @@ fn run(args: &ArgMatches) -> Result<()> { run_matches.get_flag("trace"), trace_start, itm_output, - )?; + )? } Some((_, _)) => unreachable!(), None => unreachable!(), // If all subcommands are defined above, anything else is unreachabe!() - } + }; - Ok(()) + Ok(exit_code) } fn main() { @@ -301,17 +301,23 @@ fn main() { .init() .unwrap(); - if let Err(ref e) = run(&cmd) { - error!("error: {}", e); - - for e in e.iter().skip(1) { - error!("caused by: {}", e); + let result = run(&cmd); + match result { + Ok(exit_code) => { + std::process::exit(exit_code as i32); } + Err(ref e) => { + error!("error: {}", e); - if let Some(backtrace) = e.backtrace() { - error!("backtrace: {:?}", backtrace); - } + for e in e.iter().skip(1) { + error!("caused by: {}", e); + } + + if let Some(backtrace) = e.backtrace() { + error!("backtrace: {:?}", backtrace); + } - ::std::process::exit(1); + ::std::process::exit(1); + } } } diff --git a/src/semihost.rs b/src/semihost.rs index 5e32635..f77fe78 100644 --- a/src/semihost.rs +++ b/src/semihost.rs @@ -168,12 +168,24 @@ pub fn get_semihost_func(start: Instant) -> impl FnMut(&SemihostingCommand) -> S stop, } } - SemihostingCommand::SysExitExtended { ref reason, .. } => { + SemihostingCommand::SysExitExtended { ref reason, subcode } => { // println!("sys exit {:?}", reason); + let stop = matches!( + reason, + SysExceptionReason::ADPStoppedApplicationExit | SysExceptionReason::ADPStoppedRunTimeErrorUnknown + ); + + let exit_code = if reason == &SysExceptionReason::ADPStoppedApplicationExit { + Some(*subcode) + } else { + None + }; + SemihostingResponse::SysExitExtended { success: true, - stop: reason == &SysExceptionReason::ADPStoppedApplicationExit, + stop, + exit_code } } SemihostingCommand::SysErrno { .. } => { diff --git a/test_gcc.sh b/test_gcc.sh index dc8d4fa..48f51d1 100755 --- a/test_gcc.sh +++ b/test_gcc.sh @@ -25,13 +25,13 @@ function arch_supports_cores() } declare -a archs=("armv6m" "armv7m" "armv7em") -declare -a gcc_tests=("hello_world" "pi" "instruction-test-bench") +declare -a gcc_tests=("hello_world" "instruction-test-bench" "pi") for i in "${gcc_tests[@]}" do cd tests/$i make -s clean - make -s + make cd ../.. for a in "${archs[@]}" do @@ -42,7 +42,12 @@ do for c in "${cores[@]}" do echo "./target/release/zmu-$a run tests/$i/$i-$c.elf" + # read return code and abort on failure: ./target/release/zmu-$a run tests/$i/$i-$c.elf + if [[ $? -ne 0 ]]; then + echo "Test failed" + exit $? + fi echo "" done done diff --git a/tests/instruction-test-bench/main.c b/tests/instruction-test-bench/main.c index e36e622..ab54009 100644 --- a/tests/instruction-test-bench/main.c +++ b/tests/instruction-test-bench/main.c @@ -1,38 +1,107 @@ #include #include +#include + +/* +This test bench is used to test various ARM Cortex-M instructions. +If you want to test exact instruction, use inline assembly. +If you want to test a general concept, use C code. The latter +has downside of unpredictable compiler code generation. +*/ + +/* + +Potentially useful defines lookup: + +arm-none-eabi-gcc ... -mcpu=cortex-m7 -mfloat-abi=hard -mfpu=fpv5-sp-d16 + +#define __VFP_FP__ 1 +#define __ARM_PCS_VFP 1 +#define __ARM_ARCH_PROFILE 77 +#define __ARM_ARCH_7EM__ 1 +#define __ARM_FEATURE_DSP 1 + + +*/ + +#if defined(__ARM_PCS_VFP) && defined(__VFP_FP__) + // Hard floating-point is enabled, and VFP instructions are available + #define HARD_FLOATING_POINT_ENABLED 1 +#else + // Hard floating-point is not enabled or VFP support is absent + #define HARD_FLOATING_POINT_ENABLED 0 +#endif + #if __ARM_ARCH >= 7 unsigned int bfc_0_32(int value) { - asm volatile ( + asm volatile( "bfc %[value], 0, 32" - : [value] "+r" (value)); + : [value] "+r"(value)); return value; } unsigned int bfc_0_16(int value) { - asm volatile ( + asm volatile( "bfc %[value], 0, 16" - : [value] "+r" (value)); + : [value] "+r"(value)); return value; } unsigned int bfc_15_16(int value) { - asm volatile ( + asm volatile( "bfc %[value], 15, 16" - : [value] "+r" (value)); + : [value] "+r"(value)); return value; } -#endif -// [lsb] "I" (lsb), [width] "I" (width) -int main(void) + +void bfc(void) { - -#if __ARM_ARCH >= 7 + assert(bfc_0_32(0xffffffff) == 0xffffffff); printf("bfc(0xffffffff, 0, 32) = 0x%08x\n", bfc_0_32(0xffffffff)); printf("bfc(0xffffffff, 0, 16) = 0x%08x\n", bfc_0_16(0xffffffff)); printf("bfc(0xffffffff, 15, 16) = 0x%08x\n", bfc_15_16(0xffffffff)); -#endif +} + +#endif + +#if HARD_FLOATING_POINT_ENABLED +float vabs(float value) +{ + float result; + + asm volatile( + "VABS.F32 %0, %1" + : "=t"(result) + : "t"(value)); + + return result; +} + +void floating_point(void) +{ + // Try to generate floating-point data-processing instructions + // VABS, VADD, VCMP, VCVT, VDIV, VFMA, VFNMA, VMAXNM + // VMLA, VMOV, VMOV, VMUL, VNEG, VNMLA, VRINTA, VRINTZ + // VSEL, VSQRT, VSUB + + assert(vabs(-1.0f) == 1.0f); + assert(vabs(-42.0f) == 42.0f); + assert(vabs(0.0f) == 0.0f); + assert(vabs(1.0f) == 1.0f); +} +#endif +int main(void) +{ + +#if __ARM_ARCH >= 7 + bfc(); +#endif + +#if HARD_FLOATING_POINT_ENABLED + floating_point(); +#endif } void SystemInit(void) @@ -48,5 +117,11 @@ void _start(void) exit(0); } -__attribute__((used)) -void _fini(void) { } \ No newline at end of file +__attribute__((used)) void _fini(void) {} + + +void __assert_func( const char *filename, int line, const char *assert_func, const char *expr ) +{ + printf("assert_failed: %s:%d\n", filename, line); + exit(1); +} \ No newline at end of file diff --git a/zmu.code-workspace b/zmu.code-workspace index 30506bd..c113a0b 100644 --- a/zmu.code-workspace +++ b/zmu.code-workspace @@ -10,6 +10,9 @@ "settings": { "rust-analyzer.cargo.features": [ "armv7em generic-device" - ] + ], + "files.associations": { + "assert.h": "c" + } } } \ No newline at end of file diff --git a/zmu_cortex_m/build.rs b/zmu_cortex_m/build.rs index 5407625..ac11a4c 100644 --- a/zmu_cortex_m/build.rs +++ b/zmu_cortex_m/build.rs @@ -273,10 +273,10 @@ fn main() -> Result<(), Box> { ("1110100..1.11111................", "LDRD_lit_t1"), ("1110100..1.1....................", "LDRD_imm_t1"), ("1110100..1.0....................", "STRD_imm_t1"), - //("111011101.110000....101.11.0....": "VABS_t1"), + ("111011101.110000....101.11.0....", "VABS_t1"), //("111011100.11........101..0.0....": "VADD_t1"), - //("111011101.110100....101..1.0....": "VCMP_t1"), - //("111011101.110101....101..1.0....": "VCMP_t2"), + ("111011101.110100....101..1.0....", "VCMP_t1"), + ("111011101.110101....101..1.0....", "VCMP_t2"), //("111111101.1111......101..1.0....": "VCVT_t1"), //("111011101.111.1.....101..1.0....": "VCVT_fx_t1"), //("111011101.110111....101.11.0....": "VCVT_ds_t1"), @@ -284,6 +284,7 @@ fn main() -> Result<(), Box> { //("111011101.00........101..0.0....": "VDIV"), //("111011101.10........101....0....": "VFMAS"), //("111011101.01........101....0....": "VFNMAS"), + ("1110111011110001....101000010000", "VMRS"), ("1110110....0........1011.......0", "VSTM_t1"), ("1110110....0........1010........", "VSTM_t2"), ("11101101..01........1011........", "VLDR_t1"), diff --git a/zmu_cortex_m/src/core/exception.rs b/zmu_cortex_m/src/core/exception.rs index a01c428..c2e9952 100644 --- a/zmu_cortex_m/src/core/exception.rs +++ b/zmu_cortex_m/src/core/exception.rs @@ -504,7 +504,7 @@ impl ExceptionHandling for Processor { && nested_activation == 1 // deactivate() reduced one && self.scr.get_bit(1) { - self.state.set_bit(1, true); // sleeping = true + self.sleeping = true; } Ok(()) @@ -516,7 +516,7 @@ impl ExceptionHandling for Processor { #[inline(always)] fn check_exceptions(&mut self) { if let Some(exception) = self.get_pending_exception() { - self.state.set_bit(1, false); // sleeping == false + self.sleeping = false; self.clear_pending_exception(exception); let pc = self.get_pc(); // TODO: handle failure to enter exception diff --git a/zmu_cortex_m/src/core/fpregister.rs b/zmu_cortex_m/src/core/fpregister.rs new file mode 100644 index 0000000..e9cc9c9 --- /dev/null +++ b/zmu_cortex_m/src/core/fpregister.rs @@ -0,0 +1,79 @@ +//! +//! Cortex Floating Point extension register operations +//! + +use crate::core::bits::Bits; + +/// Trait for accessing Floating Point registers +pub trait Fpscr { + /// + /// Get "N"egative flag value + /// + fn get_n(&self) -> bool; + + /// + /// Set "N"egative flag value + /// + fn set_n(&mut self, n: bool); + + /// + /// Get "Z"ero flag value + /// + fn get_z(&self) -> bool; + /// + /// Set "Z"ero flag value + /// + fn set_z(&mut self, z: bool); + + /// + /// Get "C"arry flag value + /// + fn get_c(&self) -> bool; + /// + /// Set "C"arry flag value + /// + fn set_c(&mut self, c: bool); + + /// + /// Get Overflow flag value + /// + fn get_v(&self) -> bool; + /// + /// Set Overflow flag value + /// + fn set_v(&mut self, v: bool); +} + +impl Fpscr for u32 { + fn get_n(&self) -> bool { + self.get_bit(31) + } + + fn set_n(&mut self, n: bool) { + self.set_bit(31, n); + } + + fn get_z(&self) -> bool { + self.get_bit(30) + } + + fn set_z(&mut self, z: bool) { + self.set_bit(30, z) + } + + fn get_c(&self) -> bool { + self.get_bit(29) + } + + fn set_c(&mut self, c: bool) { + self.set_bit(29, c); + } + + fn get_v(&self) -> bool { + self.get_bit(28) + } + + fn set_v(&mut self, v: bool) { + self.set_bit(28, v); + } +} diff --git a/zmu_cortex_m/src/core/instruction.rs b/zmu_cortex_m/src/core/instruction.rs index 7b478e8..8cd465d 100644 --- a/zmu_cortex_m/src/core/instruction.rs +++ b/zmu_cortex_m/src/core/instruction.rs @@ -6,6 +6,8 @@ use crate::core::condition::Condition; use crate::core::register::{DoubleReg, ExtensionReg, Reg, SingleReg}; use crate::core::thumb::ThumbCode; use enum_set::EnumSet; +use std::fmt::Display; +use std::fmt::Formatter; #[derive(Debug, PartialEq, Copy, Clone)] /// @@ -356,6 +358,24 @@ pub struct VMovRegParamsf32 { pub sm: SingleReg, } +#[allow(missing_docs)] +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct VCmpParamsf32 { + pub sd: SingleReg, + pub sm: SingleReg, + pub quiet_nan_exc: bool, + pub with_zero: bool, +} + +#[allow(missing_docs)] +#[derive(PartialEq, Debug, Copy, Clone)] +pub struct VCmpParamsf64 { + pub dd: DoubleReg, + pub dm: DoubleReg, + pub quiet_nan_exc: bool, + pub with_zero: bool, +} + #[allow(missing_docs)] #[derive(PartialEq, Debug, Copy, Clone)] pub struct VMovRegParamsf64 { @@ -492,6 +512,21 @@ pub struct BfiParams { pub width: usize, } +#[allow(missing_docs)] +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum VMRSTarget { + APSRNZCV, + Register(Reg), +} +impl Display for VMRSTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + VMRSTarget::APSRNZCV => write!(f, "APSR_nzcv"), + VMRSTarget::Register(r) => write!(f, "{:?}", r), + } + } +} + #[allow(non_camel_case_types, missing_docs)] #[derive(PartialEq, Debug, Copy, Clone)] /// @@ -773,6 +808,10 @@ pub enum Instruction { // Group: Multiply instructions // // -------------------------------------------- + + // + // Subgroup: General multiply instructions + // /// Multipy and Accumulate MLA { params: Reg4NoSetFlagsParams, @@ -786,11 +825,10 @@ pub enum Instruction { params: Reg3Params, thumb32: bool, }, - // -------------------------------------------- + // - // Group: Signed multiply instructions (ArmV7-m) + // Subgroup: Signed multiply instructions (ArmV7-m) // - // -------------------------------------------- /// Signed Multiply and Accumulate (Long) SMLAL { params: Reg643232Params, @@ -800,22 +838,9 @@ pub enum Instruction { params: Reg643232Params, }, - // -------------------------------------------- - // - // Group: Unsigned Multiply instructions (ARMv7-M base architecture) - // - // -------------------------------------------- - UMLAL { - params: Reg643232Params, - }, - UMULL { - params: Reg643232Params, - }, - // -------------------------------------------- // - // Group: Signed Multiply instructions (ARMv7-M DSP extension) + // Subgroup: Signed Multiply instructions (ARMv7-M DSP extension) // - // -------------------------------------------- /// Signed multiply: halfwords /// variants: SMULTT, SMULBB, SMULTB, SMULBT SMUL { @@ -828,37 +853,85 @@ pub enum Instruction { }, //SMLAL second variant? - //SMLALD - //SMLAW - //SMLSD - //SMLSLD - //SMMLA - //SMMLS - //SMMUL - //SMUAD + + // Signed Multiply Accumulate Long, halfwords + // SMLABB, SMLABT, SMLATT, SMLATB + + // Signed Multiply Accumulate Dual + // SMLAD, SMLADX + + // Signed Multiply Accumulate Long, halfwords + // SMLALBB, SMLALBT, SMLALTT, SMLALTB + + // Signed Multiply Accumulate Long Dual + // SMLALD, SMLALDX + + // Signed Multiply Accumulate, word by halfword + // SMLAWB, SMLAWT + + // Signed Multiply Subtract Dual + // SMLSD, SMLSDX + + // Signed Multiply Subtract Long Dual + // SMLSLD, SMLSLDX + + // Signed most significant word Multiply Accumulate + // SMMLA, SMMLAR + + // Signed most significant word Multiply Subtract + // SMMLS, SMMLSR + + // Signed most significant Word Multiply + // SMMUL, SMMULR + + // Signed Dual Multiply Add + // SMUAD, SMUADX + + // Signed Multiply, halfwords + // SMULBB, SMULBT, SMULTB, SMULTT + + // Signed Multiply, word by halfword + // SMULWB, SMULWT + + // Signed Dual Multiply Subtract + // SMUSD, SMUSDX + + // + // Subgroup: Unsigned Multiply instructions (ARMv7-M base architecture) + // + UMLAL { + params: Reg643232Params, + }, + UMULL { + params: Reg643232Params, + }, + + // + // Subgroup: Unsigned multiply instructions (Armv7-M DSP extension) + // + // UMAAL // -------------------------------------------- // - // Group: Saturating instructions (ARMv7-M base arch) + // Group: Saturating instructions // // -------------------------------------------- + // + // Subgroup: Saturating instructions (ARMv7-M base arch) + // //SSAT //USAT - // -------------------------------------------- // - // Group: Unsigned Saturating instructions (ARMv7-M DSP extensions) + // Subgroup: Halfword saturating instructions, (ARMv7-M DSP extensions) // - // -------------------------------------------- //USAT16 //SSAT16 - // -------------------------------------------- // - // Group: Saturating add/sub (ARMv7-M DSP extensions) + // Subgroup: Saturating addition and subtraction instructions, Armv7-M DSP extension // - // -------------------------------------------- //QADD //QSUB //QDADD @@ -869,6 +942,10 @@ pub enum Instruction { // Group: Packing and unpacking instructions // // -------------------------------------------- + + // + // Subgroup: Packing and unpacking instructions (ARMv7-M base arch) + // /// Signed Extend Byte SXTB { params: Reg2UsizeParams, @@ -889,11 +966,10 @@ pub enum Instruction { params: Reg2UsizeParams, thumb32: bool, }, - // -------------------------------------------- + // - // Group: Packing and unpacking instructions (DSP extensions) + // Subgroup: Packing and unpacking instructions, Armv7-M DSP extension // - // -------------------------------------------- //PKHBT, PKHTB //SXTAB //SXTAB16 @@ -940,6 +1016,10 @@ pub enum Instruction { // Group: Miscellaneous data-processing instructions // // -------------------------------------------- + + // + // Subgroup: Miscellaneous data-processing instructions, Armv7-M base architecture + // /// Bit Field Clear BFC { params: BfcParams, @@ -984,11 +1064,9 @@ pub enum Instruction { params: BfxParams, }, - // -------------------------------------------- // - // Group: Miscellaneous data-processing instructions (DSP extensions) + // Subgroup: Miscellaneous data-processing instructions, Armv7-M DSP extension // - // -------------------------------------------- /// Select bytes using GE flags SEL { params: Reg3NoSetFlagsParams, @@ -1020,7 +1098,7 @@ pub enum Instruction { // -------------------------------------------- // - // Group: Load and Store instructions + // Group: Load and Store instructions // // -------------------------------------------- LDR_reg { @@ -1135,7 +1213,7 @@ pub enum Instruction { // -------------------------------------------- // - // Group: Load and Store Multiple instructions + // Group: Load and Store Multiple instructions // // -------------------------------------------- LDM { @@ -1160,7 +1238,7 @@ pub enum Instruction { // -------------------------------------------- // - // Group: Miscellaneous + // Group: Miscellaneous instructions // // -------------------------------------------- //CLREX @@ -1336,17 +1414,28 @@ pub enum Instruction { VMOV_cr2_dp { params: VMovCr2DpParams, }, - //VMRS - //VMRS + VMRS { + rt: VMRSTarget, + }, // -------------------------------------------- // // Group: Floating-point data-processing instructions // // -------------------------------------------- - // VABS + VABS_f32 { + params: VMovRegParamsf32, + }, + VABS_f64 { + params: VMovRegParamsf64, + }, //VADD - //VCMP + VCMP_f32 { + params: VCmpParamsf32, + }, + VCMP_f64 { + params: VCmpParamsf64, + }, //VCVT //VDIV //VFMA @@ -2411,6 +2500,29 @@ impl fmt::Display for Instruction { } ) } + Self::VABS_f32 { params } => write!(f, "vabs.f32 {}, {}", params.sd, params.sm), + Self::VABS_f64 { params } => write!(f, "vabs.f64 {}, {}", params.dd, params.dm), + Self::VMRS { rt } => write!(f, "vmrs {}, fpscr", rt), + Self::VCMP_f32 { params } => write!( + f, + "vcmp.f32 {}, {}", + params.sd, + if params.with_zero { + "#0".to_string() + } else { + format!("{}", params.sm) + } + ), + Self::VCMP_f64 { params } => write!( + f, + "vcmp.f64 {}, {}", + params.dd, + if params.with_zero { + "#0".to_string() + } else { + format!("{}", params.dm) + } + ), Self::WFE { .. } => write!(f, "wfe"), Self::WFI { .. } => write!(f, "wfi"), @@ -2698,9 +2810,11 @@ pub fn instruction_size(instruction: &Instruction) -> usize { Instruction::UXTB { thumb32, .. } => isize_t(*thumb32), Instruction::UXTH { thumb32, .. } => isize_t(*thumb32), - //VABS + Instruction::VABS_f32 { params } => 4, + Instruction::VABS_f64 { params } => 4, //VADD - //VCMP + Instruction::VCMP_f32 { params } => 4, + Instruction::VCMP_f64 { params } => 4, //VCVTX //VCVT //VCVTB @@ -2725,7 +2839,7 @@ pub fn instruction_size(instruction: &Instruction) -> usize { Instruction::VMOV_cr2_sp2 { .. } => 4, Instruction::VMOV_cr2_dp { .. } => 4, - //VMRS + Instruction::VMRS { .. } => 4, //VMSR //VMUL //VNEG diff --git a/zmu_cortex_m/src/core/mod.rs b/zmu_cortex_m/src/core/mod.rs index 590f372..d133fb9 100644 --- a/zmu_cortex_m/src/core/mod.rs +++ b/zmu_cortex_m/src/core/mod.rs @@ -13,3 +13,6 @@ pub mod operation; pub mod register; pub mod reset; pub mod thumb; + +// FP extension registers +pub mod fpregister; diff --git a/zmu_cortex_m/src/core/register.rs b/zmu_cortex_m/src/core/register.rs index 0b800b3..dec463c 100644 --- a/zmu_cortex_m/src/core/register.rs +++ b/zmu_cortex_m/src/core/register.rs @@ -335,15 +335,26 @@ pub trait Apsr { /// fn set_n(&mut self, result: u32); + /// + /// Set N bit + /// + fn set_n_bit(&mut self, n: bool); + /// /// Get "Z"ero flag value /// fn get_z(&self) -> bool; + /// /// Set "Z"ero flag value /// fn set_z(&mut self, result: u32); + /// + /// Set Z bit + /// + fn set_z_bit(&mut self, z: bool); + /// /// Get "C"arry flag value /// @@ -438,6 +449,10 @@ impl Apsr for PSR { self.value |= result & 0x8000_0000; } + fn set_n_bit(&mut self, n: bool) { + self.value.set_bit(31, n); + } + fn get_z(&self) -> bool { self.value.get_bit(30) } @@ -448,6 +463,9 @@ impl Apsr for PSR { self.value &= 0x4000_0000 ^ 0xffff_ffff; } } + fn set_z_bit(&mut self, z: bool) { + self.value.set_bit(30, z); + } fn get_c(&self) -> bool { self.value.get_bit(29) diff --git a/zmu_cortex_m/src/decoder/decoder_tests.rs b/zmu_cortex_m/src/decoder/decoder_tests.rs index 999a6ef..7ffcb78 100644 --- a/zmu_cortex_m/src/decoder/decoder_tests.rs +++ b/zmu_cortex_m/src/decoder/decoder_tests.rs @@ -1,12 +1,13 @@ use crate::core::instruction::{ - AddressingMode, BfcParams, BfiParams, CondBranchParams, Imm32Carry, MovtParams, ParamsRegImm32, - Reg2DoubleParams, Reg2FullParams, Reg2ImmCarryParams, Reg2ImmParams, Reg2Params, - Reg2RdRmParams, Reg2RnRmParams, Reg2RtRnImm32Params, Reg2ShiftNParams, + AddressingMode, BfcParams, BfiParams, BfxParams, CondBranchParams, Imm32Carry, MovtParams, + ParamsRegImm32, Reg2DoubleParams, Reg2FullParams, Reg2ImmCarryParams, Reg2ImmParams, + Reg2Params, Reg2RdRmParams, Reg2RnRmParams, Reg2RtRnImm32Params, Reg2ShiftNParams, Reg2ShiftNoSetFlagsParams, Reg2ShiftParams, Reg2UsizeParams, Reg3FullParams, Reg3HighParams, Reg3NoSetFlagsParams, Reg3Params, Reg3RdRtRnImm32Params, Reg3ShiftParams, Reg3UsizeParams, Reg4HighParams, Reg4NoSetFlagsParams, Reg643232Params, RegImm32AddParams, - RegImmCarryNoSetFlagsParams, RegImmCarryParams, RegImmParams, SRType, SetFlags, BfxParams, - VMovCr2DpParams, VMovCrSpParams, VMovImmParams64, VMovRegParamsf32, + RegImmCarryNoSetFlagsParams, RegImmCarryParams, RegImmParams, SRType, SetFlags, VCmpParamsf32, + VMRSTarget, VMovCr2DpParams, VMovCrSpParams, VMovImmParams32, VMovImmParams64, + VMovRegParamsf32, }; use crate::core::instruction::VLoadAndStoreParams; @@ -2903,6 +2904,24 @@ fn test_decode_vldr() { ); } +#[test] +fn test_decode_vldr_2() { + // eddf 7a23 vldr s15, [pc, #140] @ 180 + assert_eq!( + decode_32(0xeddf_7a23), + Instruction::VLDR { + params: VLoadAndStoreParams { + dd: ExtensionReg::Single { + reg: SingleReg::S15 + }, + rn: Reg::PC, + add: true, + imm32: 140, + } + } + ); +} + #[test] fn test_decode_vstr() { //250: ed8d 7b12 vstr d7, [sp, #72] ; 0x48 @@ -3051,6 +3070,21 @@ fn test_decode_vmov_imm() { ); } +#[test] +fn test_decode_vmov_imm_2() { + //eeff 7a00 vmov.f32 s15, #240 @ 0xbf800000 -1.0 + + assert_eq!( + decode_32(0xeeff_7a00), + Instruction::VMOV_imm_32 { + params: VMovImmParams32 { + sd: SingleReg::S15, + imm32: 0xbf800000 // -1.0 + } + } + ); +} + #[test] fn test_decode_vstm_32_ia() { //ecee 7a01 vstmia lr!, {s15} @@ -3069,3 +3103,47 @@ fn test_decode_vstm_32_ia() { } } } + +#[test] +fn test_decode_vabs_32() { + //eef0 7ae7 vabs.f32 s15, s15 + + assert_eq!( + decode_32(0xeef07ae7), + Instruction::VABS_f32 { + params: VMovRegParamsf32 { + sd: SingleReg::S15, + sm: SingleReg::S15, + } + } + ); +} + +#[test] +fn test_decode_vcmp_f32() { + //eef4 7a47 vcmp.f32 s15, s14 + + assert_eq!( + decode_32(0xeef47a47), + Instruction::VCMP_f32 { + params: VCmpParamsf32 { + sd: SingleReg::S15, + sm: SingleReg::S14, + with_zero: false, + quiet_nan_exc: false, + } + } + ); +} + +#[test] +fn test_decode_vmrs() { + //0xeef1 fa10 vmrs APSR_nzcv, fpscr + + assert_eq!( + decode_32(0xeef1fa10), + Instruction::VMRS { + rt: VMRSTarget::APSRNZCV + } + ); +} diff --git a/zmu_cortex_m/src/decoder/mod.rs b/zmu_cortex_m/src/decoder/mod.rs index 8b0f998..b9ee518 100644 --- a/zmu_cortex_m/src/decoder/mod.rs +++ b/zmu_cortex_m/src/decoder/mod.rs @@ -115,12 +115,15 @@ mod umull; mod uxt; mod uxtab; +mod vabs; +mod vcmp; mod vldr; mod vmov; +mod vmrs; mod vpop; mod vpush; -mod vstr; mod vstm; +mod vstr; use { crate::decoder::str::{ @@ -258,6 +261,8 @@ use { use crate::core::thumb::ThumbCode; use crate::Processor; use { + vabs::decode_VABS_t1, + vcmp::{decode_VCMP_t1, decode_VCMP_t2}, vldr::{decode_VLDR_t1, decode_VLDR_t2}, vmov::decode_VMOV_cr2_dp, vmov::decode_VMOV_cr2_sp2, @@ -266,12 +271,13 @@ use { vmov::decode_VMOV_imm, vmov::decode_VMOV_reg, vmov::decode_VMOV_scalar_cr, + vmrs::decode_VMRS, vpop::decode_VPOP_t1, vpop::decode_VPOP_t2, vpush::decode_VPUSH_t1, vpush::decode_VPUSH_t2, - vstr::{decode_VSTR_t1, decode_VSTR_t2}, vstm::{decode_VSTM_t1, decode_VSTM_t2}, + vstr::{decode_VSTR_t1, decode_VSTR_t2}, }; /// diff --git a/zmu_cortex_m/src/decoder/vabs.rs b/zmu_cortex_m/src/decoder/vabs.rs new file mode 100644 index 0000000..494c013 --- /dev/null +++ b/zmu_cortex_m/src/decoder/vabs.rs @@ -0,0 +1,34 @@ +use crate::core::{ + bits::Bits, + instruction::{ + Instruction, VMovRegParamsf32, VMovRegParamsf64, + }, + register::{DoubleReg, SingleReg}, +}; + +#[allow(non_snake_case)] +#[inline(always)] +pub fn decode_VABS_t1(opcode: u32) -> Instruction { + let sz = opcode.get_bit(8); + + let D = u8::from(opcode.get_bit(22)); + let vd = opcode.get_bits(12..16) as u8; + let vm = opcode.get_bits(0..4) as u8; + let M = u8::from(opcode.get_bit(5)); + + if sz { + Instruction::VABS_f64 { + params: VMovRegParamsf64 { + dd: DoubleReg::from(D << 4 | vd), + dm: DoubleReg::from(M << 4 | vm), + }, + } + } else { + Instruction::VABS_f32 { + params: VMovRegParamsf32 { + sd: SingleReg::from(vd << 1 | D), + sm: SingleReg::from(vm << 1 | M), + }, + } + } +} diff --git a/zmu_cortex_m/src/decoder/vcmp.rs b/zmu_cortex_m/src/decoder/vcmp.rs new file mode 100644 index 0000000..52e7c1e --- /dev/null +++ b/zmu_cortex_m/src/decoder/vcmp.rs @@ -0,0 +1,75 @@ +use crate::core::{ + bits::Bits, + instruction::{Instruction, VCmpParamsf32, VCmpParamsf64}, + register::{DoubleReg, SingleReg}, +}; + +#[allow(non_snake_case)] +#[inline(always)] +pub fn decode_VCMP_t1(opcode: u32) -> Instruction { + let sz = opcode.get_bit(8); + + let D = u8::from(opcode.get_bit(22)); + let vd = opcode.get_bits(12..16) as u8; + let vm = opcode.get_bits(0..4) as u8; + let M = u8::from(opcode.get_bit(5)); + let e = u8::from(opcode.get_bit(7)); + + let quiet_nan_exc = e == 1; + let with_zero = false; + + if sz { + Instruction::VCMP_f64 { + params: VCmpParamsf64 { + dd: DoubleReg::from(D << 4 | vd), + dm: DoubleReg::from(M << 4 | vm), + quiet_nan_exc, + with_zero, + }, + } + } else { + Instruction::VCMP_f32 { + params: VCmpParamsf32 { + sd: SingleReg::from(vd << 1 | D), + sm: SingleReg::from(vm << 1 | M), + quiet_nan_exc, + with_zero, + }, + } + } +} + +#[allow(non_snake_case)] +#[inline(always)] +pub fn decode_VCMP_t2(opcode: u32) -> Instruction { + let sz = opcode.get_bit(8); + + let D = u8::from(opcode.get_bit(22)); + let vd = opcode.get_bits(12..16) as u8; + let vm = opcode.get_bits(0..4) as u8; + let M = u8::from(opcode.get_bit(5)); + let e = u8::from(opcode.get_bit(7)); + + let quiet_nan_exc = e == 1; + let with_zero = true; + + if sz { + Instruction::VCMP_f64 { + params: VCmpParamsf64 { + dd: DoubleReg::from(D << 4 | vd), + dm: DoubleReg::from(M << 4 | vm), + quiet_nan_exc, + with_zero, + }, + } + } else { + Instruction::VCMP_f32 { + params: VCmpParamsf32 { + sd: SingleReg::from(vd << 1 | D), + sm: SingleReg::from(vm << 1 | M), + quiet_nan_exc, + with_zero, + }, + } + } +} diff --git a/zmu_cortex_m/src/decoder/vldr.rs b/zmu_cortex_m/src/decoder/vldr.rs index 1eaca7e..38394eb 100644 --- a/zmu_cortex_m/src/decoder/vldr.rs +++ b/zmu_cortex_m/src/decoder/vldr.rs @@ -5,14 +5,15 @@ use crate::core::register::{DoubleReg, ExtensionReg, Reg, SingleReg}; #[allow(non_snake_case)] #[inline(always)] pub fn decode_VLDR_t1(opcode: u32) -> Instruction { + let vd = opcode.get_bits(12..16) as u8; + let D = u8::from(opcode.get_bit(22)); + let rn = opcode.get_bits(16..20) as u8; Instruction::VLDR { params: VLoadAndStoreParams { dd: ExtensionReg::Double { - reg: DoubleReg::from( - opcode.get_bits(12..16) as u8 + ((u8::from(opcode.get_bit(22))) << 4), - ), + reg: DoubleReg::from(D << 4 | vd), }, - rn: Reg::from(opcode.get_bits(16..20) as u8), + rn: Reg::from(rn), imm32: opcode.get_bits(0..8) << 2, add: opcode.get_bit(23), }, @@ -22,14 +23,15 @@ pub fn decode_VLDR_t1(opcode: u32) -> Instruction { #[allow(non_snake_case)] #[inline(always)] pub fn decode_VLDR_t2(opcode: u32) -> Instruction { + let vd = opcode.get_bits(12..16) as u8; + let D = u8::from(opcode.get_bit(22)); + let rn = opcode.get_bits(16..20) as u8; Instruction::VLDR { params: VLoadAndStoreParams { dd: ExtensionReg::Single { - reg: SingleReg::from( - opcode.get_bits(12..16) as u8 + ((u8::from(opcode.get_bit(22))) << 4), - ), + reg: SingleReg::from(vd << 1 | D), }, - rn: Reg::from(opcode.get_bits(16..20) as u8), + rn: Reg::from(rn), imm32: opcode.get_bits(0..8) << 2, add: opcode.get_bit(23), }, diff --git a/zmu_cortex_m/src/decoder/vmov.rs b/zmu_cortex_m/src/decoder/vmov.rs index ddf31f6..f403935 100644 --- a/zmu_cortex_m/src/decoder/vmov.rs +++ b/zmu_cortex_m/src/decoder/vmov.rs @@ -89,7 +89,7 @@ pub fn decode_VMOV_imm(opcode: u32) -> Instruction { } else { Instruction::VMOV_imm_32 { params: VMovImmParams32 { - sd: SingleReg::from(vd << 4 | D), + sd: SingleReg::from(vd << 1 | D), imm32: vfpexpand_imm32(imm8), }, } diff --git a/zmu_cortex_m/src/decoder/vmrs.rs b/zmu_cortex_m/src/decoder/vmrs.rs new file mode 100644 index 0000000..c487441 --- /dev/null +++ b/zmu_cortex_m/src/decoder/vmrs.rs @@ -0,0 +1,19 @@ +use crate::core::{ + bits::Bits, + instruction::{Instruction, VMRSTarget}, + register::Reg, +}; + +#[allow(non_snake_case)] +#[inline(always)] +pub fn decode_VMRS(opcode: u32) -> Instruction { + let target = opcode.get_bits(12..16) as u8; + + let rt = if target == 15 { + VMRSTarget::APSRNZCV + } else { + VMRSTarget::Register(Reg::from(target)) + }; + + Instruction::VMRS { rt } +} diff --git a/zmu_cortex_m/src/executor/fp_data_processing.rs b/zmu_cortex_m/src/executor/fp_data_processing.rs index e69de29..54f2889 100644 --- a/zmu_cortex_m/src/executor/fp_data_processing.rs +++ b/zmu_cortex_m/src/executor/fp_data_processing.rs @@ -0,0 +1,190 @@ +use crate::core::fpregister::Fpscr; +use crate::core::instruction::{VCmpParamsf32, VCmpParamsf64, VMovRegParamsf32, VMovRegParamsf64}; +use crate::Processor; + +use crate::executor::ExecuteSuccess; + +use super::fp_generic::{fpabs_32, FloatingPointInternalOperations}; +use super::ExecuteResult; +use crate::core::register::ExtensionRegOperations; +use crate::executor::ExecutorHelper; + +pub trait IsaFloatingPointDataProcessing { + fn exec_vabs_f32(&mut self, params: &VMovRegParamsf32) -> ExecuteResult; + fn exec_vabs_f64(&mut self, params: &VMovRegParamsf64) -> ExecuteResult; + + fn exec_vcmp_f32(&mut self, params: &VCmpParamsf32) -> ExecuteResult; + fn exec_vcmp_f64(&mut self, params: &VCmpParamsf64) -> ExecuteResult; +} + +impl IsaFloatingPointDataProcessing for Processor { + fn exec_vabs_f32(&mut self, params: &VMovRegParamsf32) -> ExecuteResult { + if self.condition_passed() { + //self.execute_fp_check(); + let value = self.get_sr(params.sm); + let result = fpabs_32(value); + self.set_sr(params.sd, result); + return Ok(ExecuteSuccess::Taken { cycles: 1 }); + } + Ok(ExecuteSuccess::NotTaken) + } + + fn exec_vabs_f64(&mut self, params: &VMovRegParamsf64) -> ExecuteResult { + if self.condition_passed() { + //self.execute_fp_check(); + let (lower, upper) = self.get_dr(params.dm); + let upper_modified = fpabs_32(upper); + self.set_dr(params.dd, lower, upper_modified); + return Ok(ExecuteSuccess::Taken { cycles: 1 }); + } + Ok(ExecuteSuccess::NotTaken) + } + + fn exec_vcmp_f32(&mut self, params: &VCmpParamsf32) -> ExecuteResult { + if self.condition_passed() { + //self.execute_fp_check(); + let op32 = if params.with_zero { + 0 + } else { + self.get_sr(params.sm) + }; + let op1 = self.get_sr(params.sd); + let (n, z, c, v) = self.fp_compare_f32(op1, op32, params.quiet_nan_exc, true); + self.fpscr.set_n(n); + self.fpscr.set_z(z); + self.fpscr.set_c(c); + self.fpscr.set_v(v); + } + Ok(ExecuteSuccess::NotTaken) + } + fn exec_vcmp_f64(&mut self, params: &VCmpParamsf64) -> ExecuteResult { + if self.condition_passed() { + //self.execute_fp_check(); + let op64 = if params.with_zero { + 0u64 + } else { + let (lower, upper) = self.get_dr(params.dm); + (upper as u64) << 32 | lower as u64 + }; + let op1_src = self.get_dr(params.dd); + let op1 = (op1_src.1 as u64) << 32 | op1_src.0 as u64; + let (n, z, c, v) = self.fp_compare_f64(op1, op64, params.quiet_nan_exc, true); + self.fpscr.set_n(n); + self.fpscr.set_z(z); + self.fpscr.set_c(c); + self.fpscr.set_v(v); + } + Ok(ExecuteSuccess::NotTaken) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::instruction::{ + VCmpParamsf32, VCmpParamsf64, VMovRegParamsf32, VMovRegParamsf64, + }; + use crate::core::register::{DoubleReg, SingleReg}; + use crate::Processor; + + #[test] + fn test_exec_vabs_f32() { + let mut processor = Processor::new(); + let params = VMovRegParamsf32 { + sd: SingleReg::S0, + sm: SingleReg::S1, + }; + processor.set_sr(SingleReg::S1, 0x80000000); // -0.0 + processor.exec_vabs_f32(¶ms).unwrap(); + assert_eq!(processor.get_sr(SingleReg::S0), 0x00000000); // 0.0 + + processor.set_sr(SingleReg::S1, 0xFFFFFFFF); // -1.0 + processor.exec_vabs_f32(¶ms).unwrap(); + assert_eq!(processor.get_sr(SingleReg::S0), 0x7FFFFFFF); // 1.0 + } + + #[test] + fn test_exec_vabs_f64() { + let mut processor = Processor::new(); + let params = VMovRegParamsf64 { + dd: DoubleReg::D0, + dm: DoubleReg::D1, + }; + processor.set_dr(DoubleReg::D1, 0x00000000, 0x80000000); // -0.0 + processor.exec_vabs_f64(¶ms).unwrap(); + let (lower, upper) = processor.get_dr(DoubleReg::D0); + assert_eq!((lower, upper), (0x00000000, 0x00000000)); // 0.0 + + processor.set_dr(DoubleReg::D1, 0xFFFFFFFF, 0xFFFFFFFF); // -1.0 + processor.exec_vabs_f64(¶ms).unwrap(); + let (lower, upper) = processor.get_dr(DoubleReg::D0); + assert_eq!((lower, upper), (0xFFFFFFFF, 0x7FFFFFFF)); // 1.0 + } + + #[test] + fn test_exec_vcmp_f32() { + let mut processor = Processor::new(); + let params = VCmpParamsf32 { + sd: SingleReg::S0, + sm: SingleReg::S1, + with_zero: false, + quiet_nan_exc: false, + }; + processor.set_sr(SingleReg::S0, 0x3F800000); // 1.0 + processor.set_sr(SingleReg::S1, 0x3F800000); // 1.0 + processor.exec_vcmp_f32(¶ms).unwrap(); + assert!(!processor.fpscr.get_n()); + assert!(processor.fpscr.get_z()); // 1.0 == 1.0 + assert!(processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + + processor.set_sr(SingleReg::S1, 0x40000000); // 2.0 + processor.exec_vcmp_f32(¶ms).unwrap(); + assert!(processor.fpscr.get_n()); // 1.0 < 2.0 + assert!(!processor.fpscr.get_z()); + assert!(!processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + + processor.set_sr(SingleReg::S0, 0x40000000); // 2.0 + processor.set_sr(SingleReg::S1, 0x3F800000); // 1.0 + processor.exec_vcmp_f32(¶ms).unwrap(); + assert!(!processor.fpscr.get_n()); //2.0 > 1.0 + assert!(!processor.fpscr.get_z()); + assert!(processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + } + + #[test] + fn test_exec_vcmp_f64() { + let mut processor = Processor::new(); + let params = VCmpParamsf64 { + dd: DoubleReg::D0, + dm: DoubleReg::D1, + with_zero: false, + quiet_nan_exc: false, + }; + processor.set_dr(DoubleReg::D0, 0x00000000, 0x3FF00000); // 1.0 + processor.set_dr(DoubleReg::D1, 0x00000000, 0x3FF00000); // 1.0 + processor.exec_vcmp_f64(¶ms).unwrap(); + assert!(!processor.fpscr.get_n()); + assert!(processor.fpscr.get_z()); // 1.0 == 1.0 + assert!(processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + + processor.set_dr(DoubleReg::D1, 0x00000000, 0x40000000); // 2.0 + processor.exec_vcmp_f64(¶ms).unwrap(); + assert!(processor.fpscr.get_n()); // 1.0 < 2.0 + assert!(!processor.fpscr.get_z()); + assert!(!processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + + processor.set_dr(DoubleReg::D0, 0x00000000, 0x40000000); // 2.0 + processor.set_dr(DoubleReg::D1, 0x00000000, 0x3FF00000); // 1.0 + processor.exec_vcmp_f64(¶ms).unwrap(); + assert!(!processor.fpscr.get_n()); //2.0 > 1.0 + assert!(!processor.fpscr.get_z()); + assert!(processor.fpscr.get_c()); + assert!(!processor.fpscr.get_v()); + } + +} diff --git a/zmu_cortex_m/src/executor/fp_generic.rs b/zmu_cortex_m/src/executor/fp_generic.rs new file mode 100644 index 0000000..8da00c9 --- /dev/null +++ b/zmu_cortex_m/src/executor/fp_generic.rs @@ -0,0 +1,407 @@ +use crate::{core::bits::Bits, Processor}; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FPType { + Nonzero, + Zero, + Infinity, + QNaN, + SNaN, +} + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FPExc { + InvalidOp, + DivideByZero, + Overflow, + Underflow, + Inexact, + InputDenorm, +} + +pub fn fpabs_32(value: u32) -> u32 { + // set upmost bit to 0 + value & 0x7FFFFFFF +} + +fn standard_fpscr_value(fpscr: u32) -> u32 { + fpscr +} +pub trait FloatingPointInternalOperations { + fn fp_compare_f32( + &mut self, + op1: u32, + op2: u32, + quiet_nan_exc: bool, + fpscr_controlled: bool, + ) -> (bool, bool, bool, bool); + + fn fp_compare_f64( + &mut self, + op1: u64, + op2: u64, + quiet_nan_exc: bool, + fpscr_controlled: bool, + ) -> (bool, bool, bool, bool); + + fn fp_process_exception(&mut self, exc: FPExc, fpscr_val: u32); + + fn fp_unpack_f32(&mut self, fpval: u32, fpscr_val: u32) -> (FPType, bool, f32); + fn fp_unpack_f64(&mut self, fpval: u64, fpscr_val: u32) -> (FPType, bool, f64); +} + +fn is_zero_32(value: u32) -> bool { + value == 0 +} + +fn is_zero_64(value: u64) -> bool { + value == 0 +} + +fn is_ones_32(value: u32, len: usize) -> bool { + value.count_ones() == len as u32 +} + +fn is_ones_64(value: u64, len: usize) -> bool { + value.count_ones() == len as u32 +} + +impl FloatingPointInternalOperations for Processor { + fn fp_compare_f32( + &mut self, + op1: u32, + op2: u32, + quiet_nan_exc: bool, + fpscr_controlled: bool, + ) -> (bool, bool, bool, bool) { + let fpscr_val = if fpscr_controlled { + self.fpscr + } else { + standard_fpscr_value(self.fpscr) + }; + + let (type1, _sign1, value1) = self.fp_unpack_f32(op1, fpscr_val); + let (type2, _sign2, value2) = self.fp_unpack_f32(op2, fpscr_val); + + if type1 == FPType::SNaN + || type1 == FPType::QNaN + || type2 == FPType::SNaN + || type2 == FPType::QNaN + { + let result = (false, false, true, true); + if type1 == FPType::SNaN || type2 == FPType::SNaN || quiet_nan_exc { + self.fp_process_exception(FPExc::InvalidOp, fpscr_val); + } + result + } else { + if value1 == value2 { + (false, true, true, false) + } else if value1 < value2 { + (true, false, false, false) + } else { + (false, false, true, false) + } + } + } + + fn fp_compare_f64( + &mut self, + op1: u64, + op2: u64, + quiet_nan_exc: bool, + fpscr_controlled: bool, + ) -> (bool, bool, bool, bool) { + let fpscr_val = if fpscr_controlled { + self.fpscr + } else { + standard_fpscr_value(self.fpscr) + }; + + let (type1, _sign1, value1) = self.fp_unpack_f64(op1, fpscr_val); + let (type2, _sign2, value2) = self.fp_unpack_f64(op2, fpscr_val); + + if type1 == FPType::SNaN + || type1 == FPType::QNaN + || type2 == FPType::SNaN + || type2 == FPType::QNaN + { + let result = (false, false, true, true); + if type1 == FPType::SNaN || type2 == FPType::SNaN || quiet_nan_exc { + self.fp_process_exception(FPExc::InvalidOp, fpscr_val); + } + result + } else { + if value1 == value2 { + (false, true, true, false) + } else if value1 < value2 { + (true, false, false, false) + } else { + (false, false, true, false) + } + } + } + + fn fp_process_exception(&mut self, exc: FPExc, fpscr_val: u32) { + let (enable, cumul) = match exc { + FPExc::InvalidOp => (8, 0), + FPExc::DivideByZero => (9, 1), + FPExc::Overflow => (10, 2), + FPExc::Underflow => (11, 3), + FPExc::Inexact => (12, 4), + FPExc::InputDenorm => (15, 7), + }; + if fpscr_val.get_bit(enable) { + // implementation defined trap handling + todo!() + } else { + self.fpscr.set_bit(cumul, true); + } + } + + fn fp_unpack_f32(&mut self, fpval: u32, fpscr_val: u32) -> (FPType, bool, f32) { + let sign = fpval.get_bit(31); + let exp32 = fpval.get_bits(23..31); + let frac32 = fpval.get_bits(0..23); + + let (ret_type, value) = if is_zero_32(exp32) { + if is_zero_32(frac32) || fpscr_val.get_bit(24) { + if !is_zero_32(frac32) { + // Denormalized input flushed to zero + self.fp_process_exception(FPExc::InputDenorm, fpscr_val); + } + (FPType::Zero, 0.0f32) + } else { + ( + FPType::Nonzero, + 2.0f32.powf(-126.0) * (frac32 as f32 * 2.0f32.powf(-23.0)), + ) + } + } else if is_ones_32(exp32, 8) { + if is_zero_32(frac32) { + (FPType::Infinity, 2.0f32.powf(1000000.0)) + } else { + ( + if frac32.get_bit(22) { + FPType::QNaN + } else { + FPType::SNaN + }, + 0.0f32, + ) + } + } else { + ( + FPType::Nonzero, + 2.0f32.powf(exp32 as f32 - 127.0) * (1.0 + frac32 as f32 * 2.0f32.powf(-23.0)), + ) + }; + (ret_type, sign, value) + } + + fn fp_unpack_f64(&mut self, fpval: u64, fpscr_val: u32) -> (FPType, bool, f64) { + let sign = fpval.get_bit(63); + let exp64 = fpval.get_bits(52..63); + let frac64 = fpval.get_bits(0..52); + + let (ret_type, value) = if is_zero_64(exp64) { + if is_zero_64(frac64) || fpscr_val.get_bit(24) { + if !is_zero_64(frac64) { + // Denormalized input flushed to zero + self.fp_process_exception(FPExc::InputDenorm, fpscr_val); + } + (FPType::Zero, 0.0f64) + } else { + ( + FPType::Nonzero, + 2.0f64.powf(-1022.0) * (frac64 as f64 * 2.0f64.powf(-52.0)), + ) + } + } else if is_ones_64(exp64, 11) { + if is_zero_64(frac64) { + (FPType::Infinity, 2.0f64.powf(1000000.0)) + } else { + ( + if frac64.get_bit(51) { + FPType::QNaN + } else { + FPType::SNaN + }, + 0.0f64, + ) + } + } else { + ( + FPType::Nonzero, + 2.0f64.powf(exp64 as f64 - 1023.0) * (1.0 + frac64 as f64 * 2.0f64.powf(-52.0)), + ) + }; + (ret_type, sign, value) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::Processor; + + #[test] + fn test_fp_compare_f32() { + let mut processor = Processor::new(); + assert_eq!( + processor.fp_compare_f32(0x3F800000, 0x3F800000, false, false), + (false, true, true, false) + ); // 1.0 == 1.0 + assert_eq!( + processor.fp_compare_f32(0x3F800000, 0x40000000, false, false), + (true, false, false, false) + ); // 1.0 < 2.0 + assert_eq!( + processor.fp_compare_f32(0x40000000, 0x3F800000, false, false), + (false, false, true, false) + ); // 2.0 > 1.0 + } + + #[test] + fn test_fp_compare_f64() { + let mut processor = Processor::new(); + assert_eq!( + processor.fp_compare_f64(0x3FF0000000000000, 0x3FF0000000000000, false, false), + (false, true, true, false) + ); // 1.0 == 1.0 + assert_eq!( + processor.fp_compare_f64(0x3FF0000000000000, 0x4000000000000000, false, false), + (true, false, false, false) + ); // 1.0 < 2.0 + assert_eq!( + processor.fp_compare_f64(0x4000000000000000, 0x3FF0000000000000, false, false), + (false, false, true, false) + ); // 2.0 > 1.0 + } + + #[test] + fn test_fpabs_32() { + assert_eq!(fpabs_32(0x80000000), 0x00000000); // -0.0 -> 0.0 + assert_eq!(fpabs_32(0xFFFFFFFF), 0x7FFFFFFF); // -1.0 -> 1.0 + assert_eq!(fpabs_32(0x7FFFFFFF), 0x7FFFFFFF); // 1.0 -> 1.0 + } + + #[test] + fn test_fp_unpack_f32() { + let mut processor = Processor::new(); + + // 1.0 + assert_eq!( + processor.fp_unpack_f32(0x3F800000, 0x00000000), + (FPType::Nonzero, false, 1.0) + ); + + // 2.0 + assert_eq!( + processor.fp_unpack_f32(0x40000000, 0x00000000), + (FPType::Nonzero, false, 2.0) + ); + + // max value: + assert_eq!( + processor.fp_unpack_f32(0x7F7FFFFF, 0x00000000), + (FPType::Nonzero, false, std::f32::MAX) + ); + + // 0.0 + assert_eq!( + processor.fp_unpack_f32(0x00000000, 0x00000000), + (FPType::Zero, false, 0.0f32) + ); + + // minimum positive value, non zero: + assert_eq!( + processor.fp_unpack_f32(0x00800000, 0x00000000), + (FPType::Nonzero, false, std::f32::MIN_POSITIVE) + ); + + // Infinity + assert_eq!( + processor.fp_unpack_f32(0x7F800000, 0x00000000), + (FPType::Infinity, false, std::f32::INFINITY) + ); + + // Negative infinity: + assert_eq!( + processor.fp_unpack_f32(0xFF800000, 0x00000000), + (FPType::Infinity, true, std::f32::INFINITY) + ); + + // QNaN + assert_eq!( + processor.fp_unpack_f32(0x7FC00000, 0x00000000), + (FPType::QNaN, false, 0.0) + ); + + // SNaN + assert_eq!( + processor.fp_unpack_f32(0x7F800001, 0x00000000), + (FPType::SNaN, false, 0.0) + ); + } + + #[test] + fn test_fp_unpack_f64() { + let mut processor = Processor::new(); + + // 1.0 + assert_eq!( + processor.fp_unpack_f64(0x3FF0000000000000, 0x00000000), + (FPType::Nonzero, false, 1.0) + ); + + // 2.0 + assert_eq!( + processor.fp_unpack_f64(0x4000000000000000, 0x00000000), + (FPType::Nonzero, false, 2.0) + ); + + // max value: + assert_eq!( + processor.fp_unpack_f64(0x7FEFFFFFFFFFFFFF, 0x00000000), + (FPType::Nonzero, false, std::f64::MAX) + ); + + // 0.0 + assert_eq!( + processor.fp_unpack_f64(0x0000000000000000, 0x00000000), + (FPType::Zero, false, 0.0f64) + ); + + // minimum positive value, non zero: + assert_eq!( + processor.fp_unpack_f64(0x0010000000000000, 0x00000000), + (FPType::Nonzero, false, std::f64::MIN_POSITIVE) + ); + + // Infinity + assert_eq!( + processor.fp_unpack_f64(0x7FF0000000000000, 0x00000000), + (FPType::Infinity, false, std::f64::INFINITY) + ); + + // Negative infinity: + assert_eq!( + processor.fp_unpack_f64(0xFFF0000000000000, 0x00000000), + (FPType::Infinity, true, std::f64::INFINITY) + ); + + // QNaN + assert_eq!( + processor.fp_unpack_f64(0x7FF8000000000000, 0x00000000), + (FPType::QNaN, false, 0.0) + ); + + // SNaN + assert_eq!( + processor.fp_unpack_f64(0x7FF0000000000001, 0x00000000), + (FPType::SNaN, false, 0.0) + ); + } +} diff --git a/zmu_cortex_m/src/executor/fp_register_transfer.rs b/zmu_cortex_m/src/executor/fp_register_transfer.rs index 5efbf5e..fa07877 100644 --- a/zmu_cortex_m/src/executor/fp_register_transfer.rs +++ b/zmu_cortex_m/src/executor/fp_register_transfer.rs @@ -1,13 +1,17 @@ use crate::core::instruction::{ - VMovCr2DpParams, VMovCrSpParams, VMovImmParams32, VMovImmParams64, VMovRegParamsf32, - VMovRegParamsf64, + VMRSTarget, VMovCr2DpParams, VMovCrSpParams, VMovImmParams32, VMovImmParams64, + VMovRegParamsf32, VMovRegParamsf64, }; + +use crate::core::fpregister::Fpscr; use crate::Processor; use crate::executor::ExecuteSuccess; use super::ExecuteResult; -use crate::core::register::{BaseReg, ExtensionRegOperations}; +use crate::core::register::{BaseReg, ExtensionRegOperations, Apsr}; + +use crate::executor::ExecutorHelper; pub trait IsaFloatingPointRegisterTransfer { fn exec_vmov_cr_sp(&mut self, params: &VMovCrSpParams) -> ExecuteResult; @@ -18,6 +22,8 @@ pub trait IsaFloatingPointRegisterTransfer { fn exec_vmov_imm_32(&mut self, params: VMovImmParams32) -> ExecuteResult; fn exec_vmov_imm_64(&mut self, params: VMovImmParams64) -> ExecuteResult; + + fn exec_vmrs(&mut self, params: VMRSTarget) -> ExecuteResult; } impl IsaFloatingPointRegisterTransfer for Processor { @@ -68,4 +74,31 @@ impl IsaFloatingPointRegisterTransfer for Processor { self.set_dr(params.dd, low, high); Ok(ExecuteSuccess::Taken { cycles: 1 }) } + + fn exec_vmrs(&mut self, params: VMRSTarget) -> ExecuteResult { + if self.condition_passed() { + //EncodingSpecificOperations(); + //ExecuteFPCheck(); + //SerializeVFP(); + //VFPExcBarrier(); + + match params { + VMRSTarget::APSRNZCV => { + let n = self.fpscr.get_n(); + let z = self.fpscr.get_z(); + let c = self.fpscr.get_c(); + let v = self.fpscr.get_v(); + + self.psr.set_n_bit(n); + self.psr.set_z_bit(z); + self.psr.set_c(c); + self.psr.set_v(v); + } + VMRSTarget::Register(reg) => { + self.set_r(reg, self.fpscr); + } + } + } + Ok(ExecuteSuccess::Taken { cycles: 1 }) + } } diff --git a/zmu_cortex_m/src/executor/misc.rs b/zmu_cortex_m/src/executor/misc.rs index 4fafe8e..c7a5804 100644 --- a/zmu_cortex_m/src/executor/misc.rs +++ b/zmu_cortex_m/src/executor/misc.rs @@ -1,7 +1,7 @@ use crate::Processor; use crate::{ - core::{bits::Bits, condition::Condition, exception::ExceptionHandling}, + core::{condition::Condition, exception::ExceptionHandling}, executor::{ExecuteSuccess, ExecutorHelper}, }; @@ -98,7 +98,7 @@ impl IsaMisc for Processor { fn exec_wfi(&mut self) -> ExecuteResult { if self.condition_passed() { if self.get_pending_exception().is_none() { - self.state.set_bit(1, true); // sleeping == true + self.sleeping = true; } return Ok(ExecuteSuccess::Taken { cycles: 1 }); } diff --git a/zmu_cortex_m/src/executor/mod.rs b/zmu_cortex_m/src/executor/mod.rs index 8d0fc14..9bbe4b1 100644 --- a/zmu_cortex_m/src/executor/mod.rs +++ b/zmu_cortex_m/src/executor/mod.rs @@ -33,6 +33,8 @@ mod std_data_processing; mod fp_load_and_store; mod fp_register_transfer; +mod fp_data_processing; +mod fp_generic; use branch::IsaBranch; use coproc::IsaCoprocessor; @@ -52,6 +54,7 @@ use std_data_processing::IsaStandardDataProcessing; use fp_load_and_store::IsaFloatingPointLoadAndStore; use fp_register_transfer::IsaFloatingPointRegisterTransfer; +use fp_data_processing::IsaFloatingPointDataProcessing; /// /// Stepping processor with instructions @@ -538,11 +541,17 @@ impl ExecutorHelper for Processor { // // -------------------------------------------- + Instruction::VMRS { rt } => self.exec_vmrs(*rt), + // -------------------------------------------- // // Group: Floating-point data-processing instructions // // -------------------------------------------- + Instruction::VABS_f32 { params } => self.exec_vabs_f32(params), + Instruction::VABS_f64 { params } => self.exec_vabs_f64(params), + Instruction::VCMP_f32 { params } => self.exec_vcmp_f32(params), + Instruction::VCMP_f64 { params } => self.exec_vcmp_f64(params), // -------------------------------------------- // diff --git a/zmu_cortex_m/src/lib.rs b/zmu_cortex_m/src/lib.rs index c775a8a..1ad605e 100644 --- a/zmu_cortex_m/src/lib.rs +++ b/zmu_cortex_m/src/lib.rs @@ -103,12 +103,14 @@ pub struct Processor { /// global interrupt masking /// primask: bool, + /// /// interrupt fault mask, a 1 bit mask register for /// global interrupt masking /// #[cfg(any(feature = "armv7m", feature = "armv7em"))] faultmask: bool, + /// /// basepri for selection of executed interrupt priorities /// @@ -127,9 +129,10 @@ pub struct Processor { /// /// processor simulation state /// - /// bit 0 : 1= simulation running, 0 : simulation terminating /// bit 1 : 1= processor sleeping, 0 : processor awake - pub state: u32, + pub running: bool, + pub sleeping: bool, + pub exit_code: u32, /// /// lookup table for exceptions and their states @@ -176,6 +179,7 @@ pub struct Processor { pub fpccr: u32, pub fpcar: u32, pub fpdscr: u32, + pub fpscr: u32, pub mvfr0: u32, pub mvfr1: u32, @@ -302,7 +306,9 @@ impl Processor { // TODO make RAM size configurable sram: RAM::new_with_fill(0x2000_0000, 128 * 1024, 0xcd), itm_file: None, - state: 0, + sleeping: false, + running: true, + exit_code: 0, cycle_count: 0, instruction_count: 0, exceptions: make_default_exception_priorities(), @@ -327,6 +333,7 @@ impl Processor { fpccr: 0, fpcar: 0, fpdscr: 0, + fpscr: 0, mvfr0: 0, mvfr1: 0, mvfr2: 0, diff --git a/zmu_cortex_m/src/semihosting/mod.rs b/zmu_cortex_m/src/semihosting/mod.rs index a40ab6b..9ee837d 100644 --- a/zmu_cortex_m/src/semihosting/mod.rs +++ b/zmu_cortex_m/src/semihosting/mod.rs @@ -3,7 +3,6 @@ //! use crate::bus::Bus; -use crate::core::bits::Bits; use crate::core::fault::Fault; use crate::core::register::BaseReg; use crate::core::register::Reg; @@ -108,7 +107,7 @@ pub enum SemihostingCommand { }, /// /// Write single char to the debug console - /// + /// SysWriteC { /// char to write data: u8, @@ -222,6 +221,8 @@ pub enum SemihostingResponse { success: bool, /// system is stopping stop: bool, + /// subcode of the exit, dependant of the reason + exit_code: Option, }, /// sysclock command response SysClock { @@ -284,9 +285,7 @@ pub fn decode_semihostcmd( SYS_WRITEC => { let params_ptr = r1; let ch = processor.read8(params_ptr)?; - SemihostingCommand::SysWriteC { - data: ch, - } + SemihostingCommand::SysWriteC { data: ch } } SYS_WRITE => { let params_ptr = r1; @@ -339,6 +338,10 @@ pub fn decode_semihostcmd( SYS_ERRNO => SemihostingCommand::SysErrno, SYS_EXIT_EXTENDED => { let params_ptr = r1; + + // see: https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst#sys-exit-extended-0x20 + + // read field 1 and field 2 let reason = SysExceptionReason::from_u32(processor.read32(params_ptr)?); let subcode = processor.read32(params_ptr + 4)?; @@ -372,10 +375,21 @@ pub fn semihost_return(processor: &mut Processor, response: &SemihostingResponse Ok(response) => processor.set_r(Reg::R0, response), Err(error_code) => processor.set_r(Reg::R0, error_code as u32), }, - SemihostingResponse::SysException { success, stop } - | SemihostingResponse::SysExitExtended { success, stop } => { - if success { - processor.state.set_bit(0, !stop); + SemihostingResponse::SysException { success, stop } => { + if success && stop { + processor.running = false; + } + } + SemihostingResponse::SysExitExtended { + success, + stop, + exit_code, + } => { + if success && stop { + processor.running = false; + if let Some(exit_code) = exit_code { + processor.exit_code = exit_code; + } } } SemihostingResponse::SysClose { success } | SemihostingResponse::SysSeek { success } => { diff --git a/zmu_cortex_m/src/system/simulation.rs b/zmu_cortex_m/src/system/simulation.rs index cbebf09..e258fdb 100644 --- a/zmu_cortex_m/src/system/simulation.rs +++ b/zmu_cortex_m/src/system/simulation.rs @@ -2,7 +2,6 @@ //! Cortex system simulation framework //! -use crate::core::bits::Bits; use crate::core::fault::Fault; use crate::core::register::BaseReg; use crate::core::reset::Reset; @@ -44,6 +43,11 @@ pub struct SimulationStatistics { /// Wallclock time spent for the simulation /// pub duration: Duration, + + /// + /// exit code from process, if any + /// + pub exit_code: u32 } impl From for SimulationError { @@ -74,15 +78,15 @@ pub fn simulate( let start = Instant::now(); processor.reset()?; - processor.state.set_bit(0, true); // running + processor.running = true; - while processor.state & 1 == 1 { - while processor.state == 0b01 { + while processor.running { + while !processor.sleeping && processor.running { //running, !sleeping processor.step(); } - while processor.state == 0b11 { + while processor.sleeping && processor.running{ //running, sleeping processor.step_sleep(); } @@ -93,6 +97,7 @@ pub fn simulate( instruction_count: processor.instruction_count, cycle_count: processor.cycle_count, duration: end.duration_since(start), + exit_code: processor.exit_code, }) } @@ -120,17 +125,17 @@ where let start = Instant::now(); processor.reset().unwrap(); - processor.state.set_bit(0, true); // running + processor.running = true; - while processor.state & 1 == 1 { - while processor.state == 0b01 { + while processor.running { + while !processor.sleeping && processor.running { //running, !sleeping processor.last_pc = processor.get_pc(); processor.step(); trace_func(&processor); } processor.last_pc = processor.get_pc(); - while processor.state == 0b11 { + while processor.sleeping && processor.running { //running, sleeping processor.step_sleep(); } @@ -142,5 +147,6 @@ where instruction_count: processor.instruction_count, cycle_count: processor.cycle_count, duration: end.duration_since(start), + exit_code: processor.exit_code }) }