mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-25 18:33:11 +00:00
working on new emulator framework
This commit is contained in:
parent
9de22d6389
commit
0f63282931
@ -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
|
||||
|
@ -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
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
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
165
src/nemu/nemu.ts
Normal 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 {
|
||||
}
|
||||
|
@ -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;
|
||||
|
BIN
test/cli/6502/6502_functional_test.bin
Normal file
BIN
test/cli/6502/6502_functional_test.bin
Normal file
Binary file not shown.
68
test/cli/6502/test6502sim.js
Normal file
68
test/cli/6502/test6502sim.js
Normal 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);
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user