Compare commits

...

14 Commits

Author SHA1 Message Date
Steven Hugg d733a2cd27 arm32: fixed VCVT, VLDR/VSTR 2024-01-09 15:56:10 -05:00
Steven Hugg 098dcda93a arm32: parse dwarf info tree, convert to number 2024-01-06 11:55:59 -05:00
Steven Hugg 886e19611e arm32: crt0 update for malloc 2024-01-05 12:23:54 -05:00
Steven Hugg 02986aed38 arm32: parse dwarf 2024-01-04 22:30:53 -05:00
Steven Hugg d5b5734ef9 arm: fixed fpu, raster frame sync 2024-01-02 13:49:55 -05:00
Steven Hugg 579e58e966 close button for debug info 2024-01-01 15:27:42 -05:00
Steven Hugg 47a7aa5a83 arm: started on fpu insns 2024-01-01 15:21:52 -05:00
Steven Hugg 9142328468 arm: merged libc and libtcc1 2023-12-31 14:45:51 -05:00
Steven Hugg 70eee2bdae arm: fixing libc 2023-12-30 16:08:16 -05:00
Steven Hugg 949e216c69 make sure display + serial is updated if advanceFrame() throws 2023-12-30 15:24:57 -05:00
Steven Hugg b160fb2ef2 license: clarified multi-license 2023-12-30 15:07:29 -05:00
Steven Hugg 63ee25741b arm: libc 2023-12-30 11:53:56 -05:00
Steven Hugg 8bdbae36e3 arm: parse ELF 2023-12-29 20:41:40 -05:00
Steven Hugg c0909bef1b arm: arm-tcc 2023-12-28 17:26:44 -05:00
28 changed files with 2455 additions and 95 deletions

View File

