notes, removed base_z80, added 7800 cc65

This commit is contained in:
Steven Hugg 2019-08-14 08:56:45 -04:00
parent fef73d9b54
commit 9bdec710d7
11 changed files with 694 additions and 143 deletions

265
doc/platforms.md Normal file
View File

@ -0,0 +1,265 @@
class Platform
--------------
Mandatory functions:
~~~
start() : void;
reset() : void;
isRunning() : boolean;
pause() : void;
resume() : void;
loadROM(title:string, rom:any);
~~~
These are for the compiler/editor:
~~~
getToolForFilename(s:string) : string;
getDefaultExtension() : string;
getPresets() : Preset[];
~~~
Most platforms have these:
~~~
loadState?(state : EmuState) : void;
saveState?() : EmuState;
~~~
... etc
6502
----
`advance()` advances one frame.
The basic idea: iterate through all the scanlines, run a bunch of CPU cycles per scanline.
If we hit a breakpoint, exit the loop.
~~~
var debugCond = this.getDebugCallback();
for (var sl=0; sl<262; sl++) {
for (var i=0; i<cpuCyclesPerLine; i++) {
if (debugCond && debugCond()) {
debugCond = null;
sl = 999;
break;
}
clock++;
cpu.clockPulse();
}
}
~~~
Hitting a breakpoint does a `saveState()` but debug info is better when the platform is stopped at the breakpoint instead of being allowed to continue.
Some platforms like `vector` aren't scanline-based, they just have a target number of scanlines per frame (per 1/60 sec)
The 6502 CPU core is usually a byte behind the current instruction at breakpoints.
So when saving state we +1 the PC by calling `fixPC`.
When loading state we have to -1 the PC, load state, then +1 the PC.
~~~
this.unfixPC(state.c);
cpu.loadState(state.c);
this.fixPC(state.c);
~~~
Z80
---
There's a `runCPU()` wrapper:
~~~
advance(novideo : boolean) {
for (var sl=0; sl<scanlinesPerFrame; sl++) {
drawScanline(pixels, sl);
this.runCPU(cpu, cpuCyclesPerLine);
}
// NMI each frame
if (interruptEnabled) { cpu.nonMaskableInterrupt(); }
}
~~~
Atari 2600
-----------
8bitworkshop was originally VCS-only, Javatari.js was the first emulator it supported.
It's a wonderful emulator, but it didn't have hooks for debugging.
I had to hack it up quite a bit, and wasn't sure what I was doing.
A lot of the debugging functions just pass-through to my hacked-up functions:
~~~
step() { Javatari.room.console.debugSingleStepCPUClock(); }
stepBack() { Javatari.room.console.debugStepBackInstruction(); }
runEval(evalfunc) { Javatari.room.console.debugEval(evalfunc); }
~~~
Even so, I decided to monkey-patch the `clockPulse()` function so that I could record frames:
~~~
Javatari.room.console.oldClockPulse = Javatari.room.console.clockPulse;
Javatari.room.console.clockPulse = function() {
self.updateRecorder();
this.oldClockPulse();
}
~~~
Eventually I'd like to make it more like the other platforms.
8bitworkshop uses its CPU core for other 6502 platforms.
BasicZ80ScanlinePlatform
------------------------
Can be used to easily build a Z80-based raster platform.
Just have to fill out the following:
~~~
cpuFrequency : number;
canvasWidth : number;
numTotalScanlines : number;
numVisibleScanlines : number;
defaultROMSize : number;
abstract newRAM() : Uint8Array;
abstract newMembus() : MemoryBus;
abstract newIOBus() : MemoryBus;
abstract getVideoOptions() : {};
abstract getKeyboardMap();
abstract startScanline(sl : number) : void;
abstract drawScanline(sl : number) : void;
getRasterScanline() : number { return this.currentScanline; }
getKeyboardFunction() { return null; }
~~~
NES
---
NES uses the JSNES emulator, which has a callback function after each frame.
~~~
this.nes = new jsnes.NES({
onFrame: (frameBuffer : number[]) => {
},
onAudioSample: (left:number, right:number) => {
},
onStatusUpdate: function(s) {
},
});
~~~
We monkey-patch the code to add a debugging hook:
~~~
// insert debug hook
this.nes.cpu._emulate = this.nes.cpu.emulate;
this.nes.cpu.emulate = () => {
var cycles = this.nes.cpu._emulate();
this.evalDebugCondition();
return cycles;
}
~~~
NES was the first platform with an "illegal opcode" hard stop, so we added a special `EmuHalt` exception which causes a breakpoint:
~~~
this.nes.stop = () => {
console.log(this.nes.cpu.toJSON());
throw new EmuHalt("CPU STOPPED @ PC $" + hex(this.nes.cpu.REG_PC));
};
~~~
MAME
----
The `BaseMAMEPlatform` class implements a MAME platform.
You just have to pass it various parameters when starting, and tell it how to load the ROM file:
~~~
class ColecoVisionMAMEPlatform extends BaseMAMEPlatform implements Platform {
start() {
this.startModule(this.mainElement, {
jsfile: 'mamecoleco.js',
cfgfile: 'coleco.cfg',
biosfile: 'coleco/313 10031-4005 73108a.u2',
driver: 'coleco',
width: 280 * 2,
height: 216 * 2,
romfn: '/emulator/cart.rom',
romsize: 0x8000,
preInit: function(_self) {
},
});
}
loadROM(title, data) {
this.loadROMFile(data);
this.loadRegion(":coleco_cart:rom", data);
}
getPresets() { return ColecoVision_PRESETS; }
getToolForFilename = getToolForFilename_z80;
getDefaultExtension() { return ".c"; };
}
~~~
A lot of things are done via Lua scripting -- for example, loading a ROM requires we loop over the memory region and issue `rgn:write_u32` calls.
It kinda-sorta works, except debugging isn't reliable because MAME [doesn't return from the event loop](https://github.com/mamedev/mame/issues/3649) at breakpoints.
MAME platforms don't have state load/save either.
Verilog
--------
The Verilog platform is the odd one out, since it has no fixed CPU as such.
The `loadROM` function instead loads a JavaScript function.
Some platforms do have a ROM if using assembly, so we load that into a Verilog array.
It's quite the hack, and it could be better.
Verilog has its own debugger, logging signals in a fixed-size buffer.
Profiling
----------
`EmuProfilerImpl` runs the profiler.
When started, it calls `setBreakpoint` to add a profiler-specific breakpoint that never hits, just records the CPU state at each clock.
It uses `getRasterScanline` to associate IPs with scanlines.
Platforms can also log their own reads, writes, interrupts, etc.
Future Ideas
------------
There should be a standard CPU interface, buses, memory map.
More like MAME configuration.
Platforms might have different ideas of "clock" (CPU clock, pixel clock, 1 clock per instruction, etc)
The goal is to rewind and advance to any clock cycle within a frame, and get complete introspection of events, without hurting performance.
Unify raster platforms, they should all allow the same debugging and CPU interfaces.
Separate UI/sim parts of platform?
A lot of platforms write into a uint32 buffer.
We might want to buffer audio the same way.
Also some way to log events, and handle input.
Figure out how to make platform-specific type for load/save state.
(generics?)
Separate emulators from 8bitworkshop IDE.
Can we use WASM emulators without JS interop penalty?
Maybe using [AssemblyScript](https://docs.assemblyscript.org/)?
Startup would be faster, probably runtime too.
Drawback is that dynamic stuff (custom breakpoint functions, profiling) might be slow, slower dev too maybe.
Need proof-of-concept.

View File

@ -0,0 +1,142 @@
// define basic types for convenience
typedef unsigned char byte; // 8-bit unsigned
typedef signed char sbyte; // 8-bit signed
typedef unsigned short word; // 16-bit signed
typedef enum { false, true } bool; // boolean
/// MEMORY MAPS
typedef struct t_TIA {
byte _00;
byte VBLANK; // input port control
byte _02_07[6];
byte INPT0; // PADDLE CONTROL INPUT 0 WO
byte INPT1; // PADDLE CONTROL INPUT 1 WO
byte INPT2; // PADDLE CONTROL INPUT 2 WO
byte INPT3; // PADDLE CONTROL INPUT 3 WO
byte INPT4; // PLAYER 0 FIRE BUTTON INPUT WO
byte INPT5; // PLAYER 1 FIRE BUTTON INPUT WO
byte _0e_14[7];
byte AUDC0; // AUDIO CONTROL CHANNEL 0 WO
byte AUDC1; // AUDIO CONTROL CHANNEL 1 WO
byte AUDF0; // AUDIO FREQUENCY CHANNEL 0 WO
byte AUDF1; // AUDIO FREQUENCY CHANNEL 1 WO
byte AUDV0; // AUDIO VOLUME CHANNEL 0 WO
byte AUDV1; // AUDIO VOLUME CHANNEL 1 WO
} t_TIA;
typedef struct t_MARIA {
byte BACKGRND ;// X '20' BACKGROUND COLOR R/W
byte P0C1 ;// X '21' PALETTE 0 - COLOR 1 R/W
byte P0C2 ;// X '22' - COLOR 2 R/W
byte P0C3 ;// X '23' - COLOR 3 R/W
byte WSYNC ;// X '24' WAIT FOR SYNC STROBE
byte P1C1 ;// X '25' PALETTE 1 - COLOR 1 R/W
byte P1C2 ;// X '26' - COLOR 2 R/W
byte P1C3 ;// X '27' - COLOR 3 R/W
byte MSTAT ;// X '28' MARIA STATUS RO
byte P2C1 ;// X '29' PALETTE 2 - COLOR 1 R/W
byte P2C2 ;// X '2A' - COLOR 2 R/W
byte P2C3 ;// X '2B' - COLOR 3 R/W
byte DPPH ;// X '2C' DISPLAY LIST LIST POINT HIGH WO
byte P3C1 ;// X '2D' PALETTE 3 - COLOR 1 R/W
byte P3C2 ;// X '2E' - COLOR 2 R/W
byte P3C3 ;// X '2F' - COLOR 3 R/W
byte DPPL ;// X '30' DISPLAY LIST LIST POINT LOW WO
byte P4C1 ;// X '31' PALETTE 4 - COLOR 1 R/W
byte P4C2 ;// X '32' - COLOR 2 R/W
byte P4C3 ;// X '33' - COLOR 3 R/W
byte CHARBASE ;// X '34' CHARACTER BASE ADDRESS WO
byte P5C1 ;// X '35' PALETTE 5 - COLOR 1 R/W
byte P5C2 ;// X '36' - COLOR 2 R/W
byte P5C3 ;// X '37' - COLOR 3 R/W
byte OFFSET ;// X '38' FOR FUTURE EXPANSION -STORE ZERO HERE R/W
byte P6C1 ;// X '39' PALETTE 6 - COLOR 1 R/W
byte P6C2 ;// X '3A' - COLOR 2 R/W
byte P6C3 ;// X '3B' - COLOR 3 R/W
byte CTRL ;// X '3C' MARIA CONTROL REGISTER WO
byte P7C1 ;// X '3D' PALETTE 7 - COLOR 1 R/W
byte P7C2 ;// X '3E' - COLOR 2 R/W
byte P7C3 ;// X '3F' - COLOR 3 R/W
} t_MARIA;
typedef struct t_P6532 {
byte SWCHA; // P0,P1 JOYSTICK DIRECTIONAL INPUT R/W
byte CTLSWA; // CONSOLE SWITCHES RO
byte SWCHB; // I/O CONTROL FOR SWCHA R/W
byte CTLSWB; // I/O CONTROL FOR SWCHB R/W
} t_P6532;
typedef struct DLLEntry {
byte offset_flags;
/*
unsigned int offset:4;
unsigned int _unused:1;
unsigned int h8:1;
unsigned int h16:1;
unsigned int dli:1;
*/
byte dl_hi;
byte dl_lo;
} DLLEntry;
typedef struct DL4Entry {
byte data_lo;
byte width_pal;
/*
unsigned int width:5;
unsigned int palette:3;
*/
byte data_hi;
byte xpos;
} DL4Entry;
typedef struct DL5Entry {
byte data_lo;
byte flags;
byte data_hi;
byte width_pal;
/*
unsigned int width:5;
unsigned int palette:3;
*/
byte xpos;
} DL5Entry;
/// CONSTANTS
#define DLL_DLI 0x80 // Display List Interrupt flag
#define DLL_H16 0x40 // Holey DMA 4k flag
#define DLL_H8 0x20 // Holey DMA 2k flag
#define DL5_WM 0x80 // Write Mode
#define DL5_DIRECT 0x40 // Direct Mode
#define DL5_INDIRECT 0x60 // Indirect Mode
#define DL_WIDTH(x) (32-(x)) // for width_pal
#define DL_PAL(x) ((x)<<5) // OR them together
#define DL_WP(w,p) (DL_WIDTH(w)|DL_PAL(p))
#define CTRL_COLORKILL 0x80
#define CTRL_DMA_ON 0x40
#define CTRL_DMA_OFF 0x60
#define CTRL_DBLBYTE 0x10
#define CTRL_BLKBORDER 0x08
#define CTRL_KANGAROO 0x04
#define CTRL_160AB 0x00
#define CTRL_320BD 0x02
#define CTRL_320AC 0x03
#define MSTAT_VBLANK 0x80
/// GLOBALS
#define TIA (*((t_TIA*) 0x00))
#define MARIA (*((t_MARIA*) 0x20))
#define P6532 (*((t_P6532*) 0x280))
/// MACROS
#define STROBE(addr) __asm__ ("sta %w", addr)
#define WSYNC() STROBE(0x24)

View File

@ -0,0 +1,20 @@
#include "atari7800.h"
void main() {
byte y1 = 110;
byte y2 = 10;
byte i;
while (1) {
while ((MARIA.MSTAT & MSTAT_VBLANK) == 0) ;
while ((MARIA.MSTAT & MSTAT_VBLANK) != 0) ;
for (i=0; i<y1; i++) {
WSYNC();
}
for (i=0; i<y2; i++) {
MARIA.BACKGRND = P6532.SWCHA - i;
WSYNC();
}
MARIA.BACKGRND = 0;
}
}

View File

@ -109,40 +109,26 @@ crloop1
inx
bne crloop1
;************* Clear RAM **************************
ldy #$00 ;Clear Ram
lda #$18 ;Start at $1800
sta $81
lda #$00
sta $80
crloop3
lda #$00
sta ($80),y ;Store data
iny ;Next byte
bne crloop3 ;Branch if not done page
inc $81 ;Next page
lda $81
cmp #$20 ;End at $1FFF
bne crloop3 ;Branch if not
ldy #$00 ;Clear Ram
lda #$22 ;Start at $2200
sta $81
lda #$00
sta $80
crloop4
lda #$00
sta ($80),y ;Store data
iny ;Next byte
bne crloop4 ;Branch if not done page
inc $81 ;Next page
lda $81
cmp #$27 ;End at $27FF
bne crloop4 ;Branch if not
crloop2
sta $1800,y
sta $1900,y
sta $1a00,y
sta $1b00,y
sta $1c00,y
sta $1d00,y
sta $1e00,y
sta $1f00,y
sta $2200,y
sta $2300,y
sta $2400,y
sta $2500,y
sta $2600,y
sta $2700,y
iny
bne crloop2
ldx #$00
lda #$00
crloop5 ;Clear 2100-213F
sta $2100,x
inx

View File

@ -140,14 +140,15 @@ class MARIA {
if (b) {
this.regs[0x08] |= 0x80;
this.offset = -1;
this.dll = this.getDLLStart(); // TODO?
this.dll = this.getDLLStart();
this.dli = this.bus && (this.bus.read(this.dll) & 0x80) != 0; // if DLI on first zone
} else {
this.regs[0x08] &= ~0x80;
}
}
readDLLEntry(bus) {
this.profiler && this.profiler.logRead(this.dll);
var x = bus.read(this.dll);
let x = bus.read(this.dll);
this.offset = (x & 0xf);
this.h16 = (x & 0x40) != 0;
this.h8 = (x & 0x20) != 0;
@ -173,8 +174,8 @@ class MARIA {
}
}
doDMA(platform : Atari7800Platform) {
var bus = this.bus = platform.bus;
var profiler = this.profiler = platform.profiler;
let bus = this.bus = platform.bus;
let profiler = this.profiler = platform.profiler;
this.cycles = 0;
this.pixels.fill(this.regs[0x0]);
if (this.isDMAEnabled()) {
@ -184,17 +185,17 @@ class MARIA {
this.readDLLEntry(bus);
}
// read the DL (only can span two pages)
var dlhi = this.dlstart & 0xff00;
var dlofs = this.dlstart & 0xff;
let dlhi = this.dlstart & 0xff00;
let dlofs = this.dlstart & 0xff;
do {
// read DL entry
profiler && profiler.logRead(dlhi + ((dlofs+0) & 0x1ff));
var b0 = bus.read(dlhi + ((dlofs+0) & 0x1ff));
var b1 = bus.read(dlhi + ((dlofs+1) & 0x1ff));
let b0 = bus.read(dlhi + ((dlofs+0) & 0x1ff));
let b1 = bus.read(dlhi + ((dlofs+1) & 0x1ff));
if (b1 == 0) break; // end of DL
var b2 = bus.read(dlhi + ((dlofs+2) & 0x1ff));
var b3 = bus.read(dlhi + ((dlofs+3) & 0x1ff));
var indirect = false;
let b2 = bus.read(dlhi + ((dlofs+2) & 0x1ff));
let b3 = bus.read(dlhi + ((dlofs+3) & 0x1ff));
let indirect = false;
// extended header?
if ((b1 & 31) == 0) {
var pal = b3 >> 5;
@ -213,21 +214,25 @@ class MARIA {
dlofs += 4;
this.cycles += 8;
}
var gfxadr = b0 + (((b2 + (indirect?0:this.offset)) & 0xff) << 8);
let gfxadr = b0 + (((b2 + (indirect?0:this.offset)) & 0xff) << 8);
xpos *= 2;
// copy graphics data (direct)
// TODO
var readmode = (this.regs[0x1c] & 0x3) + (writemode?4:0);
let readmode = (this.regs[0x1c] & 0x3) + (writemode?4:0);
// double bytes?
let dbl = indirect && (this.regs[0x1c] & 0x10) != 0;
if (dbl) { width *= 2; }
//if (this.offset == 0) console.log(hex(dla,4), hex(gfxadr,4), xpos, width, pal, readmode);
for (var i=0; i<width; i++) {
var data = this.readDMA(gfxadr + i);
let data = this.readDMA( dbl ? (gfxadr+(i>>1)) : (gfxadr+i) );
if (indirect) {
// TODO: double bytes
data = this.readDMA(((this.regs[0x14] + this.offset) << 8) + data);
let indadr = ((this.regs[0x14] + this.offset) << 8) + data;
if (dbl && (i&1)) indadr++;
data = this.readDMA(indadr);
}
// TODO: more modes
switch (readmode) {
case 0: // 160 A/B
for (var j=0; j<4; j++) {
for (let j=0; j<4; j++) {
var col = (data >> 6) & 3;
if (col > 0) {
this.pixels[xpos] = this.pixels[xpos+1] = this.regs[(pal<<2) + col];
@ -238,7 +243,7 @@ class MARIA {
break;
case 2: // 320 B/D (TODO?)
case 3: // 320 A/C
for (var j=0; j<8; j++) {
for (let j=0; j<8; j++) {
var col = (data & 128) ? 1 : 0;
if (col > 0) {
this.pixels[xpos] = this.regs[(pal<<2) + col];
@ -328,17 +333,18 @@ class Atari7800Platform extends Base6502Platform implements Platform {
[0x1800, 0x27ff, 0xffff, (a) => { return this.ram[a - 0x1800]; }],
[0x2800, 0x3fff, 0x7ff, (a) => { return this.bus.read(a | 0x2000); }], // shadow
[0x4000, 0xffff, 0xffff, (a) => { return this.rom ? this.rom[a - 0x4000] : 0; }],
[0x0000, 0xffff, 0xffff, (a) => { throw new EmuHalt("Read @ " + hex(a,4)); }],
[0x0000, 0xffff, 0xffff, (a) => { return 0; }], // TODO
]),
write: newAddressDecoder([
[0x0015, 0x001A, 0x1f, (a,v) => { this.audio.pokey1.setTIARegister(a, v); }],
[0x0000, 0x001f, 0x1f, (a,v) => { this.tia.write(a,v); this.profiler && this.profiler.logWrite(a); }],
[0x0020, 0x003f, 0x1f, (a,v) => { this.maria.write(a,v); this.profiler && this.profiler.logWrite(a+0x20); }],
[0x0040, 0x00ff, 0xff, (a,v) => { this.ram[a + 0x800] = v; }],
[0x0100, 0x013f, 0xff, (a,v) => { this.bus.write(a); }], // shadow
[0x0140, 0x01ff, 0x1ff, (a,v) => { this.ram[a + 0x800] = v; }],
[0x0280, 0x02ff, 0x3, (a,v) => { this.regs6532[a] = v; /*TODO*/ }],
[0x1800, 0x27ff, 0xffff, (a,v) => { this.ram[a - 0x1800] = v; }],
[0x2800, 0x3fff, 0x7ff, (a,v) => { this.bus.write(a | 0x2000, v); }],
[0x2800, 0x3fff, 0x7ff, (a,v) => { this.bus.write(a | 0x2000, v); }], // shadow
[0xbfff, 0xbfff, 0xffff, (a,v) => { }], // TODO: bank switching?
[0x0000, 0xffff, 0xffff, (a,v) => { throw new EmuHalt("Write @ " + hex(a,4) + " " + hex(v,2)); }],
]),
@ -397,18 +403,19 @@ class Atari7800Platform extends Base6502Platform implements Platform {
this.cpu.clockPulse();
}
mariaClocks += colorClocksPerLine;
// do DMA for scanline?
if (this.maria.isDMAEnabled() && visible) {
// is this scanline visible?
if (visible) {
// do DMA for scanline?
mariaClocks -= this.maria.doDMA(this);
// copy line to frame buffer
for (var i=0; i<320; i++) {
idata[iofs++] = COLORS_RGBA[this.maria.pixels[i]];
}
// do interrupt?
if (this.maria.doInterrupt()) {
this.profiler && this.profiler.logInterrupt(0);
mariaClocks -= this.cpu.setNMIAndWait() * 4;
}
}
// do interrupt? (if visible or before 1st scanline)
if ((visible || sl == linesPerFrame-1) && this.maria.doInterrupt()) {
this.profiler && this.profiler.logInterrupt(0);
mariaClocks -= this.cpu.setNMIAndWait() * 4;
}
}
// update video frame
@ -455,8 +462,9 @@ class Atari7800Platform extends Base6502Platform implements Platform {
this.cpu.clockPulse(); // TODO: needed for test to pass?
}
// TODO: don't log if profiler active
readAddress(addr : number) {
return (addr < 0x1800 ? this.ram[addr] : this.bus.read(addr)) | 0;
return this.bus.read(addr) | 0;
}
loadState(state) {
@ -497,7 +505,7 @@ class Atari7800Platform extends Base6502Platform implements Platform {
}
getDebugCategories() {
return super.getDebugCategories().concat(['TIA','MARIA']);
return ['CPU','Stack','TIA','MARIA'];
}
getDebugInfo(category, state) {
switch (category) {

View File

@ -1,81 +0,0 @@
"use strict";
import { Platform, BaseZ80Platform, BaseMAMEPlatform } from "../baseplatform";
import { PLATFORMS, RAM, newAddressDecoder, padBytes } from "../emu";
var BASEZ80_PRESETS = [
{id:'simple1.c', name:'Multiply by 2'},
{id:'simple2.c', name:'Divide by 4'},
{id:'prng.c', name:'Pseudorandom Numbers'},
{id:'fib.c', name:'Fibonacci'},
{id:'gfx.c', name:'Graphics'},
{id:'empty.c', name:'Your Code Here...'},
];
var Base_Z80Platform = function(mainElement) {
var self = this;
this.__proto__ = new (BaseZ80Platform as any)();
var cpu, ram, membus, iobus, rom, timer;
this.getPresets = function() {
return BASEZ80_PRESETS;
}
this.start = function() {
ram = new RAM(0x8000);
membus = {
read: newAddressDecoder([
[0x0000, 0x7fff, 0x7fff, function(a) { return rom ? rom[a] : null; }],
[0x8000, 0xffff, 0x7fff, function(a) { return ram.mem[a]; }],
]),
write: newAddressDecoder([
[0x8000, 0xffff, 0x7fff, function(a,v) { ram.mem[a] = v; }],
]),
};
this.readAddress = membus.read;
iobus = {
read: function(addr) {
return 0;
},
write: function(addr, val) {
}
};
cpu = this.newCPU(membus, iobus);
}
this.loadROM = function(title, data) {
rom = padBytes(data, 0x8000);
self.reset();
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
}
this.saveState = function() {
return {
c:self.getCPUState(),
b:ram.mem.slice(0),
};
}
this.getCPUState = function() {
return cpu.saveState();
}
this.isRunning = function() {
return timer && timer.isRunning();
}
this.pause = function() {
if (timer) timer.stop();
}
this.resume = function() {
if (timer) timer.start();
}
this.reset = function() {
cpu.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
}
PLATFORMS['base_z80'] = Base_Z80Platform;

View File

@ -0,0 +1,47 @@
SYMBOLS {
__STACKSIZE__: type = weak, value = $0600;
}
MEMORY {
# Zero Page
ZP: file = "", start = $0040, size = $00C0, type = rw, define = yes;
# Cartridge Header
HEADER: file = %O, start = $0000, size = $0080, fill = yes;
# ROM Bank
PRG: file = %O, start = $4000, size = $BFFA, fill = yes, define = yes;
# CPU Vectors
VECTORS: file = %O, start = $FFFA, size = $0006, fill = yes;
# standard 2k SRAM (-zeropage)
# $0140-$0200 cpu stack
# $0500-$0800 3 pages for cc65 parameter stack
RAM: file = "", start = $2200, size = __STACKSIZE__, define = yes;
}
SEGMENTS {
ZEROPAGE: load = ZP, type = zp;
HEADER: load = HEADER, type = ro;
STARTUP: load = PRG, type = ro, define = yes;
RODATA: load = PRG, type = ro, define = yes;
ONCE: load = PRG, type = ro, optional = yes;
CODE: load = PRG, type = ro, define = yes;
DATA: load = PRG, run = RAM, type = rw, define = yes;
VECTORS: load = VECTORS, type = ro;
BSS: load = RAM, type = bss, define = yes;
}
FEATURES {
CONDES: type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__,
segment = ONCE;
CONDES: type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__,
segment = RODATA;
CONDES: type = interruptor,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__,
segment = RODATA,
import = __CALLIRQ__;
}

View File

@ -0,0 +1,52 @@
INPTCTRL = $01 ;Input control
AUDC0 = $15 ;Audio Control Channel 0
AUDC1 = $16 ;Audio Control Channel 1
AUDF0 = $17 ;Audio Fr=ency Channel 0
AUDF1 = $18 ;Audio Fr=ency Channel 1
AUDV0 = $19 ;Audio Volume Channel 0
AUDV1 = $1A ;Audio Volume Channel 1
INPT0 = $08 ;Paddle Control Input 0
INPT1 = $09 ;Paddle Control Input 1
INPT2 = $0A ;Paddle Control Input 2
INPT3 = $0B ;Paddle Control Input 3
INPT4 = $0C ;Player 0 Fire Button Input
INPT5 = $0D ;Player 1 Fire Button Input
BACKGRND = $20 ;Background Color
P0C1 = $21 ;Palette 0 - Color 1
P0C2 = $22 ;Palette 0 - Color 2
P0C3 = $23 ;Palette 0 - Color 3
WSYNC = $24 ;Wait For Sync
P1C1 = $25 ;Palette 1 - Color 1
P1C2 = $26 ;Palette 1 - Color 2
P1C3 = $27 ;Palette 1 - Color 3
MSTAT = $28 ;Maria Status
P2C1 = $29 ;Palette 2 - Color 1
P2C2 = $2A ;Palette 2 - Color 2
P2C3 = $2B ;Palette 2 - Color 3
DPPH = $2C ;Display List List Pointer High
P3C1 = $2D ;Palette 3 - Color 1
P3C2 = $2E ;Palette 3 - Color 2
P3C3 = $2F ;Palette 3 - Color 3
DPPL = $30 ;Display List List Pointer Low
P4C1 = $31 ;Palette 4 - Color 1
P4C2 = $32 ;Palette 4 - Color 2
P4C3 = $33 ;Palette 4 - Color 3
CHARBASE = $34 ;Character Base Address
P5C1 = $35 ;Palette 5 - Color 1
P5C2 = $36 ;Palette 5 - Color 2
P5C3 = $37 ;Palette 5 - Color 3
OFFSET = $38 ;Unused - Store zero here
P6C1 = $39 ;Palette 6 - Color 1
P6C2 = $3A ;Palette 6 - Color 2
P6C3 = $3B ;Palette 6 - Color 3
CTRL = $3C ;Maria Control Register
P7C1 = $3D ;Palette 7 - Color 1
P7C2 = $3E ;Palette 7 - Color 2
P7C3 = $3F ;Palette 7 - Color 3
SWCHA = $280 ;P0, P1 Joystick Directional Input
SWCHB = $282 ;Console Switches
CTLSWA = $281 ;I/O Control for SCHWA
CTLSWB = $283 ;I/O Control for SCHWB

Binary file not shown.

View File

@ -0,0 +1,108 @@
; Startup code for cc65 and Shiru's NES library
; based on code by Groepaz/Hitmen <groepaz@gmx.net>, Ullrich von Bassewitz <uz@cc65.org>
; edited by Steven Hugg (remove integrated Famitone2 library, add NMICallback)
.export _exit,__STARTUP__:absolute=1
.export _HandyRTI
.export NMI,IRQ,START
.import initlib,push0,popa,popax,_main,zerobss,copydata
; Linker generated symbols
.import __RAM_START__ ,__RAM_SIZE__
.import __ROM0_START__ ,__ROM0_SIZE__
.import __STARTUP_LOAD__,__STARTUP_RUN__,__STARTUP_SIZE__
.import __CODE_LOAD__ ,__CODE_RUN__ ,__CODE_SIZE__
.import __RODATA_LOAD__ ,__RODATA_RUN__ ,__RODATA_SIZE__
.include "atari7800.inc"
.segment "ZEROPAGE"
INTVEC: .res 2
.segment "HEADER"
.byte $4e,$45,$53,$1a
.res 8,0
.segment "STARTUP"
START:
_exit:
sei ;Disable interrupts
cld ;Clear decimal mode
;******** Atari recommended startup procedure
lda #$07
sta INPTCTRL ;Lock into 7800 mode
lda #$7F
sta CTRL ;Disable DMA
lda #$00
sta OFFSET
sta INPTCTRL
ldx #$FF ;Reset stack pointer
txs
;************** Clear zero page and hardware ******
ldx #$40
lda #$00
@1:
sta $00,x ;Clear zero page
sta $100,x ;Clear page 1
inx
bne @1
ldy #$00 ;Clear Ram
@2:
sta $1800,y
sta $1900,y
sta $1a00,y
sta $1b00,y
sta $1c00,y
sta $1d00,y
sta $1e00,y
sta $1f00,y
sta $2200,y
sta $2300,y
sta $2400,y
sta $2500,y
sta $2600,y
sta $2700,y
iny
bne @2
ldx #$00
@3: ;Clear 2100-213F
sta $2100,x
inx
cpx #$40
bne @3
; set interrupt vector in ZP
lda #<_HandyRTI
sta INTVEC
lda #>_HandyRTI
sta INTVEC+1
; start main()
jmp _main ;no parameters
; interrupt handler
NMI:
IRQ:
jmp (INTVEC)
_HandyRTI:
rti
; CPU vectors
.segment "VECTORS"
.word NMI ;$fffa vblank nmi
.word START ;$fffc reset
.word IRQ ;$fffe irq / brk

View File

@ -273,7 +273,8 @@ var PLATFORM_PARAMS = {
'atari7800': {
define: '__ATARI7800__',
cfgfile: 'atari7800.cfg',
libargs: ['atari7800.lib'],
libargs: ['crt0.o', 'sim6502.lib'],
extra_link_files: ['crt0.o', 'atari7800.cfg'],
extra_segments:[
{name:'TIA',start:0x00,size:0x20,type:'io'},
{name:'MARIA',start:0x20,size:0x20,type:'io'},
@ -542,6 +543,7 @@ function loadNative(modulename:string) {
function setupFS(FS, name:string) {
var WORKERFS = FS.filesystems['WORKERFS'];
if (name === '65-vector') name = '65-sim6502'; // TODO
if (name === '65-atari7800') name = '65-sim6502'; // TODO
if (!fsMeta[name]) throw "No filesystem for '" + name + "'";
FS.mkdir('/share');
FS.mount(WORKERFS, {
@ -2031,6 +2033,8 @@ var TOOL_PRELOADFS = {
'ca65-atari8': '65-atari8',
'cc65-vector': '65-sim6502',
'ca65-vector': '65-sim6502',
'cc65-atari7800': '65-sim6502',
'ca65-atari7800': '65-sim6502',
'sdasz80': 'sdcc',
'sdcc': 'sdcc',
'sccz80': 'sccz80',