From 9bdec710d70fb3a94510448b67de80d8c7e42a88 Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Wed, 14 Aug 2019 08:56:45 -0400 Subject: [PATCH] notes, removed base_z80, added 7800 cc65 --- doc/platforms.md | 265 +++++++++++++++++++++++++ presets/atari7800/atari7800.h | 142 +++++++++++++ presets/atari7800/skeleton.cc65 | 20 ++ presets/atari7800/sprites.dasm | 48 ++--- src/platform/atari7800.ts | 68 ++++--- src/platform/base_z80.ts | 81 -------- src/worker/lib/atari7800/atari7800.cfg | 47 +++++ src/worker/lib/atari7800/atari7800.inc | 52 +++++ src/worker/lib/atari7800/crt0.o | Bin 0 -> 2076 bytes src/worker/lib/atari7800/crt0.s | 108 ++++++++++ src/worker/workermain.ts | 6 +- 11 files changed, 694 insertions(+), 143 deletions(-) create mode 100644 doc/platforms.md create mode 100644 presets/atari7800/atari7800.h create mode 100644 presets/atari7800/skeleton.cc65 delete mode 100644 src/platform/base_z80.ts create mode 100644 src/worker/lib/atari7800/atari7800.cfg create mode 100644 src/worker/lib/atari7800/atari7800.inc create mode 100644 src/worker/lib/atari7800/crt0.o create mode 100644 src/worker/lib/atari7800/crt0.s diff --git a/doc/platforms.md b/doc/platforms.md new file mode 100644 index 00000000..5e8bd709 --- /dev/null +++ b/doc/platforms.md @@ -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 { + }, + 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. diff --git a/presets/atari7800/atari7800.h b/presets/atari7800/atari7800.h new file mode 100644 index 00000000..98f35369 --- /dev/null +++ b/presets/atari7800/atari7800.h @@ -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) diff --git a/presets/atari7800/skeleton.cc65 b/presets/atari7800/skeleton.cc65 new file mode 100644 index 00000000..752e7d6f --- /dev/null +++ b/presets/atari7800/skeleton.cc65 @@ -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> 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>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) { diff --git a/src/platform/base_z80.ts b/src/platform/base_z80.ts deleted file mode 100644 index aa671b46..00000000 --- a/src/platform/base_z80.ts +++ /dev/null @@ -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; diff --git a/src/worker/lib/atari7800/atari7800.cfg b/src/worker/lib/atari7800/atari7800.cfg new file mode 100644 index 00000000..ae62cb6b --- /dev/null +++ b/src/worker/lib/atari7800/atari7800.cfg @@ -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__; +} diff --git a/src/worker/lib/atari7800/atari7800.inc b/src/worker/lib/atari7800/atari7800.inc new file mode 100644 index 00000000..04028a3f --- /dev/null +++ b/src/worker/lib/atari7800/atari7800.inc @@ -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 diff --git a/src/worker/lib/atari7800/crt0.o b/src/worker/lib/atari7800/crt0.o new file mode 100644 index 0000000000000000000000000000000000000000..b6fa31217a6e69502d0a38efd8aaff8a0571343f GIT binary patch literal 2076 zcma)6OK%fb82!HM89zc^w4nsT!wIhdjbnRaCoPY9Ja%G(V^78thXm58^8gGX33gBe zo0dlGDs|OGD^)`5_y_$7l`2(8-E`SqTYf>MF6ej00e9Ib_k8C&=iEDYX0B)c$!7f^ z5q%~jBBLRrPsXl{9!2y_Mo9SAQg2HAL#q7KYXMirRh{S;somlByuXCA_(Rg8Dy1?n zpTByoD_-eymw)a*{!h2N{LMflDCWFm6oJ*=v4wZ+hR?dhT_nZKQN?GGUGnv;u$$D) zs++hhSK>UN3OEWNUilxeEYVwZht(wUE~kip<9<>vLwuXmGv-<0U+_WUm-(7-;zz=# zxaPZIQZL@{1o8Lm3C-{YKN2JLE%9jz{6Ne(;y?Kq@hd(`{I`I5PCQP5?`V*(q8#A? zl%w2-GQ`s;$9N3oINwD%!9yq~c>`sH&!C)Q6Xi7bqMYF#l(YOX$~jIF|HLmxje&rXZ_87pAWwo$lHTvFh}rta|tgbMQx2 z;k^)XLm5hV=8&%1Dc%cQ&;cxaT&%WTp9A8stH-16Z!kgL2u=j|k8$ znD+qVIGCuBfDz=*`+!IQa23WuuwlT04AKDy z(0&AFAufP}7X-|qy)1(k5xD_44dZ>lMYIOkrxaXm z?!;rQfz^6C6|E&>iK*yVbf&QrEsvYJx4zrhIpBF!*L2)@+w&q`p}x8H*l~-2@@z3s zbmny53pF+yI~$D^efv@CZd}{m+OF$>UEO<7Z)}F1tT(q-TCMQv*7oDI`cA#a^Bi-w zy{_kV?{~$eyyx{wQ<;sw^^o*i-|X+~rBpFv zw9jikuO)PZ<36@|(&vVc6F#QGS<|{c#5ECNK%1dxH zI|(rfF=-iE&bAAhZ9p@i8Evhk*%QzvpiNjt*eaM#)~x0=I|V%jJ!Kie%Jg(KFRP}Z zrlF=~1>B@&PePl7HYt~{F31`_W`mYnsxFxAk_{HiZY^)=u~y^By2>O}CaE&UiIIB` zwxgrvz_pD_hOA^`?e56l}kXK(<=d@DjpHd8cBVGkL8%Un&I) fc{7)HLi^`3B-d~&PL;b0dm24`Bd42O`F{Tk9Q;25 literal 0 HcmV?d00001 diff --git a/src/worker/lib/atari7800/crt0.s b/src/worker/lib/atari7800/crt0.s new file mode 100644 index 00000000..b1f723d0 --- /dev/null +++ b/src/worker/lib/atari7800/crt0.s @@ -0,0 +1,108 @@ +; Startup code for cc65 and Shiru's NES library +; based on code by Groepaz/Hitmen , Ullrich von Bassewitz +; 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 + diff --git a/src/worker/workermain.ts b/src/worker/workermain.ts index e34c6e3d..1c263248 100644 --- a/src/worker/workermain.ts +++ b/src/worker/workermain.ts @@ -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',