This will facilitate the development of a solution for cross-compiling directly to EEPROM.master
@@ -1,6 +1,6 @@ | |||||
TARGETS = forth rc2014 sms ti84 trs80 | TARGETS = forth rc2014 sms ti84 trs80 | ||||
OBJS = emul.o z80.o | OBJS = emul.o z80.o | ||||
RC2014_OBJS = $(OBJS) sio.o acia.o sdc.o rc2014_spi.o | |||||
RC2014_OBJS = $(OBJS) sio.o acia.o sdc.o rc2014_spi.o at28.o | |||||
SMS_OBJS = $(OBJS) tms9918.o sms_vdp.o sms_ports.o sms_pad.o ps2_kbd.o sdc.o \ | SMS_OBJS = $(OBJS) tms9918.o sms_vdp.o sms_ports.o sms_pad.o ps2_kbd.o sdc.o \ | ||||
sms_spi.o | sms_spi.o | ||||
TI84_OBJS = $(OBJS) t6a04.o ti84_kbd.o | TI84_OBJS = $(OBJS) t6a04.o ti84_kbd.o | ||||
@@ -32,11 +32,14 @@ stdin/stdout. | |||||
Run `./rc2014 /path/to/rom` (for example, `os.bin` from RC2014's recipe). | Run `./rc2014 /path/to/rom` (for example, `os.bin` from RC2014's recipe). | ||||
Serial I/O is hooked to stdin/stdout. `CTRL+D` to quit. | Serial I/O is hooked to stdin/stdout. `CTRL+D` to quit. | ||||
There are 2 options. `-s` replaces the ACIA with a Zilog SIO and | |||||
`-c/path/to/image` hooks up a SD card with specified contents. | |||||
You can press `CTRL+E` to dump the whole 64K of memory into `memdump`. | You can press `CTRL+E` to dump the whole 64K of memory into `memdump`. | ||||
Options: | |||||
* `-s` replaces the ACIA with a Zilog SIO. | |||||
* `-e` puts a 8K AT28 EEPROM at address `0x2000`. | |||||
* `-c/path/to/image` hooks up a SD card with specified contents. | |||||
## Sega Master System emulator | ## Sega Master System emulator | ||||
This emulates a Sega Master system with a monochrome screen and a Genesis pad | This emulates a Sega Master system with a monochrome screen and a Genesis pad | ||||
@@ -0,0 +1,54 @@ | |||||
#include <string.h> | |||||
#include "at28.h" | |||||
void at28_init(AT28 *at28, Z80Context *cpu, ushort startoffset, ushort size) | |||||
{ | |||||
at28->cpu = cpu; | |||||
memset(at28->mem, 0, LEN16BIT); | |||||
at28->startoffset = startoffset; | |||||
at28->size = size; | |||||
at28->wrstamp = 0; | |||||
} | |||||
static void _maybe_end_write(AT28 *at28) | |||||
{ | |||||
unsigned int ts = at28->cpu->tstates; | |||||
unsigned int stamp = at28->wrstamp; | |||||
// if ts < stamp, it means that the CPU re-initialized its counter | |||||
if (stamp && ((ts < stamp) || (ts > stamp+80000))) { | |||||
at28->mem[at28->wraddr] = at28->wrval; | |||||
at28->wrstamp = 0; | |||||
} | |||||
} | |||||
byte at28_mem_read(AT28 *at28, ushort addr) | |||||
{ | |||||
_maybe_end_write(at28); | |||||
if ((addr >= at28->startoffset) && (addr < at28->startoffset+at28->size)) { | |||||
if (at28->wrstamp) { | |||||
if (addr == at28->wraddr) { | |||||
// poll | |||||
at28->pollval ^= 0b01000000; // bit 6 toggle | |||||
return at28->pollval; | |||||
} else { | |||||
// reading another addr interrupts write | |||||
at28->wrstamp = 0; | |||||
} | |||||
} | |||||
return at28->mem[addr]; | |||||
} else { | |||||
return emul_mem_read(0, addr); | |||||
} | |||||
} | |||||
void at28_mem_write(AT28 *at28, ushort addr, byte val) | |||||
{ | |||||
_maybe_end_write(at28); | |||||
if ((addr >= at28->startoffset) && (addr < at28->startoffset+at28->size)) { | |||||
at28->wrstamp = at28->cpu->tstates; | |||||
at28->wraddr = addr; | |||||
at28->wrval = at28->pollval = val; | |||||
} else { | |||||
emul_mem_write(0, addr, val); | |||||
} | |||||
} |
@@ -0,0 +1,37 @@ | |||||
#pragma once | |||||
#include "emul.h" | |||||
/* Emulates the behavior of an AT28 EEPROM. When reading, behaves like regular | |||||
* RAM. When writing, be in "writing mode" for 10ms. If we assume 8MHz, that | |||||
* means 80k t-states tracked from the CPU. | |||||
* | |||||
* While we're in programming mode, reading the written address will emulate | |||||
* the "polling mode" of the AT28, that is, each read toggles IO/6. | |||||
* | |||||
* If another write happens before we're done writing or if we read from another | |||||
* address, writing fails (both the new write and the old one) and nothing is | |||||
* written to memory. | |||||
*/ | |||||
typedef struct { | |||||
// CPU reference needed to keep track of time | |||||
Z80Context *cpu; | |||||
// only range startoffset:size is used | |||||
byte mem[LEN16BIT]; | |||||
// offset at which the EEPROM begins | |||||
ushort startoffset; | |||||
// EEPROM size | |||||
ushort size; | |||||
// t-state stamp of the active writing operation. 0 means none. | |||||
unsigned int wrstamp; | |||||
// address being written to | |||||
ushort wraddr; | |||||
// byte being written | |||||
byte wrval; | |||||
// last polled value. Next polling will yield this value with 6th bit | |||||
// toggled. | |||||
byte pollval; | |||||
} AT28; | |||||
void at28_init(AT28 *at28, Z80Context *cpu, ushort startoffset, ushort size); | |||||
byte at28_mem_read(AT28 *at28, ushort addr); | |||||
void at28_mem_write(AT28 *at28, ushort addr, byte val); |
@@ -30,12 +30,12 @@ static void io_write(int unused, uint16_t addr, uint8_t val) | |||||
} | } | ||||
} | } | ||||
static uint8_t mem_read(int unused, uint16_t addr) | |||||
uint8_t emul_mem_read(int unused, uint16_t addr) | |||||
{ | { | ||||
return m.mem[addr]; | return m.mem[addr]; | ||||
} | } | ||||
static void mem_write(int unused, uint16_t addr, uint8_t val) | |||||
void emul_mem_write(int unused, uint16_t addr, uint8_t val) | |||||
{ | { | ||||
if (addr < m.ramstart) { | if (addr < m.ramstart) { | ||||
fprintf(stderr, "Writing to ROM (%d)!\n", addr); | fprintf(stderr, "Writing to ROM (%d)!\n", addr); | ||||
@@ -78,8 +78,8 @@ Machine* emul_init(char *binpath, ushort binoffset) | |||||
} | } | ||||
m.pchooks_cnt = 0; | m.pchooks_cnt = 0; | ||||
Z80RESET(&m.cpu); | Z80RESET(&m.cpu); | ||||
m.cpu.memRead = mem_read; | |||||
m.cpu.memWrite = mem_write; | |||||
m.cpu.memRead = emul_mem_read; | |||||
m.cpu.memWrite = emul_mem_write; | |||||
m.cpu.ioRead = io_read; | m.cpu.ioRead = io_read; | ||||
m.cpu.ioWrite = io_write; | m.cpu.ioWrite = io_write; | ||||
return &m; | return &m; | ||||
@@ -4,6 +4,8 @@ | |||||
#include "z80.h" | #include "z80.h" | ||||
#define MAX_PCHOOK_COUNT 8 | #define MAX_PCHOOK_COUNT 8 | ||||
#define LEN8BIT 0x100 | |||||
#define LEN16BIT 0x10000 | |||||
typedef byte (*IORD) (); | typedef byte (*IORD) (); | ||||
typedef void (*IOWR) (byte data); | typedef void (*IOWR) (byte data); | ||||
@@ -11,7 +13,7 @@ typedef byte (*EXCH) (byte data); | |||||
typedef struct _Machine { | typedef struct _Machine { | ||||
Z80Context cpu; | Z80Context cpu; | ||||
byte mem[0x10000]; | |||||
byte mem[LEN16BIT]; | |||||
// Set to non-zero to specify where ROM ends. Any memory write attempt | // Set to non-zero to specify where ROM ends. Any memory write attempt | ||||
// below ramstart will trigger a warning. | // below ramstart will trigger a warning. | ||||
ushort ramstart; | ushort ramstart; | ||||
@@ -21,8 +23,8 @@ typedef struct _Machine { | |||||
ushort maxix; | ushort maxix; | ||||
// Array of 0x100 function pointers to IO read and write routines. Leave to | // Array of 0x100 function pointers to IO read and write routines. Leave to | ||||
// NULL when IO port is unhandled. | // NULL when IO port is unhandled. | ||||
IORD iord[0x100]; | |||||
IOWR iowr[0x100]; | |||||
IORD iord[LEN8BIT]; | |||||
IOWR iowr[LEN8BIT]; | |||||
// function to call when PC falls in one of the hooks | // function to call when PC falls in one of the hooks | ||||
void (*pchookfunc) (struct _Machine *m); | void (*pchookfunc) (struct _Machine *m); | ||||
// List of PC values at which we want to call pchookfunc | // List of PC values at which we want to call pchookfunc | ||||
@@ -44,6 +46,8 @@ void emul_trace(ushort addr); | |||||
void emul_memdump(); | void emul_memdump(); | ||||
void emul_debugstr(char *s); | void emul_debugstr(char *s); | ||||
void emul_printdebug(); | void emul_printdebug(); | ||||
uint8_t emul_mem_read(int unused, uint16_t addr); | |||||
void emul_mem_write(int unused, uint16_t addr, uint8_t val); | |||||
// use when a port is a NOOP, but it's not an error to access it. | // use when a port is a NOOP, but it's not an error to access it. | ||||
byte iord_noop(); | byte iord_noop(); | ||||
void iowr_noop(byte val); | void iowr_noop(byte val); |
@@ -17,6 +17,7 @@ | |||||
#include "sio.h" | #include "sio.h" | ||||
#include "sdc.h" | #include "sdc.h" | ||||
#include "rc2014_spi.h" | #include "rc2014_spi.h" | ||||
#include "at28.h" | |||||
#define RAMSTART 0x8000 | #define RAMSTART 0x8000 | ||||
#define ACIA_CTL_PORT 0x80 | #define ACIA_CTL_PORT 0x80 | ||||
@@ -31,6 +32,7 @@ static ACIA acia; | |||||
static SIO sio; | static SIO sio; | ||||
static SDC sdc; | static SDC sdc; | ||||
static SPI spi; | static SPI spi; | ||||
static AT28 at28; | |||||
static uint8_t iord_acia_ctl() | static uint8_t iord_acia_ctl() | ||||
{ | { | ||||
@@ -121,15 +123,23 @@ static void _write(uint8_t val) | |||||
if (use_sio) { sio_write(&sio, val); } else { acia_write(&acia, val); } | if (use_sio) { sio_write(&sio, val); } else { acia_write(&acia, val); } | ||||
} | } | ||||
static byte _at28_mem_read(int unused, ushort addr) { | |||||
return at28_mem_read(&at28, addr); | |||||
} | |||||
static void _at28_mem_write(int unused, ushort addr, byte val) { | |||||
at28_mem_write(&at28, addr, val); | |||||
} | |||||
static void usage() | static void usage() | ||||
{ | { | ||||
fprintf(stderr, "Usage: ./rc2014 [-s] [-c sdcard.img] /path/to/rom\n"); | |||||
fprintf(stderr, "Usage: ./rc2014 [-se] [-c sdcard.img] /path/to/rom\n"); | |||||
} | } | ||||
int main(int argc, char *argv[]) | int main(int argc, char *argv[]) | ||||
{ | { | ||||
FILE *fp = NULL; | FILE *fp = NULL; | ||||
int ch; | int ch; | ||||
bool use_at28 = false; | |||||
if (argc < 2) { | if (argc < 2) { | ||||
usage(); | usage(); | ||||
@@ -140,11 +150,14 @@ int main(int argc, char *argv[]) | |||||
sdc_init(&sdc); | sdc_init(&sdc); | ||||
spi_init(&spi, spix_sdc); | spi_init(&spi, spix_sdc); | ||||
while ((ch = getopt(argc, argv, "sc:")) != -1) { | |||||
while ((ch = getopt(argc, argv, "sec:")) != -1) { | |||||
switch (ch) { | switch (ch) { | ||||
case 's': | case 's': | ||||
use_sio = true; | use_sio = true; | ||||
break; | break; | ||||
case 'e': | |||||
use_at28 = true; | |||||
break; | |||||
case 'c': | case 'c': | ||||
fprintf(stderr, "Setting up SD card image with %s\n", optarg); | fprintf(stderr, "Setting up SD card image with %s\n", optarg); | ||||
sdc.fp = fopen(optarg, "r+"); | sdc.fp = fopen(optarg, "r+"); | ||||
@@ -193,6 +206,11 @@ int main(int argc, char *argv[]) | |||||
m->iowr[SDC_SPI] = iowr_spi; | m->iowr[SDC_SPI] = iowr_spi; | ||||
m->iord[SDC_CTL] = iord_spi_ctl; | m->iord[SDC_CTL] = iord_spi_ctl; | ||||
m->iowr[SDC_CTL] = iowr_spi_ctl; | m->iowr[SDC_CTL] = iowr_spi_ctl; | ||||
if (use_at28) { | |||||
at28_init(&at28, &m->cpu, 0x2000, 0x2000); | |||||
m->cpu.memRead = _at28_mem_read; | |||||
m->cpu.memWrite = _at28_mem_write; | |||||
} | |||||
char tosend = 0; | char tosend = 0; | ||||
while (emul_step()) { | while (emul_step()) { | ||||