mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-29 14:51:17 +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="gen/ui.js"></script>
|
||||||
<!-- <script src="src/audio/votrax.js"></script> -->
|
<!-- <script src="src/audio/votrax.js"></script> -->
|
||||||
<!-- <script src="local/lzg.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>
|
<script>
|
||||||
// submenus open on click + hover
|
// submenus open on click + hover
|
||||||
|
@ -125,7 +125,7 @@ export class RasterVideo {
|
|||||||
|
|
||||||
getContext() { return this.ctx; }
|
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)
|
if (w && h)
|
||||||
this.ctx.putImageData(this.imageData, sx, sy, dx, dy, w, h);
|
this.ctx.putImageData(this.imageData, sx, sy, dx, dy, w, h);
|
||||||
else
|
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'] = _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