2021-06-06 05:50:45 +00:00
|
|
|
|
|
|
|
import { ARM32CPU, ARMCoreState } from "../common/cpu/ARM";
|
2021-06-14 19:38:51 +00:00
|
|
|
import { BasicScanlineMachine, HasSerialIO, SerialEvent, SerialIOInterface } from "../common/devices";
|
2021-06-18 14:08:25 +00:00
|
|
|
import { newAddressDecoder, Keys, makeKeycodeMap, newKeyboardHandler, EmuHalt } from "../common/emu";
|
2021-06-06 05:50:45 +00:00
|
|
|
import { Debuggable, EmuState } from "../common/baseplatform";
|
2021-06-14 19:38:51 +00:00
|
|
|
import { hex, lpad } from "../common/util";
|
2021-06-06 05:50:45 +00:00
|
|
|
|
2021-06-12 16:29:49 +00:00
|
|
|
var GBA_KEYCODE_MAP = makeKeycodeMap([
|
|
|
|
[Keys.A, 0, 0x1],
|
|
|
|
[Keys.B, 0, 0x2],
|
|
|
|
[Keys.SELECT,0, 0x4],
|
|
|
|
[Keys.START ,0, 0x8],
|
|
|
|
[Keys.RIGHT, 0, 0x10],
|
|
|
|
[Keys.LEFT, 0, 0x20],
|
|
|
|
[Keys.UP, 0, 0x40],
|
|
|
|
[Keys.DOWN, 0, 0x80],
|
2021-06-06 05:50:45 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
const ROM_START = 0x0;
|
|
|
|
const ROM_SIZE = 0x80000;
|
2021-06-12 16:29:49 +00:00
|
|
|
const RAM_START = 0x2000000;
|
2021-06-06 05:50:45 +00:00
|
|
|
const RAM_SIZE = 0x80000;
|
2021-06-12 16:29:49 +00:00
|
|
|
const IO_START = 0x4000000;
|
|
|
|
const IO_SIZE = 0x100;
|
2021-06-14 19:38:51 +00:00
|
|
|
const MAX_SERIAL_CHARS = 1000000;
|
2021-06-06 05:50:45 +00:00
|
|
|
|
2021-06-10 22:04:15 +00:00
|
|
|
const CPU_FREQ = 4000000; // 4 MHz
|
2021-06-06 05:50:45 +00:00
|
|
|
|
2021-06-14 19:38:51 +00:00
|
|
|
export class ARM32Machine extends BasicScanlineMachine implements Debuggable, HasSerialIO {
|
2021-06-06 05:50:45 +00:00
|
|
|
|
|
|
|
cpuFrequency = CPU_FREQ; // MHz
|
|
|
|
canvasWidth = 160;
|
|
|
|
numTotalScanlines = 256;
|
|
|
|
numVisibleScanlines = 128;
|
|
|
|
cpuCyclesPerLine = Math.floor(CPU_FREQ / (256*60));
|
|
|
|
defaultROMSize = 512*1024;
|
|
|
|
sampleRate = 1;
|
|
|
|
|
|
|
|
cpu: ARM32CPU = new ARM32CPU();
|
2021-06-10 22:04:15 +00:00
|
|
|
ram = new Uint8Array(96*1024);
|
|
|
|
ram16 = new Uint16Array(this.ram.buffer);
|
|
|
|
pixels32 : Uint32Array;
|
2021-06-06 05:50:45 +00:00
|
|
|
pixels8 : Uint8Array;
|
2021-06-10 22:04:15 +00:00
|
|
|
vidbase : number = 0;
|
|
|
|
brightness : number = 255;
|
2021-06-14 19:38:51 +00:00
|
|
|
serial : SerialIOInterface;
|
|
|
|
serialOut : SerialEvent[];
|
|
|
|
serialIn : SerialEvent[];
|
2021-06-06 05:50:45 +00:00
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.connectCPUMemoryBus(this);
|
2021-06-12 16:29:49 +00:00
|
|
|
this.handler = newKeyboardHandler(this.inputs, GBA_KEYCODE_MAP);
|
2021-06-06 05:50:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
connectVideo(pixels:Uint32Array) : void {
|
|
|
|
super.connectVideo(pixels);
|
2021-06-10 22:04:15 +00:00
|
|
|
this.pixels32 = pixels;
|
2021-06-06 05:50:45 +00:00
|
|
|
this.pixels8 = new Uint8Array(pixels.buffer);
|
|
|
|
}
|
|
|
|
|
2021-06-14 19:38:51 +00:00
|
|
|
connectSerialIO(serial: SerialIOInterface) {
|
|
|
|
this.serial = serial;
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
|
|
|
super.reset();
|
|
|
|
this.serialOut = [];
|
|
|
|
this.serialIn = [];
|
|
|
|
}
|
|
|
|
|
2021-06-10 22:04:15 +00:00
|
|
|
// TODO: 32-bit bus?
|
|
|
|
|
2021-06-06 05:50:45 +00:00
|
|
|
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];
|
|
|
|
}],
|
2021-06-12 16:29:49 +00:00
|
|
|
[IO_START, IO_START+IO_SIZE-1, IO_SIZE-1, (a, v) => {
|
|
|
|
return this.readIO(a);
|
2021-06-06 05:50:45 +00:00
|
|
|
}],
|
2021-06-18 14:08:25 +00:00
|
|
|
[0, (1<<31)-1, 0, (a, v) => {
|
|
|
|
throw new EmuHalt(`Address read out of bounds: 0x${hex(a)}`);
|
|
|
|
}]
|
2021-06-06 05:50:45 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
write = newAddressDecoder([
|
|
|
|
[RAM_START, RAM_START+RAM_SIZE-1, RAM_SIZE-1, (a, v) => {
|
|
|
|
this.ram[a] = v;
|
|
|
|
}],
|
2021-06-12 16:29:49 +00:00
|
|
|
[IO_START, IO_START+IO_SIZE-1, IO_SIZE-1, (a, v) => {
|
|
|
|
this.writeIO(a, v);
|
|
|
|
}],
|
2021-06-06 05:50:45 +00:00
|
|
|
]);
|
|
|
|
|
2021-06-12 16:29:49 +00:00
|
|
|
readIO(a : number) : number {
|
|
|
|
switch (a) {
|
|
|
|
case 0x0:
|
|
|
|
return this.inputs[0];
|
2021-06-14 19:38:51 +00:00
|
|
|
case 0x40:
|
|
|
|
return (this.serial.byteAvailable() ? 0x80 : 0) | (this.serial.clearToSend() ? 0x40 : 0);
|
|
|
|
case 0x44:
|
|
|
|
let evin = this.serialIn.shift();
|
|
|
|
if (evin != null) {
|
|
|
|
this.serialOut.push(evin);
|
|
|
|
return evin.value;
|
|
|
|
} else
|
|
|
|
return 0;
|
2021-06-12 16:29:49 +00:00
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writeIO(a : number, v : number) : void {
|
|
|
|
switch (a) {
|
|
|
|
case 0x0:
|
|
|
|
//this.brightness = v & 0xff;
|
|
|
|
break;
|
2021-06-14 19:38:51 +00:00
|
|
|
case 0x48:
|
|
|
|
if (this.serialOut.length < MAX_SERIAL_CHARS) {
|
|
|
|
this.serialOut.push({op:'write', value:v, nbits:8});
|
|
|
|
}
|
|
|
|
break;
|
2021-06-12 16:29:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-06 05:50:45 +00:00
|
|
|
startScanline() {
|
|
|
|
}
|
|
|
|
|
|
|
|
drawScanline() {
|
|
|
|
}
|
|
|
|
|
2021-06-10 22:04:15 +00:00
|
|
|
postFrame() {
|
|
|
|
var p32 = this.pixels32;
|
|
|
|
var vbase = (this.vidbase >> 1) & 0xfffff;
|
|
|
|
var mask = this.brightness << 24;
|
|
|
|
for (var i=0; i<p32.length; i++) {
|
|
|
|
var col = this.ram16[i + vbase];
|
|
|
|
// rrrrrgggggbbbbb0 ->
|
|
|
|
// 000rrrrr000ggggg000bbbbb00011111111
|
|
|
|
p32[i] = mask | ((col&31)<<3) | (((col>>5)&31)<<11) | (((col>>10)&31)<<19);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-06 05:50:45 +00:00
|
|
|
getDebugCategories() {
|
2021-06-18 14:08:25 +00:00
|
|
|
return ['CPU', 'Stack'];
|
2021-06-06 05:50:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getDebugInfo?(category: string, state: EmuState) : string {
|
2021-06-12 16:29:49 +00:00
|
|
|
switch (category) {
|
|
|
|
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';
|
|
|
|
}
|
|
|
|
s += 'Flags ';
|
|
|
|
s += c.cpsrN ? " N" : " -";
|
|
|
|
s += c.cpsrV ? " V" : " -";
|
|
|
|
s += c.cpsrF ? " F" : " -";
|
|
|
|
s += c.cpsrZ ? " Z" : " -";
|
|
|
|
s += c.cpsrC ? " C" : " -";
|
|
|
|
s += c.cpsrI ? " I" : " -";
|
|
|
|
s += '\n';
|
|
|
|
s += 'MODE ' + EXEC_MODE[c.instructionWidth] + ' ' + MODE_NAMES[c.mode] + '\n';
|
|
|
|
s += 'SPSR ' + hex(c.spsr,8) + '\n';
|
|
|
|
s += 'cycl ' + c.cycles + '\n';
|
|
|
|
return s;
|
2021-06-06 05:50:45 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-14 19:38:51 +00:00
|
|
|
|
|
|
|
saveState() {
|
|
|
|
var state = super.saveState() as any;
|
|
|
|
state.serial = {
|
|
|
|
sin: this.serialIn.slice(0),
|
|
|
|
sout : this.serialOut.slice(0)
|
|
|
|
}
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
loadState(state) {
|
|
|
|
super.loadState(state);
|
|
|
|
this.serialIn = state.serial.sin;
|
|
|
|
this.serialOut = state.serial.sout;
|
|
|
|
}
|
2021-06-06 05:50:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const MODE_NAMES = {
|
|
|
|
0x10: "USER",
|
|
|
|
0x11: "FIQ",
|
|
|
|
0x12: "IRQ",
|
|
|
|
0x13: "SUPERVISOR",
|
|
|
|
0x17: "ABORT",
|
|
|
|
0x1b: "UNDEFINED",
|
|
|
|
0x1f: "SYSTEM",
|
|
|
|
};
|