From d733a2cd273cf6e0d33c2f780e1e17e1da0c17e9 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Tue, 9 Jan 2024 14:02:18 -0500 Subject: [PATCH] arm32: fixed VCVT, VLDR/VSTR --- src/common/cpu/ARM.ts | 115 ++++++++++++++++++++++++------------ src/machine/arm32.ts | 1 + src/worker/lib/arm32/libc.a | Bin 536478 -> 568472 bytes 3 files changed, 77 insertions(+), 39 deletions(-) diff --git a/src/common/cpu/ARM.ts b/src/common/cpu/ARM.ts index a006f4aa..7adc97e7 100644 --- a/src/common/cpu/ARM.ts +++ b/src/common/cpu/ARM.ts @@ -1811,15 +1811,6 @@ ARMCoreArm.prototype.constructVFP3Register = function(condOp, opcode, nOperandRe }; /* -if opc2 != '000' && !(opc2 IN "10x") then SEE "Related encodings"; -to_integer = (opc2<2> == '1'); dp_operation = (sz == 1); -if to_integer then - unsigned = (opc2<0> == '0'); round_zero = (op == '1'); - d = UInt(Vd:D); m = if dp_operation then UInt(M:Vm) else UInt(Vm:M); -else - unsigned = (op == '0'); round_nearest = FALSE; // FALSE selects FPSCR rounding - m = UInt(Vm:M); d = if dp_operation then UInt(D:Vd) else UInt(Vd:D); - if ConditionPassed() then EncodingSpecificOperations(); CheckVFPEnabled(TRUE); if to_integer then @@ -1833,24 +1824,11 @@ if ConditionPassed() then else S[d] = FixedToFP(S[m], 32, 0, unsigned, round_nearest, TRUE); */ -ARMCoreArm.prototype.constructVCVT = function(condOp, D, opc2, Vd, sz, op, M, Vm) { +ARMCoreArm.prototype.constructVCVT = function(condOp, d, m, to_integer, dp_operation, unsigned, round_zero, round_nearest) { var cpu : ARMCoreType = this.cpu; var sregs = cpu.sfprs; var dregs = cpu.dfprs; var iregs = cpu.ifprs; - var to_integer = (opc2 & 0x4) != 0; - var dp_operation = (sz & 1) != 0; - var unsigned = (opc2 & 0x1) == 0; - var round_zero = false; - var round_nearest = false; - if (to_integer) { - unsigned = (opc2 & 0x1) == 0; - round_zero = (op & 0x1) != 0; - } else { - unsigned = (op & 0x1) == 0; - round_nearest = false; - } - //console.log("VCVT: " + hex(D) + " " + hex(opc2) + " " + hex(Vd) + " " + hex(sz) + " " + hex(op) + " " + hex(M) + " " + hex(Vm) + " " + to_integer + " " + unsigned); return function() { cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]); if (condOp && !condOp()) { @@ -1860,11 +1838,11 @@ ARMCoreArm.prototype.constructVCVT = function(condOp, D, opc2, Vd, sz, op, M, Vm var dest : number; // get source if (to_integer && dp_operation) { - src = dregs[M]; + src = dregs[m]; } else if (to_integer) { - src = sregs[M]; + src = sregs[m]; } else { - src = iregs[M]; + src = iregs[m]; } // convert if (to_integer) { @@ -1874,11 +1852,11 @@ ARMCoreArm.prototype.constructVCVT = function(condOp, D, opc2, Vd, sz, op, M, Vm } // store result if (to_integer) { - iregs[D] = dest; + iregs[d] = dest; } else if (dp_operation) { - dregs[D] = dest; + dregs[d] = dest; } else { - sregs[D] = dest; + sregs[d] = dest; } }; } @@ -1935,8 +1913,8 @@ ARMCoreArm.prototype.constructVSTR = function(condOp, srcReg, address, single_re if (single_reg) { cpu.mmu.store32(addr, iregs[srcReg]); } else { - cpu.mmu.store32(addr, iregs[srcReg*2]); - cpu.mmu.store32(addr+4, iregs[srcReg*2+1]); + cpu.mmu.store32(addr, iregs[srcReg]); + cpu.mmu.store32(addr+4, iregs[srcReg+1]); } cpu.mmu.wait32(addr); cpu.mmu.wait32(cpu.gprs[ARMRegs.PC]); @@ -2015,17 +1993,40 @@ ARMCoreArm.prototype.constructVCMP = function(condOp, d, Vd, sz, E, m, Vm) { } } -ARMCoreArm.prototype.constructVMOV = function(condOp, to_arm_reg, n, t) { +ARMCoreArm.prototype.constructVCMP0 = function(condOp, d, Vd, sz, E) { var cpu : ARMCoreType = this.cpu; - var srcregs = to_arm_reg ? cpu.ifprs : cpu.gprs; - var destregs = to_arm_reg ? cpu.gprs : cpu.ifprs; - //console.log('VMOV: ' + hex(to_arm_reg) + ' ' + hex(n) + ' ' + hex(t)); + var sregs = cpu.sfprs; + var dregs = cpu.dfprs; return function() { cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]); if (condOp && !condOp()) { return; } - destregs[t] = srcregs[n]; + let op1, op2=0; + if (sz) { + op1 = dregs[d]; + } else { + op1 = sregs[d]; + } + let result = FPCompare(op1, op2); + cpu.cpsrN = (result & 8) != 0; + cpu.cpsrZ = (result & 4) != 0; + cpu.cpsrC = (result & 2) != 0; + cpu.cpsrV = (result & 1) != 0; + } +} +ARMCoreArm.prototype.constructVMOV = function(condOp, to_arm_reg, n, t) { + var cpu : ARMCoreType = this.cpu; + return function() { + cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]); + if (condOp && !condOp()) { + return; + } + if (to_arm_reg) { + cpu.gprs[t] = cpu.ifprs[n]; + } else { + cpu.ifprs[n] = cpu.gprs[t]; + } } } @@ -3976,6 +3977,7 @@ ARMCore.prototype.compileArm = function(instruction) { op = this.armCompiler.constructVPOP(condOp, ((user?16:0)|crd)*2, immediate, false); } // VLDR, VSTR + // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VSTR?lang=en else if ((instruction & 0x0f200f00) == 0x0d000a00) { immediate *= 4; if (!u) immediate = -immediate; @@ -4022,16 +4024,34 @@ ARMCore.prototype.compileArm = function(instruction) { } // VCVT, VCVTR, VCVT // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VCVT--VCVTR--between-floating-point-and-integer--Floating-point- + /* + if opc2 != '000' && !(opc2 IN "10x") then SEE "Related encodings"; + to_integer = (opc2<2> == '1'); dp_operation = (sz == 1); + if to_integer then + unsigned = (opc2<0> == '0'); round_zero = (op == '1'); + d = UInt(Vd:D); m = if dp_operation then UInt(M:Vm) else UInt(Vm:M); + else + unsigned = (op == '0'); round_nearest = FALSE; // FALSE selects FPSCR rounding + m = UInt(Vm:M); d = if dp_operation then UInt(D:Vd) else UInt(Vd:D); + */ else if ((instruction & 0x0FB80E50) == 0x0EB80A40) { const cond = (instruction >> 28) & 0xf; const D = (instruction >> 22) & 0x1; const opc2 = (instruction >> 16) & 0x7; const Vd = (instruction >> 12) & 0xf; const sz = (instruction >> 8) & 0x1; - const to_fixed = (instruction >> 7) & 0x1; + const op0 = (instruction >> 7) & 0x1; const M = (instruction >> 5) & 0x1; const Vm = instruction & 0xf; - op = this.armCompiler.constructVCVT(condOp, D, opc2, Vd, sz, to_fixed, M, Vm); + const to_integer = opc2 & 0x4; + const dp_operation = sz != 0; + const unsigned = to_integer ? opc2 & 0x1 : 0; + const round_zero = op0 != 0; + const round_nearest = false; + const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0); + const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0); + //console.log("VCVT", d, m, opc2, to_integer, dp_operation, unsigned, round_zero, round_nearest); + op = this.armCompiler.constructVCVT(condOp, d, m, to_integer, dp_operation, unsigned, round_zero, round_nearest); op.writesPC = false; } // VCVT f64/f32 @@ -4106,11 +4126,27 @@ ARMCore.prototype.compileArm = function(instruction) { op = this.armCompiler.constructVCMP(condOp, d, Vd, sz, E, m, Vm); op.writesPC = false; } + // VCMP #0 + else if ((instruction & 0x0FBF0EFF) == 0x0EB50A40) { + const cond = (instruction >> 28) & 0xf; + const D = (instruction >> 22) & 0x1; + const Vd = (instruction >> 12) & 0xf; + const sz = (instruction >> 8) & 0x1; + const E = (instruction >> 7) & 0x1; + const M = (instruction >> 5) & 0x1; + const Vm = instruction & 0x0000000F; + const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0); + const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0); + + var condOp = this.conds[cond]; + op = this.armCompiler.constructVCMP0(condOp, d, Vd, sz, E, m, Vm); + op.writesPC = false; + } // vmrs apsr_nzcv, fpscr (ignore, we always call this after CMP) else if (instruction == 0xeef1fa10) { op = this.armCompiler.constructNOP(); } - // VMOV + // VMOV - https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/VMOV--between-ARM-core-register-and-single-precision-register- else if ((instruction & 0x0FE00F10) == 0x0E000A10) { const cond = (instruction >> 28) & 0xf; const opc1 = (instruction >> 20) & 0x1; @@ -4118,6 +4154,7 @@ ARMCore.prototype.compileArm = function(instruction) { const Rt = (instruction >> 12) & 0xf; const N = (instruction >> 7) & 0x1; var condOp = this.conds[cond]; + //console.log("VMOV", instruction.toString(16), opc1, Vn, Rt, N); op = this.armCompiler.constructVMOV(condOp, opc1, (Vn<<1)|(N?1:0), Rt); } // vmov.32 dn[i], rn diff --git a/src/machine/arm32.ts b/src/machine/arm32.ts index e34658a6..c9c16c40 100644 --- a/src/machine/arm32.ts +++ b/src/machine/arm32.ts @@ -96,6 +96,7 @@ export class ARM32Machine extends BasicScanlineMachine } reset() { + this.ram.fill(0); if (this.rom) { this.ram.set(this.rom, this.rombase); } diff --git a/src/worker/lib/arm32/libc.a b/src/worker/lib/arm32/libc.a index 16f66e8dd800aec3bc60a75d961bc900a4b8d9c0..6216371d2ff070315ffe6e56c17251b6dc22921e 100644 GIT binary patch delta 19684 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