working on new emulator framework

This commit is contained in:
Steven Hugg 2019-08-21 22:55:32 -04:00
parent 9de22d6389
commit 0f63282931
8 changed files with 3349 additions and 2 deletions

View File

@ -542,6 +542,9 @@ function require(modname) {
<script src="gen/ui.js"></script>
<!-- <script src="src/audio/votrax.js"></script> -->
<!-- <script src="local/lzg.js"></script> -->
<script src="gen/nemu/nemu.js"></script>
<script src="gen/nemu/cpu/MOS6502.js"></script>
<script src="gen/nemu/machine/apple2.js"></script>
<script>
// submenus open on click + hover

View File

@ -125,7 +125,7 @@ export class RasterVideo {
getContext() { return this.ctx; }
updateFrame(sx:number, sy:number, dx:number, dy:number, w?:number, h?:number) {
updateFrame(sx?:number, sy?:number, dx?:number, dy?:number, w?:number, h?:number) {
if (w && h)
this.ctx.putImageData(this.imageData, sx, sy, dx, dy, w, h);
else

1949
src/nemu/cpu/MOS6502.ts Normal file

File diff suppressed because it is too large Load Diff

1103
src/nemu/machine/apple2.ts Normal file

File diff suppressed because it is too large Load Diff

165
src/nemu/nemu.ts Normal file
View File

@ -0,0 +1,165 @@
export interface SavesState<T> {
saveState() : T;
loadState(state:T) : void;
}
export interface Bus {
read(a:number) : number;
write(a:number, v:number) : void;
// TODO: readConst?(a:number) : number;
}
export interface ClockBased {
advanceClock() : void;
}
export interface InstructionBased {
advanceInsn() : number;
}
export interface FrameBased {
advanceFrame() : number;
}
export interface VideoFrameBased extends FrameBased {
getVideoParams() : VideoParams;
connectVideo(pixels:Uint32Array) : void;
}
export interface RasterFrameBased extends VideoFrameBased {
}
export interface VideoParams {
width : number;
height : number;
overscan? : boolean;
rotate? : number;
}
// TODO: frame buffer optimization (apple2, etc)
export interface SampledAudioParams {
sampleRate : number;
stereo : boolean;
}
export interface SampledAudio {
setAudioParams(params:SampledAudioParams) : void;
sendAudioFrame(samples:Uint16Array) : void;
}
export interface AcceptsROM {
loadROM(data:Uint8Array, title?:string) : void;
}
export interface Resettable {
reset() : void;
}
export interface MemoryBusConnected {
connectMemoryBus(bus:Bus) : void;
}
export interface CPU extends MemoryBusConnected, Resettable {
getPC() : number;
getSP() : number;
}
export interface Interruptable<T> {
interrupt(type:T) : void;
}
// TODO
export interface AcceptsInput {
setInput(key:number, code:number, flags:number) : void;
//loadControlState();
//saveControlState();
}
// TODO?
export function noise(x : number) : number {
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return x;
}
/// HOOKS
export interface Hook<T> {
//target : T;
unhook();
}
export class BusHook implements Hook<Bus> {
//target : Bus;
constructor(bus : Bus, profiler : ProfilerInterface) {
//this.target = bus;
var oldread = bus.read.bind(bus);
var oldwrite = bus.write.bind(bus);
bus.read = (a:number):number => {
profiler.logRead(a);
var val = oldread(a);
return val;
}
bus.write = (a:number,v:number) => {
profiler.logWrite(a);
oldwrite(a,v);
}
this.unhook = () => {
bus.read = oldread;
bus.write = oldwrite;
}
}
unhook : () => void;
}
export class CPUClockHook implements Hook<CPU&ClockBased> {
//target : CPU&ClockBased;
constructor(cpu : CPU&ClockBased, profiler : ProfilerInterface) {
//this.target = cpu;
var oldclock = cpu.advanceClock.bind(cpu);
cpu.advanceClock = () => {
profiler.logExecute(cpu.getPC());
return oldclock();
}
this.unhook = () => {
cpu.advanceClock = oldclock;
}
}
unhook : () => void;
}
export class CPUInsnHook implements Hook<CPU&InstructionBased> {
//target : CPU&InstructionBased;
constructor(cpu : CPU&InstructionBased, profiler : ProfilerInterface) {
//this.target = cpu;
var oldinsn = cpu.advanceInsn.bind(cpu);
cpu.advanceInsn = () => {
profiler.logExecute(cpu.getPC());
return oldinsn();
}
this.unhook = () => {
cpu.advanceInsn = oldinsn;
}
}
unhook : () => void;
}
/// PROFILER
export interface ProfilerInterface {
logExecute(address:number);
logRead(address:number);
logWrite(address:number);
logIORead(address:number);
logIOWrite(address:number);
logInterrupt(type:number);
}
/// DEBUGGING
class EmuBreakpoint extends Error {
}

View File

@ -1065,5 +1065,64 @@ class Apple2MAMEPlatform extends BaseMAMEPlatform implements Platform {
}
}
///
// TODO: move to own place, debugging
import { CPU, Bus, ClockBased, SavesState, Interruptable } from "../nemu/nemu";
import { MOS6502 } from "../nemu/cpu/MOS6502";
import { AppleII } from "../nemu/machine/apple2";
class NewApple2Platform extends Base6502Platform implements Platform {
mainElement : HTMLElement;
machine : AppleII;
timer : AnimationTimer;
video : RasterVideo;
constructor(mainElement : HTMLElement) {
super();
this.mainElement = mainElement;
}
getOpcodeMetadata = getOpcodeMetadata_6502;
getDefaultExtension () { return ".c"; };
getToolForFilename = getToolForFilename_6502;
getPresets () { return APPLE2_PRESETS; }
getCPUState() { return this.machine.cpu.saveState(); }
saveState() { return this.machine.saveState(); }
loadState(s) { this.machine.loadState(s); }
readAddress(a) { return this.machine.read(a); }
start() {
this.machine = new AppleII();
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
var vp = this.machine.getVideoParams();
this.video = new RasterVideo(this.mainElement, vp.width, vp.height);
//this.audio = new SampleAudio(cpuFrequency);
this.video.create();
this.machine.connectVideo(this.video.getFrameData());
}
reset() {
this.machine.reset();
}
loadROM(title, data) {
this.machine.loadROM(data);
this.reset();
}
advance(novideo:boolean) {
this.machine.advanceFrame();
if (!novideo) this.video.updateFrame();
}
isRunning() {
return this.timer.isRunning();
}
resume() {
this.timer.start();
}
pause() {
this.timer.stop();
}
}
PLATFORMS['apple2'] = _Apple2Platform;
PLATFORMS['apple2-e'] = Apple2MAMEPlatform;
PLATFORMS['apple2.mame'] = Apple2MAMEPlatform;
PLATFORMS['apple2.new'] = NewApple2Platform;

Binary file not shown.

View File

@ -0,0 +1,68 @@
var assert = require('assert');
var fs = require('fs');
var emu = require("gen/nemu/nemu.js");
var MOS6502 = require("gen/nemu/cpu/MOS6502.js");
var testbin = fs.readFileSync('test/cli/6502/6502_functional_test.bin', null);
describe('MOS6502', function() {
it('Should pass functional tests', function() {
assert.equal(65536, testbin.length);
var mem = new Uint8Array(testbin);
var bus = {
read: (a) => { return mem[a]; },
write: (a,v) => { mem[a] = v; }
};
var cpu = new MOS6502.MOS6502();
cpu.connectMemoryBus(bus);
cpu.reset();
var s0 = cpu.saveState();
s0.PC = 0x400;
cpu.loadState(s0);
for (var i=0; i<100000000; i++) {
cpu.advanceClock();
var pc = cpu.getPC();
if (pc == 0x3469) break; // success!
}
console.log(i+' cycles, PC = $'+pc.toString(16));
assert.equal(pc, 0x3469);
// NMI trap
cpu.interrupt(1);
for (var i=0; i<20; i++) {
cpu.advanceClock();
var pc = cpu.getPC();
if (pc == 0x379e) break;
}
assert.equal(pc, 0x379e);
// hooks
mem.set(testbin);
cpu.loadState(s0);
var pcs = [];
var profiler = {
logExecute: function(a) { pcs.push(a); },
logRead: function(a) { pcs.push(a); },
logWrite: function(a) { pcs.push(a); },
};
// test hooks
var chook = new emu.CPUClockHook(cpu, profiler);
for (var i=0; i<10000; i++) {
cpu.advanceClock();
}
chook.unhook();
for (var i=0; i<100000; i++) {
cpu.advanceClock();
}
console.log(pcs.slice(pcs.length-10));
assert.equal(10000, pcs.length);
// bus hook
var bhook = new emu.BusHook(bus, profiler);
for (var i=0; i<10000; i++) {
cpu.advanceClock();
}
bhook.unhook();
console.log(pcs.slice(pcs.length-10));
assert.equal(20000, pcs.length);
});
});