@ -45,20 +45,22 @@ Note: Github tests may fail due to lack of API key.
## License
Copyright © 2016-2022 [Steven Hugg](https://github.com/sehugg).
Copyright © 2016-2024 [Steven E. Hugg](https://github.com/sehugg).
This project is [GPL-3.0](https://github.com/sehugg/8bitworkshop/blob/master/LICENSE) licensed.
Dependencies retain their original licenses.
This project, unless specifically noted, is multi-licensed.
You may choose to adhere to the terms of either the [GPL-3.0](https://github.com/sehugg/8bitworkshop/blob/master/LICENSE) License for the entire project or respect the individual licenses of its dependencies and included code samples, as applicable.
All included code samples (all files under the presets/ directory) are licensed under
This project includes various dependencies, modules, and components that retain their original licenses.
For detailed licensing information for each dependency, please refer to the respective files and documentation.
All included code samples located in the presets/ directory are licensed under
[CC0](https://creativecommons.org/publicdomain/zero/1.0/)
unless otherwise licensed.
unless a different license is explicitly stated within the specific code sample.
## Dependencies
The IDE uses custom forks for many of these, found at https://github.com/sehugg?tab=repositories
### Emulators
* https://javatari.org/
@ -84,6 +86,7 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg?
* https://github.com/wiz-lang/wiz
* https://github.com/sylefeb/Silice
* https://github.com/steux/cc7800
* https://bellard.org/tcc/
### Assemblers/Linkers
@ -112,6 +115,8 @@ The IDE uses custom forks for many of these, found at https://github.com/sehugg?
* https://github.com/sehugg/8bitworkshop-compilers
* https://github.com/sehugg/8bit-tools
* https://github.com/sehugg/awesome-8bitgamedev
* https://github.com/sehugg?tab=repositories
## Tool Server (experimental)

View File

@ -127,13 +127,15 @@ div.mem_info {
bottom: 10px;
background-color: #333;
color: #66ff66;
white-space: pre;
padding: 20px;
z-index: 12;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 12pt;
box-shadow: 0px 0px 8px rgba(0,0,0,.5);
max-height: 90vh;
}
div.mem_info_msg {
white-space: pre;
padding: 20px;
max-height: 80vh;
overflow-y: auto;
}
div.mem_info a {
@ -148,6 +150,9 @@ div.mem_info a:hover {
div.mem_info a.selected {
color: #ffffff;
}
div.mem_info button {
color: #fff;
}
.mem_info_links {
text-align:right;
}

View File

@ -378,6 +378,8 @@ body {
</div>
</div>
<div id="mem_info" class="mem_info" style="display:none">
<div><button type="button" class="close" onclick="$('.mem_info').hide()" aria-hidden="true">&times;</button></div>
<div id="mem_info_msg" class="mem_info_msg"></div>
</div>
<div id="error_alert" class="alert alert-danger alert-dismissable" style="position:absolute;right:0;top:0;display:none">
<button type="button" class="close" onclick="$('.alert').hide()" aria-hidden="true">&times;</button>

View File

@ -0,0 +1,7 @@
#define SERIAL_OUT ((int*)0x4000048)
void putchar_(char c) {
*SERIAL_OUT = c;
}

35
presets/arm32/sieve.c Normal file
View File

@ -0,0 +1,35 @@
#include <string.h>
#include <stdio.h>
#define true 1
#define false 0
#define size 8190
#define sizepl 8191
//#link "serialout.c"
main() {
char flags[sizepl];
int i, prime, k, count, iter;
printf("Running benchmark...\n");
for (iter = 1; iter <= 10; iter ++) {
count=0;
for (i = 0; i <= size; i++)
flags[i] = true;
for (i = 0; i <= size; i++) {
if (flags[i]) {
prime = i + i + 3;
k = i + prime;
while (k <= size) {
flags[k] = false;
k += prime;
}
count = count + 1;
}
}
}
printf("Primes: %d\n", count);
return 0;
}

View File

@ -0,0 +1,11 @@
#include <string.h>
#include <stdio.h>
//#link "serialout.c"
int main() {
int x = 2024;
printf("Hello World! %d\n", x);
return 0;
}

25
presets/arm32/vidfill.c Normal file
View File

@ -0,0 +1,25 @@
const char const str[] = "HELLO WORLD!";
int global = 0x1234;
int global2 = 0x123456;
#define VIDBASE ((int*)0x4000080)
int vidbuf[160*128];
int main() {
*VIDBASE = (int)vidbuf;
global += str[0];
global++;
global2++;
int c = 0xff880000;
c += str[0];
int* p = (int*) vidbuf;
for (int i=0; i<160*128; i++) {
p[i] = c++;
}
return 0;
}

View File

@ -873,15 +873,19 @@ export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPl
advance(novideo:boolean) {
let trap = this.getDebugCallback();
var steps = this.machine.advanceFrame(trap);
if (!novideo && this.video) {
this.video.updateFrame();
this.updateVideoDebugger();
try {
var steps = this.machine.advanceFrame(trap);
return steps;
} finally {
// in case EmuHalt is thrown...
if (!novideo && this.video) {
this.video.updateFrame();
this.updateVideoDebugger();
}
if (!novideo && this.serialVisualizer) {
this.serialVisualizer.refresh();
}
}
if (!novideo && this.serialVisualizer) {
this.serialVisualizer.refresh();
}
return steps;
}
updateVideoDebugger() {

1257
src/common/binutils.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import { Bus, CPU, InstructionBased, SavesState } from "../devices";
import { Bus, Bus32, CPU, InstructionBased, SavesState } from "../devices";
import { EmuHalt } from "../emu";
import { hex } from "../util";
@ -103,12 +103,18 @@ export interface ARMCoreState {
bankedRegisters: number[][],
spsr: number,
bankedSPSRs: number[],
sfprs: number[],
dfprs: number[],
ifprs: number[],
cycles: number,
instructionWidth: 2 | 4
}
interface ARMCoreType {
gprs: Int32Array;
sfprs: Float32Array;
dfprs: Float64Array;
ifprs: Int32Array;
PC: number;
SP: number;
LR: number;
@ -659,6 +665,11 @@ ARMCoreArm.prototype.constructAddressingMode4Writeback = function(immediate, off
}
};
ARMCoreArm.prototype.constructNOP = function() {
this.writesPC = false;
return function() { };
}
ARMCoreArm.prototype.constructADC = function(rd, rn, shiftOp, condOp) {
var cpu : ARMCoreType = this.cpu;
var gprs = cpu.gprs;
@ -1760,6 +1771,265 @@ ARMCoreArm.prototype.constructUMULLS = function(rd, rn, rs, rm, condOp) {
};
};
ARMCoreArm.prototype.constructVFP3Register = function(condOp, opcode, nOperandReg, destReg, sz, opcode2, mOperandReg) {
var cpu : ARMCoreType = this.cpu;
var fpregs = sz ? cpu.dfprs : cpu.sfprs;
//console.log("VFP3Register: " + hex(opcode) + " " + hex(nOperandReg) + " " + hex(destReg) + " " + hex(number) + " " + hex(opcode2) + " " + hex(mOperandReg));
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
switch (opcode) {
case 2: // VMUL
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] * fpregs[mOperandReg];
return;
}
break;
case 3: // VADD/VSUB
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] + fpregs[mOperandReg];
return;
case 2:
fpregs[destReg] = fpregs[nOperandReg] - fpregs[mOperandReg];
return;
}
break;
case 8: // VDIV
switch (opcode2) {
case 0:
fpregs[destReg] = fpregs[nOperandReg] / fpregs[mOperandReg];
return;
}
break;
}
console.log("Unsupported instruction: " + hex(opcode) + " " + hex(opcode2));
};
};
/*
if ConditionPassed() then
EncodingSpecificOperations(); CheckVFPEnabled(TRUE);
if to_integer then
if dp_operation then
S[d] = FPToFixed(D[m], 32, 0, unsigned, round_zero, TRUE);
else
S[d] = FPToFixed(S[m], 32, 0, unsigned, round_zero, TRUE);
else
if dp_operation then
D[d] = FixedToFP(S[m], 64, 0, unsigned, round_nearest, TRUE);
else
S[d] = FixedToFP(S[m], 32, 0, unsigned, round_nearest, TRUE);
*/
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;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
var src : number;
var dest : number;
// get source
if (to_integer && dp_operation) {
src = dregs[m];
} else if (to_integer) {
src = sregs[m];
} else {
src = iregs[m];
}
// convert
if (to_integer) {
dest = round_zero ? Math.floor(src) : Math.round(src);
} else {
dest = src;
}
// store result
if (to_integer) {
iregs[d] = dest;
} else if (dp_operation) {
dregs[d] = dest;
} else {
sregs[d] = dest;
}
};
}
ARMCoreArm.prototype.constructVCVTF = function(condOp, d, m, double_to_single) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
var n = double_to_single ? dregs[m] : sregs[m];
// store result
if (double_to_single) {
sregs[d] = n;
} else {
dregs[d] = n;
}
};
}
ARMCoreArm.prototype.constructVLDR = function(condOp, destReg, address, single_reg) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = address();
if (single_reg) {
iregs[destReg] = cpu.mmu.load32(addr);
} else {
iregs[destReg] = cpu.mmu.load32(addr);
iregs[destReg+1] = cpu.mmu.load32(addr+4);
}
cpu.mmu.wait32(addr);
cpu.mmu.wait32(cpu.gprs[ARMRegs.PC]);
};
};
ARMCoreArm.prototype.constructVSTR = function(condOp, srcReg, address, single_reg) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = address();
if (single_reg) {
cpu.mmu.store32(addr, iregs[srcReg]);
} else {
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]);
};
}
ARMCoreArm.prototype.constructVPUSH = function(condOp, d, regs, single_regs) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = cpu.gprs[ARMRegs.SP] - regs * 4;
cpu.gprs[ARMRegs.SP] = addr;
for (let i = 0; i < regs; ++i) {
cpu.mmu.store32(addr, iregs[d+i]);
addr += 4;
}
};
}
ARMCoreArm.prototype.constructVPOP = function(condOp, d, regs, single_regs) {
var cpu : ARMCoreType = this.cpu;
var iregs = cpu.ifprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let addr = cpu.gprs[ARMRegs.SP];
cpu.gprs[ARMRegs.SP] += regs * 4;
for (let i = 0; i < regs; ++i) {
iregs[d+i] = cpu.mmu.load32(addr);
addr += 4;
}
};
}
function FPCompare(op1: number, op2: number) {
/* assert N IN {32,64};
fpscr_val = if fpscr_controlled then FPSCR else StandardFPSCRValue();
(type1,sign1,value1) = FPUnpack(op1, fpscr_val);
(type2,sign2,value2) = FPUnpack(op2, fpscr_val); */
if (isNaN(op1) || isNaN(op2)) {
return 0b0011;
}
if (op1 == op2) return 0b0110;
if (op1 < op2) return 0b1000;
else return 0b0010;
}
ARMCoreArm.prototype.constructVCMP = function(condOp, d, Vd, sz, E, m, Vm) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
let op1, op2;
if (sz) {
op1 = dregs[d];
op2 = dregs[m];
} else {
op1 = sregs[d];
op2 = sregs[m];
}
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.constructVCMP0 = function(condOp, d, Vd, sz, E) {
var cpu : ARMCoreType = this.cpu;
var sregs = cpu.sfprs;
var dregs = cpu.dfprs;
return function() {
cpu.mmu.waitPrefetch32(cpu.gprs[ARMRegs.PC]);
if (condOp && !condOp()) {
return;
}
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];
}
}
}
///////////////////////////////////////////////////////////////////////////
function ARMCoreThumb(cpu) {
@ -2658,6 +2928,9 @@ function ARMCore() {
this.generateConds();
this.gprs = new Int32Array(16);
this.dfprs = new Float64Array(16);
this.sfprs = new Float32Array(this.dfprs.buffer); // regs shared with dfprs
this.ifprs = new Int32Array(this.dfprs.buffer); // regs shared with dfprs
};
ARMCore.prototype.resetCPU = function(startOffset) {
@ -2665,6 +2938,7 @@ ARMCore.prototype.resetCPU = function(startOffset) {
this.gprs[i] = 0;
}
this.gprs[ARMRegs.PC] = startOffset + ARMConstants.WORD_SIZE_ARM;
this.dfprs.set(0); // no need to zero the sfprs, since they share the same buffer
this.loadInstruction = this.loadInstructionArm;
this.execMode = ARMMode.MODE_ARM;
@ -2769,6 +3043,9 @@ ARMCore.prototype.freeze = function() : ARMCoreState {
this.gprs[14],
this.gprs[15],
],
'sfprs': this.sfprs.slice(),
'dfprs': this.dfprs.slice(),
'ifprs': this.ifprs.slice(),
'mode': this.mode,
'cpsrI': this.cpsrI,
'cpsrF': this.cpsrF,
@ -2850,6 +3127,8 @@ ARMCore.prototype.defrost = function(frost: ARMCoreState) {
this.gprs[14] = frost.gprs[14];
this.gprs[15] = frost.gprs[15];
this.ifprs.set(frost.ifprs); // regs shared with sfprs
this.mode = frost.mode;
this.cpsrI = frost.cpsrI;
this.cpsrF = frost.cpsrF;
@ -3538,6 +3817,11 @@ ARMCore.prototype.compileArm = function(instruction) {
var load = instruction & 0x00100000;
var b = instruction & 0x00400000;
var i = instruction & 0x02000000;
// test for UDF instruction
if ((instruction & 0xfff000f0) == (0xe7f000f0|0)) {
var immediate = instruction & 0x0000000f; // TODO: full range
throw new EmuHalt("Program exited (" + immediate + ")");
}
var address : AddressFunction = function() {
throw new EmuHalt("Unimplemented memory access: 0x" + instruction.toString(16));
@ -3667,6 +3951,68 @@ ARMCore.prototype.compileArm = function(instruction) {
break;
case 0x0C000000:
// Coprocessor data transfer
var load = instruction & 0x00100000;
var w = instruction & 0x00200000;
var user = instruction & 0x00400000;
var u = instruction & 0x00800000;
var p = instruction & 0x01000000;
var rn = (instruction & 0x000F0000) >> 16;
var crd = (instruction & 0x0000F000) >> 12;
var cpnum = (instruction & 0x00000F00) >> 8;
var immediate = instruction & 0x000000FF;
var cond = (instruction >> 28) & 0xf;
var condOp = this.conds[cond];
// VPUSH, VPOP
if ((instruction & 0x0fbf0f00) == 0x0d2d0a00) {
op = this.armCompiler.constructVPUSH(condOp, (crd<<1)|(user?1:0), immediate, true);
}
else if ((instruction & 0x0fbf0f00) == 0x0d2d0b00) {
op = this.armCompiler.constructVPUSH(condOp, ((user?16:0)|crd)*2, immediate, false);
}
else if ((instruction & 0x0fbf0f00) == 0x0cbd0a00) {
op = this.armCompiler.constructVPOP(condOp, (crd<<1)|(user?1:0), immediate, true);
}
else if ((instruction & 0x0fbf0f00) == 0x0cbd0b00) {
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;
var overlap = false;
var d = (crd<<1)|(user?1:0);
var address : AddressFunction;
if (w) {
address = this.armCompiler.constructAddressingMode4Writeback(immediate, offset, rn, overlap);
} else {
address = this.armCompiler.constructAddressingMode4(immediate, rn);
}
if (load) {
op = this.armCompiler.constructVLDR(condOp, d, address, true);
} else {
op = this.armCompiler.constructVSTR(condOp, d, address, true);
}
} else if ((instruction & 0x0f200f00) == 0x0d000b00) {
immediate *= 4;
if (!u) immediate = -immediate;
var overlap = false;
var d = ((user?16:0)|crd)*2;
var address : AddressFunction;
if (w) {
address = this.armCompiler.constructAddressingMode4Writeback(immediate, offset, rn, overlap);
} else {
address = this.armCompiler.constructAddressingMode4(immediate, rn);
}
if (load) {
op = this.armCompiler.constructVLDR(condOp, d, address, false);
} else {
op = this.armCompiler.constructVSTR(condOp, d, address, false);
}
}
break;
case 0x0E000000:
// Coprocessor data operation/SWI
@ -3676,6 +4022,148 @@ ARMCore.prototype.compileArm = function(instruction) {
op = this.armCompiler.constructSWI(immediate, condOp);
op.writesPC = false;
}
// 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 op0 = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
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
else if ((instruction & 0x0FBF0ED0) == 0x0EB70AC0) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vd = (instruction >> 12) & 0xf;
const sz = (instruction >> 8) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
const double_to_single = sz != 0;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
op = this.armCompiler.constructVCVTF(condOp, d, m, double_to_single);
op.writesPC = false;
}
// 3-op floating point vector instructions (VADD, etc)
else if ((instruction & 0x0FA00E10) == 0x0E200A00) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const N = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const opcode = (instruction & 0x0F00000) >> 20;
const Vn = (instruction & 0x000F0000) >> 16;
const Vd = (instruction & 0x0000F000) >> 12;
const opcode2 = (instruction & 0b11100000) >> 5;
const Vm = instruction & 0x0000000F;
const sz = (instruction >> 8) & 0x1;
const d = sz ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = sz ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
const n = sz ? (N?16:0)|Vn : (Vn<<1)|(N?1:0);
var condOp = this.conds[cond];
op = this.armCompiler.constructVFP3Register(condOp, opcode, n, d, sz, opcode2, m);
op.writesPC = false;
}
// VDIV - https://developer.arm.com/documentation/ddi0597/2023-12/SIMD-FP-Instructions/VDIV--Divide-?lang=en
else if ((instruction & 0x0FB00C50) == 0x0E800800) {
const cond = (instruction >> 28) & 0xf;
const D = (instruction >> 22) & 0x1;
const Vn = (instruction >> 16) & 0xf;
const Vd = (instruction >> 12) & 0xf;
const size = (instruction >> 8) & 0x3;
const N = (instruction >> 7) & 0x1;
const M = (instruction >> 5) & 0x1;
const Vm = instruction & 0xf;
/*
case size of
when '01' esize = 16; d = UInt(Vd:D); n = UInt(Vn:N); m = UInt(Vm:M);
when '10' esize = 32; d = UInt(Vd:D); n = UInt(Vn:N); m = UInt(Vm:M);
when '11' esize = 64; d = UInt(D:Vd); n = UInt(N:Vn); m = UInt(M:Vm);
*/
const d = size==3 ? (D?16:0)|Vd : (Vd<<1)|(D?1:0);
const m = size==3 ? (M?16:0)|Vm : (Vm<<1)|(M?1:0);
const n = size==3 ? (N?16:0)|Vn : (Vn<<1)|(N?1:0);
op = this.armCompiler.constructVFP3Register(condOp, 8, n, d, size==3, 0, m);
op.writesPC = false;
}
// 2-op floating point vector instructions (VCMP, etc)
else if ((instruction & 0x0FBF0E50) == 0x0EB40A40) {
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.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 - 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;
const Vn = (instruction >> 16) & 0xf;
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
else if (instruction == 0xee000b10) {
op = this.armCompiler.constructVMOV(condOp, false, 0, 0);
}
else if (instruction == 0xee201b10) {
op = this.armCompiler.constructVMOV(condOp, false, 1, 1);
}
break;
default:
throw new EmuHalt('Bad opcode: 0x' + instruction.toString(16));
@ -4103,11 +4591,16 @@ ARMCore.prototype.compileThumb = function(instruction) {
///////////////////////////////////////////////////////////////////////////
type ARMBus = Bus & Bus32;
export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQInterface, SavesState<ARMCoreState> {
core : ARMCoreType;
bus : Bus;
bus : ARMBus;
memory : ARMMemoryRegion[];
f64arr = new Float64Array(1);
f32arr = new Float32Array(this.f64arr.buffer);
i32arr = new Int32Array(this.f64arr.buffer);
BASE_OFFSET = 24;
OFFSET_MASK = 0x00FFFFFF;
@ -4122,8 +4615,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
this.memory = []; // TODO
for (var i=0; i<256; i++) {
// TODO: constant
var bits = 10;
var size = 0x80000;
const bits = 10;
this.memory[i] = {
PAGE_MASK: (2 << bits) - 1,
ICACHE_PAGE_BITS: bits,
@ -4146,12 +4638,13 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
isStable(): boolean {
return true; // TODO?
}
connectMemoryBus(bus: Bus): void {
connectMemoryBus(bus: ARMBus): void {
this.bus = bus;
}
reset(): void {
this.resetMemory();
this.core.resetCPU(0);
const resetVector = this.load32(0);
this.core.resetCPU(resetVector);
}
saveState() : ARMCoreState {
return this.core.freeze();
@ -4173,7 +4666,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
return this.bus.read(a) | (this.bus.read(a+1) << 8);
}
load32(a: number): number {
var v = this.bus.read(a) | (this.bus.read(a+1) << 8) | (this.bus.read(a+2) << 16) | (this.bus.read(a+3) << 24);
var v = this.bus.read32(a);
return v;
}
// TODO: memory.invalidatePage(maskedOffset);
@ -4185,10 +4678,7 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
this.bus.write(a+1, (v >> 8) & 0xff);
}
store32(a: number, v: number): void {
this.bus.write(a, v & 0xff);
this.bus.write(a+1, (v >> 8) & 0xff);
this.bus.write(a+2, (v >> 16) & 0xff);
this.bus.write(a+3, (v >> 24) & 0xff);
this.bus.write32(a, v);
}
// TODO
wait(a: number): void {
@ -4253,4 +4743,10 @@ export class ARM32CPU implements CPU, InstructionBased, ARMMMUInterface, ARMIRQI
isThumb() : boolean {
return this.core.instructionWidth == 2;
}
getDebugTree() {
return {
state: this.saveState(),
mmu: this.core.mmu
};
}
}

View File

@ -10,6 +10,12 @@ export interface Bus {
readConst?(a: number): number;
}
export interface Bus32 {
read32(a: number): number;
write32(a: number, v: number): void;
readConst32?(a: number): number;
}
export interface ClockBased {
advanceClock(): void;
}
@ -276,7 +282,7 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
this.probe.logClocks(n);
return n;
}
probeMemoryBus(membus: Bus): Bus {
probeMemoryBus(membus: Bus & Partial<Bus32>): Bus & Partial<Bus32> {
return {
read: (a) => {
let val = membus.read(a);
@ -286,11 +292,20 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
write: (a, v) => {
this.probe.logWrite(a, v);
membus.write(a, v);
},
read32: (a) => {
let val = membus.read32(a);
this.probe.logRead(a, val);
return val;
},
write32: (a, v) => {
this.probe.logWrite(a, v);
membus.write32(a, v);
}
};
}
connectCPUMemoryBus(membus: Bus): void {
this.cpu.connectMemoryBus(this.probeMemoryBus(membus));
this.cpu.connectMemoryBus(this.probeMemoryBus(membus as Bus&Bus32));
}
probeIOBus(iobus: Bus): Bus {
return {
@ -302,7 +317,7 @@ export abstract class BasicHeadlessMachine implements HasCPU, Bus, AcceptsROM, P
write: (a, v) => {
this.probe.logIOWrite(a, v);
iobus.write(a, v);
}
},
};
}
probeDMABus(iobus: Bus): Bus {

View File

@ -658,7 +658,7 @@ export function padBytes(data:Uint8Array|number[], len:number, padstart?:boolean
type AddressReadWriteFn = ((a:number) => number) | ((a:number,v:number) => void);
type AddressDecoderEntry = [number, number, number, AddressReadWriteFn];
type AddressDecoderOptions = {gmask?:number};
type AddressDecoderOptions = {gmask?:number, defaultval?:number};
// TODO: better performance, check values
export function AddressDecoder(table : AddressDecoderEntry[], options?:AddressDecoderOptions) {
@ -679,7 +679,7 @@ export function AddressDecoder(table : AddressDecoderEntry[], options?:AddressDe
if (mask) s += "a&="+mask+";";
s += "return this.__fn"+i+"(a,v)&0xff;}\n";
}
s += "return 0;"; // TODO: noise()?
s += "return "+(options?.defaultval|0)+";";
return new Function('a', 'v', s);
}
return makeFunction().bind(self);

View File

@ -1,3 +1,24 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// https://dev.to/ndesmic/building-a-minimal-wasi-polyfill-for-browsers-4nel
// http://www.wasmtutor.com/webassembly-barebones-wasi
@ -154,7 +175,7 @@ export enum WASIErrors {
export class WASIFileDescriptor {
fdindex: number = -1;
data: Uint8Array = new Uint8Array(16);
protected data: Uint8Array = new Uint8Array(16);
flags: number = 0;
size: number = 0;
offset: number = 0;
@ -224,6 +245,8 @@ class WASIStreamingFileDescriptor extends WASIFileDescriptor {
export interface WASIFilesystem {
getFile(name: string) : WASIFileDescriptor;
getFiles() : WASIFileDescriptor[];
getDirectories() : WASIFileDescriptor[];
}
export class WASIMemoryFilesystem implements WASIFilesystem {
@ -239,6 +262,13 @@ export class WASIMemoryFilesystem implements WASIFilesystem {
}
putDirectory(name: string, rights?: number) {
if (!rights) rights = FDRights.PATH_OPEN | FDRights.PATH_CREATE_DIRECTORY | FDRights.PATH_CREATE_FILE;
if (name != '/' && name.endsWith('/')) name = name.substring(0, name.length - 1);
// add parent directory(s)
const parent = name.substring(0, name.lastIndexOf('/'));
if (parent && parent != name) {
this.putDirectory(parent, rights);
}
// add directory
const dir = new WASIFileDescriptor(name, FDType.DIRECTORY, rights);
this.dirs.set(name, dir);
return dir;
@ -261,6 +291,12 @@ export class WASIMemoryFilesystem implements WASIFilesystem {
}
return file;
}
getDirectories() {
return [...this.dirs.values()];
}
getFiles() {
return [...this.files.values()];
}
}
export class WASIRunner {

View File

@ -274,7 +274,8 @@ export class CodeProject {
var depfiles = [];
msg.updates.push({path:mainfilename, data:maintext});
this.filename2path[mainfilename] = this.mainPath;
let usesRemoteTool = this.getToolForFilename(mainfilename).startsWith('remote:');
const tool = this.getToolForFilename(this.mainPath);
let usesRemoteTool = tool.startsWith('remote:');
for (var dep of depends) {
// remote tools send both includes and linked files in one build step
if (!dep.link || usesRemoteTool) {

View File

@ -136,6 +136,7 @@ const TOOL_TO_SOURCE_STYLE = {
'ecs': 'ecs',
'remote:llvm-mos': 'text/x-csrc',
'cc7800': 'text/x-csrc',
'armtcc': 'text/x-csrc',
}
// TODO: move into tool class
@ -908,13 +909,15 @@ function hideDebugInfo() {
function showDebugInfo(state?) {
if (!isDebuggable(platform)) return;
var meminfo = $("#mem_info");
var meminfomsg = $("#mem_info_msg");
var allcats = platform.getDebugCategories();
if (allcats && !debugCategory)
debugCategory = allcats[0];
var s = state && platform.getDebugInfo(debugCategory, state);
if (s) {
if (typeof s === 'string') {
var hs = lastDebugInfo ? highlightDifferences(lastDebugInfo, s) : s;
meminfo.show().html(hs);
meminfo.show();
meminfomsg.html(hs);
var catspan = $('<div class="mem_info_links">');
var addCategoryLink = (cat:string) => {
var catlink = $('<a>'+cat+'</a>');
@ -931,8 +934,8 @@ function showDebugInfo(state?) {
for (var cat of allcats) {
addCategoryLink(cat);
}
meminfo.append('<br>');
meminfo.append(catspan);
meminfomsg.append('<br>');
meminfomsg.append(catspan);
lastDebugInfo = s;
} else {
hideDebugInfo();

View File

@ -23,7 +23,8 @@ export class MemoryView implements ProjectView {
dumplines;
maindiv : HTMLElement;
recreateOnResize = true;
totalRows = 0x1400;
hibits = 0; // a hack to make it work with 32-bit addresses
totalRows = 0x1400; // a little more room in case we split lots of lines
createDiv(parent : HTMLElement) {
var div = document.createElement('div');
@ -44,7 +45,7 @@ export class MemoryView implements ProjectView {
var linediv = document.createElement("div");
if (this.dumplines) {
var dlr = this.dumplines[row];
if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a));
if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a | this.hibits));
}
linediv.appendChild(document.createTextNode(s));
return linediv;
@ -59,7 +60,8 @@ export class MemoryView implements ProjectView {
scrollToAddress(addr : number) {
if (this.dumplines) {
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr));
this.hibits = addr & 0xffff0000;
this.memorylist.scrollToItem(this.findMemoryWindowLine(addr & 0xffff));
}
}
@ -101,7 +103,7 @@ export class MemoryView implements ProjectView {
for (var i=0; i<n1; i++) s += ' ';
if (n1 > 8) s += ' ';
for (var i=n1; i<n2; i++) {
var read = this.readAddress(offset+i);
var read = this.readAddress((offset+i) | this.hibits);
if (i==8) s += ' ';
s += ' ' + (typeof read == 'number' ? hex(read,2) : '??');
}
@ -130,7 +132,7 @@ export class MemoryView implements ProjectView {
var sym;
for (const _nextofs of Object.keys(addr2sym)) {
var nextofs = parseInt(_nextofs); // convert from string (stupid JS)
var nextsym = addr2sym[nextofs];
var nextsym = addr2sym[nextofs | this.hibits];
if (sym) {
// ignore certain symbols
if (ignoreSymbol(sym)) {

View File

@ -1,6 +1,27 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { ARM32CPU, ARMCoreState } from "../common/cpu/ARM";
import { BasicScanlineMachine, HasSerialIO, SerialEvent, SerialIOInterface } from "../common/devices";
import { BasicScanlineMachine, Bus32, HasSerialIO, SerialEvent, SerialIOInterface } from "../common/devices";
import { newAddressDecoder, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
import { Debuggable, EmuState } from "../common/baseplatform";
import { hex, lpad } from "../common/util";
@ -18,36 +39,41 @@ var GBA_KEYCODE_MAP = makeKeycodeMap([
[Keys.DOWN, 0, 0x80],
]);
const ROM_START = 0x0;
const ROM_SIZE = 0x80000;
const RAM_START = 0x2000000;
const RAM_SIZE = 0x80000;
const RAM_START = 0x0;
const RAM_SIZE = 0x100000;
const ROM_BASE = 0x0;
const IO_START = 0x4000000;
const IO_SIZE = 0x100;
const MAX_SERIAL_CHARS = 1000000;
const CPU_FREQ = 4000000; // 4 MHz
export class ARM32Machine extends BasicScanlineMachine implements Debuggable, HasSerialIO {
const ILLEGAL_OPCODE = 0xedededed;
export class ARM32Machine extends BasicScanlineMachine
implements Debuggable, HasSerialIO, Bus32 {
cpuFrequency = CPU_FREQ; // MHz
canvasWidth = 160;
numTotalScanlines = 256;
numVisibleScanlines = 128;
cpuCyclesPerLine = Math.floor(CPU_FREQ / (256*60));
defaultROMSize = 512*1024;
defaultROMSize = RAM_SIZE - ROM_BASE;
sampleRate = 1;
cpu: ARM32CPU = new ARM32CPU();
ram = new Uint8Array(96*1024);
ram = new Uint8Array(RAM_SIZE);
ram16 = new Uint16Array(this.ram.buffer);
ram32 = new Uint32Array(this.ram.buffer);
pixels32 : Uint32Array;
pixels8 : Uint8Array;
vidbase : number = 0;
rombase : number = ROM_BASE;
brightness : number = 255;
serial : SerialIOInterface;
serialOut : SerialEvent[];
serialIn : SerialEvent[];
ioregs = new Uint8Array(IO_SIZE);
ioregs32 = new Uint32Array(this.ioregs.buffer);
constructor() {
super();
@ -65,7 +91,15 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
this.serial = serial;
}
loadROM(rom: Uint8Array) {
super.loadROM(rom);
}
reset() {
this.ram.fill(0);
if (this.rom) {
this.ram.set(this.rom, this.rombase);
}
super.reset();
this.serialOut = [];
this.serialIn = [];
@ -74,19 +108,13 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
// TODO: 32-bit bus?
read = newAddressDecoder([
[ROM_START, ROM_START+ROM_SIZE-1, ROM_SIZE-1, (a) => {
return this.rom ? this.rom[a] : 0;
}],
[RAM_START, RAM_START+RAM_SIZE-1, RAM_SIZE-1, (a) => {
return this.ram[a];
}],
[IO_START, IO_START+IO_SIZE-1, IO_SIZE-1, (a, v) => {
return this.readIO(a);
}],
[0, (1<<31)-1, 0, (a, v) => {
throw new EmuHalt(`Address read out of bounds: 0x${hex(a)}`);
}]
]);
], {defaultval: ILLEGAL_OPCODE & 0xff});
write = newAddressDecoder([
[RAM_START, RAM_START+RAM_SIZE-1, RAM_SIZE-1, (a, v) => {
@ -97,10 +125,42 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}],
]);
read32 = (a) => {
if (a >= RAM_START && a < RAM_SIZE && (a & 3) == 0) {
return this.ram32[a >> 2];
} else {
return this.read(a) | (this.read(a+1)<<8) | (this.read(a+2)<<16) | (this.read(a+3)<<24);
}
};
write32 = (a, v) => {
if (a >= RAM_START && a < RAM_SIZE && (a & 3) == 0) {
this.ram32[a >> 2] = v;
} else {
this.write(a, v & 0xff);
this.write(a+1, (v>>8) & 0xff);
this.write(a+2, (v>>16) & 0xff);
this.write(a+3, (v>>24) & 0xff);
}
}
readAddress(a : number) : number {
if (a >= RAM_START && a < RAM_START+RAM_SIZE) return this.read(a);
else return ILLEGAL_OPCODE;
}
readIO(a : number) : number {
switch (a) {
case 0x0:
return this.inputs[0];
case 0x20:
return this.getRasterY() & 0xff;
case 0x21:
return this.getRasterY() >> 8;
case 0x24:
return this.getRasterX();
case 0x25:
return this.getRasterX() >> 8;
case 0x40:
return (this.serial.byteAvailable() ? 0x80 : 0) | (this.serial.clearToSend() ? 0x40 : 0);
case 0x44:
@ -116,16 +176,14 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}
writeIO(a : number, v : number) : void {
this.ioregs[a] = v;
switch (a) {
case 0x0:
//this.brightness = v & 0xff;
break;
case 0x48:
if (this.serialOut.length < MAX_SERIAL_CHARS) {
this.serialOut.push({op:'write', value:v, nbits:8});
}
break;
}
}
}
startScanline() {
@ -136,7 +194,8 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
postFrame() {
var p32 = this.pixels32;
var vbase = (this.vidbase >> 1) & 0xfffff;
const vidbase = this.ioregs32[0x80 >> 2];
var vbase = (vidbase >> 1) & 0xfffff;
var mask = this.brightness << 24;
for (var i=0; i<p32.length; i++) {
var col = this.ram16[i + vbase];
@ -147,18 +206,34 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
}
getDebugCategories() {
return ['CPU', 'Stack'];
return ['CPU', 'Stack', 'FPU'];
}
getDebugInfo?(category: string, state: EmuState) : string {
switch (category) {
case 'Stack':
var s = '';
var c = state.c as ARMCoreState;
var sp = c.gprs[13];
var fp = c.gprs[11];
// dump stack using ram32
for (var i=0; i<16; i++) {
s += hex(sp,8) + ' ' + hex(this.ram32[(sp-RAM_START)>>2],8);
if (sp == fp) s += ' FP';
s += '\n';
sp += 4;
if (sp >= RAM_START+RAM_SIZE) break;
}
return s;
case 'CPU':
var s = '';
var c = state.c as ARMCoreState;
const EXEC_MODE = {2:'Thumb',4:'ARM'};
const REGNAMES = {15:'PC',14:'LR',13:'SP',12:'IP',11:'FP',9:'SB'};
for (var i=0; i<16; i++) {
s += lpad(REGNAMES[i]||'',3) + lpad('r'+i, 5) + ' ' + hex(c.gprs[i],8) + '\n';
for (var i=0; i<8; i++) {
let j = i+8;
s += lpad('r'+i, 5) + ' ' + hex(c.gprs[i],8) + ' ';
s += lpad('r'+j, 5) + ' ' + hex(c.gprs[j],8) + lpad(REGNAMES[j]||'',3) + '\n';
}
s += 'Flags ';
s += c.cpsrN ? " N" : " -";
@ -172,6 +247,19 @@ export class ARM32Machine extends BasicScanlineMachine implements Debuggable, Ha
s += 'SPSR ' + hex(c.spsr,8) + '\n';
s += 'cycl ' + c.cycles + '\n';
return s;
case 'FPU':
var s = '';
var c = state.c as ARMCoreState;
for (var i=0; i<16; i++) {
//let j = i+16;
s += lpad('s'+i, 5) + ' ' + hex(c.ifprs[i],8) + ' ' + c.sfprs[i].toPrecision(6);
if (i & 1) {
s += lpad('d'+(i>>1), 5) + ' ' + c.dfprs[i>>1].toPrecision(12);
}
s += '\n';
//s += lpad('s'+j, 5) + ' ' + lpad(c.sfprs[j]+'',8) + '\n';
}
return s;
}
}

View File

@ -1,40 +1,49 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { BaseDebugPlatform, CpuState, EmuState, Platform, DisasmLine, Debuggable, Machine, BaseMachinePlatform } from "../common/baseplatform";
import { AnimationTimer, EmuHalt, padBytes, PLATFORMS, RasterVideo } from "../common/emu";
import { hex, lpad, loadScript } from "../common/util";
import { ARM32CPU } from "../common/cpu/ARM";
import { Platform, DisasmLine, Machine, BaseMachinePlatform } from "../common/baseplatform";
import { PLATFORMS } from "../common/emu";
import { loadScript } from "../common/util";
import { ARM32Machine } from "../machine/arm32";
declare var uc, cs : any; // Unicorn module
declare var cs : any; // Unicorn module
const ARM32_PRESETS = [
{ id: 'vidfill.vasm', name: 'Video Memory Fill' },
{ id: 'vidfill.c', name: 'Video Memory Fill' },
];
const SCREEN_WIDTH = 160;
const SCREEN_HEIGHT = 128;
const ROM_START_ADDR = 0x0;
const HIROM_START_ADDR = 0xff800000;
const ROM_SIZE = 512*1024;
const RAM_START_ADDR = 0x20000000;
const RAM_SIZE = 512*1024;
const CLOCKS_PER_FRAME = 10000;
interface ARM32State extends EmuState {
r: Uint32Array; // registers
}
export abstract class BaseARMMachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
//getOpcodeMetadata = getOpcodeMetadata_z80;
getToolForFilename(fn: string) {
fn = fn.toLowerCase();
if (fn.endsWith('.vasm')) return "vasmarm";
else if (fn.endsWith('.armips')) return "armips";
else return "vasmarm";
if (fn.endsWith('.armips')) return "armips";
if (fn.endsWith('.c')) return "armtcc";
if (fn.endsWith('.s')) return "armtcc";
return "armtcc";
}
getPresets() { return ARM32_PRESETS; }
getDefaultExtension() { return ".vasm"; };
getDefaultExtension() { return ".c"; };
}
class ARM32Platform extends BaseARMMachinePlatform<ARM32Machine> implements Platform {
@ -53,10 +62,16 @@ class ARM32Platform extends BaseARMMachinePlatform<ARM32Machine> implements Plat
newMachine() { return new ARM32Machine(); }
readAddress(a) { return this.machine.read(a); }
getMemoryMap = function() { return { main:[
{name:'ROM',start:0x0000000,size:0x80000,type:'rom'},
{name:'RAM',start:0x2000000,size:0x80000,type:'ram'},
{name:'ROM',start:0x0000000,size:0x100000,type:'ram'},
{name:'I/O',start:0x4000000,size:0x100,type:'io'},
] } };
getPlatformName() { return "ARM7"; }
getDebugTree() {
return {
...this.machine.cpu.getDebugTree(),
dwarf: this.debugSymbols.debuginfo
}
}
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
var is_thumb = this.machine.cpu.isThumb();
var capstone = is_thumb ? this.capstone_thumb : this.capstone_arm;

63
src/test/testelfparser.ts Normal file
View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import assert from "assert";
import { DWARFParser, ELFParser } from "../common/binutils";
describe('test ELFParser', () => {
const fs = require('fs');
const data = fs.readFileSync('./test/exes/arm32.elf');
const elfParser = new ELFParser(new Uint8Array(data));
it('should parse sections and symbols', () => {
/*
elfParser.sectionHeaders.forEach((section, index) => {
console.log('section', index, section.name, section.type, section.vaddr.toString(16), section.size.toString(16));
});
elfParser.getSymbols().forEach((symbol, index) => {
console.log('symbol', index, symbol.info, symbol.other, symbol.name, symbol.value.toString(16));
});
*/
assert.strictEqual(21, elfParser.sectionHeaders.length);
assert.strictEqual(29, elfParser.getSymbols().length);
assert.ok(elfParser.sectionHeaders.find((section) => section.name === '.text') != null);
assert.ok(elfParser.getSymbols().find((symbol) => symbol.name === 'main') != null);
});
it('should parse DWARF info', () => {
const dwarf = new DWARFParser(elfParser);
assert.strictEqual(2, dwarf.units.length);
const cu = dwarf.units[0];
// TODO: check info content
const li = dwarf.lineInfos[0];
assert.strictEqual('crt0.c', li.files[1].name);
/*
assert.ok(info != null);
assert.ok(info!.lineNumberProgram != null);
assert.ok(info!.lineNumberProgram!.length > 0);
assert.ok(info!.lineNumberProgram![0].file != null);
assert.ok(info!.lineNumberProgram![0].file!.name != null);
assert.ok(info!.lineNumberProgram![0].file!.name!.length > 0);
*/
});
});

BIN
src/worker/fs/arm32-fs.zip Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
extern void _end;
void malloc_addblock(void* addr, int size);
int entry();
__attribute__((weak, naked, noinline, noreturn)) void _start() {
// set bss segment symbols
asm(".global __bss_start__, __bss_end__");
asm("__bss_start__ = _edata");
asm("__bss_end__ = _end");
// set stack pointer
asm("mov sp, #0x100000");
// allocate heap block
malloc_addblock(&_end, (void*)0xf0000 - &_end);
// run main()
entry();
// wait for next video frame
while (*(volatile int*)0x4000020 != 0) { }
// halt cpu
asm(".long 0xe7f000f0"); // udf #0
}
void _Exit(int ec) {
asm(".long 0xe7f000f0"); // udf #0
}

BIN
src/worker/lib/arm32/libc.a Normal file

Binary file not shown.

View File

@ -1,3 +1,24 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
export var PLATFORM_PARAMS = {
'vcs': {
@ -334,6 +355,13 @@ export var PLATFORM_PARAMS = {
extra_link_files: ['crt0.o', 'exidy.cfg'],
//extra_compile_files: ['exidy.h'],
},
'arm32': {
arch: 'arm32',
define: ['__ARM__', 'DISABLE_UNIMPLEMENTED_LIBC_APIS', 'PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT'],
extra_compile_args: ['-I./arch/arm/include', '-I./openlibm/include', '-I./openlibm/src', '-I./printf/src'],
extra_link_files: ['crt0.c', 'libc.a'],
extra_link_args: ['crt0.c', '-lc'],
},
};
PLATFORM_PARAMS['sms-sms-libcv'] = PLATFORM_PARAMS['sms-sg1000-libcv'];

View File

@ -1,8 +1,32 @@
/*
* Copyright (c) 2024 Steven E. Hugg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import { DWARFParser, ELFParser } from "../../common/binutils";
import { hex } from "../../common/util";
import { WASIFilesystem } from "../../common/wasi/wasishim";
import { CodeListingMap, SourceLine, WorkerError, WorkerResult } from "../../common/workertypes";
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, populateFiles, putWorkFile, anyTargetChanged, getPrefix, getWorkFileAsString } from "../builder";
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, populateFiles, putWorkFile, anyTargetChanged, getPrefix, getWorkFileAsString, populateExtraFiles, processEmbedDirective } from "../builder";
import { makeErrorMatcher, re_crlf } from "../listingutils";
import { loadWASIFilesystemZip } from "../wasiutils";
import { loadNative, moduleInstFn, execMain, emglobal, EmscriptenModule } from "../wasmutils";
export function assembleARMIPS(step: BuildStep): WorkerResult {
@ -239,3 +263,191 @@ export function assembleVASMARM(step: BuildStep): BuildStepResult {
}
}
function tccErrorMatcher(errors: WorkerError[], mainpath: string) {
return makeErrorMatcher(errors, /([^:]+|tcc):(\d+|\s*error): (.+)/, 2, 3, mainpath, 1);;
}
let armtcc_fs: WASIFilesystem | null = null;
export async function compileARMTCC(step: BuildStep): Promise<BuildStepResult> {
loadNative("arm-tcc");
const params = step.params;
const errors = [];
gatherFiles(step, { mainFilePath: "main.c" });
const objpath = step.prefix + ".o";
const error_fn = tccErrorMatcher(errors, step.path);
if (!armtcc_fs) {
armtcc_fs = await loadWASIFilesystemZip("arm32-fs.zip");
}
if (staleFiles(step, [objpath])) {
const armtcc: EmscriptenModule = await emglobal.armtcc({
instantiateWasm: moduleInstFn('arm-tcc'),
noInitialRun: true,
print: error_fn,
printErr: error_fn,
});
var args = ['-c', '-I.', '-I./include',
//'-std=c11',
'-funsigned-char',
//'-Wwrite-strings',
'-gdwarf-2',
'-o', objpath];
if (params.define) {
params.define.forEach((x) => args.push('-D' + x));
}
if (params.extra_compile_args) {
args = args.concat(params.extra_compile_args);
}
args.push(step.path);
const FS = armtcc.FS;
// TODO: only should do once?
armtcc_fs.getDirectories().forEach((dir) => {
if (dir.name != '/') FS.mkdir(dir.name);
});
armtcc_fs.getFiles().forEach((file) => {
FS.writeFile(file.name, file.getBytes(), { encoding: 'binary' });
});
populateExtraFiles(step, FS, params.extra_compile_files);
populateFiles(step, FS, {
mainFilePath: step.path,
processFn: (path, code) => {
if (typeof code === 'string') {
code = processEmbedDirective(code);
}
return code;
}
});
execMain(step, armtcc, args);
if (errors.length)
return { errors: errors };
var objout = FS.readFile(objpath, { encoding: 'binary' }) as Uint8Array;
putWorkFile(objpath, objout);
}
return {
linktool: "armtcclink",
files: [objpath],
args: [objpath]
}
}
export async function linkARMTCC(step: BuildStep): Promise<WorkerResult> {
loadNative("arm-tcc");
const params = step.params;
const errors = [];
gatherFiles(step, { mainFilePath: "main.c" });
const objpath = "main.elf";
const error_fn = tccErrorMatcher(errors, step.path);
if (staleFiles(step, [objpath])) {
const armtcc: EmscriptenModule = await emglobal.armtcc({
instantiateWasm: moduleInstFn('arm-tcc'),
noInitialRun: true,
print: error_fn,
printErr: error_fn,
});
var args = ['-L.', '-nostdlib', '-nostdinc',
'-Wl,--oformat=elf32-arm',
//'-Wl,-section-alignment=0x100000',
'-gdwarf-2',
'-o', objpath];
if (params.define) {
params.define.forEach((x) => args.push('-D' + x));
}
args = args.concat(step.files);
if (params.extra_link_args) {
args = args.concat(params.extra_link_args);
}
const FS = armtcc.FS;
populateExtraFiles(step, FS, params.extra_link_files);
populateFiles(step, FS);
execMain(step, armtcc, args);
if (errors.length)
return { errors: errors };
var objout = FS.readFile(objpath, { encoding: 'binary' }) as Uint8Array;
putWorkFile(objpath, objout);
if (!anyTargetChanged(step, [objpath]))
return;
// parse ELF and create ROM
const elfparser = new ELFParser(objout);
let maxaddr = 0;
elfparser.sectionHeaders.forEach((section, index) => {
maxaddr = Math.max(maxaddr, section.vmaddr + section.size);
});
let rom = new Uint8Array(maxaddr);
elfparser.sectionHeaders.forEach((section, index) => {
if (section.flags & 0x2) {
let data = objout.slice(section.offset, section.offset + section.size);
//console.log(section.name, section.vmaddr.toString(16), data);
rom.set(data, section.vmaddr);
}
});
// set vectors, entry point etc
const obj32 = new Uint32Array(rom.buffer);
const start = elfparser.entry;
obj32[0] = start; // set reset vector
obj32[1] = start; // set undefined vector
obj32[2] = start; // set swi vector
obj32[3] = start; // set prefetch abort vector
obj32[4] = start; // set data abort vector
obj32[5] = start; // set reserved vector
obj32[6] = start; // set irq vector
obj32[7] = start; // set fiq vector
let symbolmap = {};
elfparser.getSymbols().forEach((symbol, index) => {
symbolmap[symbol.name] = symbol.value;
});
let segments = [];
elfparser.sectionHeaders.forEach((section, index) => {
if ((section.flags & 0x2) && section.size) {
segments.push({
name: section.name,
start: section.vmaddr,
size: section.size,
type: section.type,
});
}
});
const listings: CodeListingMap = {};
const dwarf = new DWARFParser(elfparser);
dwarf.lineInfos.forEach((lineInfo) => {
lineInfo.files.forEach((file) => {
if (!file || !file.lines) return;
file.lines.forEach((line) => {
const filename = line.file;
const offset = line.address;
const path = getPrefix(filename) + '.lst';
const linenum = line.line;
let lst = listings[path];
if (lst == null) { lst = listings[path] = { lines: [] }; }
lst.lines.push({
path,
line: linenum,
offset
});
});
});
});
//console.log(listings);
return {
output: rom, //.slice(0x34),
listings: listings,
errors: errors,
symbolmap: symbolmap,
segments: segments,
debuginfo: dwarf
};
}
}

File diff suppressed because one or more lines are too long

BIN
src/worker/wasm/arm-tcc.wasm Executable file

Binary file not shown.

View File

@ -52,6 +52,8 @@ export const TOOLS = {
'ecs': ecs.assembleECS,
'remote': remote.buildRemote,
'cc7800': cc7800.compileCC7800,
'armtcc': arm.compileARMTCC,
'armtcclink': arm.linkARMTCC,
}
export const TOOL_PRELOADFS = {

BIN
test/exes/arm32.elf Executable file

Binary file not shown.