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 | |||
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_spi.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). | |||
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`. | |||
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 | |||
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]; | |||
} | |||
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) { | |||
fprintf(stderr, "Writing to ROM (%d)!\n", addr); | |||
@@ -78,8 +78,8 @@ Machine* emul_init(char *binpath, ushort binoffset) | |||
} | |||
m.pchooks_cnt = 0; | |||
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.ioWrite = io_write; | |||
return &m; | |||
@@ -4,6 +4,8 @@ | |||
#include "z80.h" | |||
#define MAX_PCHOOK_COUNT 8 | |||
#define LEN8BIT 0x100 | |||
#define LEN16BIT 0x10000 | |||
typedef byte (*IORD) (); | |||
typedef void (*IOWR) (byte data); | |||
@@ -11,7 +13,7 @@ typedef byte (*EXCH) (byte data); | |||
typedef struct _Machine { | |||
Z80Context cpu; | |||
byte mem[0x10000]; | |||
byte mem[LEN16BIT]; | |||
// Set to non-zero to specify where ROM ends. Any memory write attempt | |||
// below ramstart will trigger a warning. | |||
ushort ramstart; | |||
@@ -21,8 +23,8 @@ typedef struct _Machine { | |||
ushort maxix; | |||
// Array of 0x100 function pointers to IO read and write routines. Leave to | |||
// 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 | |||
void (*pchookfunc) (struct _Machine *m); | |||
// 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_debugstr(char *s); | |||
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. | |||
byte iord_noop(); | |||
void iowr_noop(byte val); |
@@ -17,6 +17,7 @@ | |||
#include "sio.h" | |||
#include "sdc.h" | |||
#include "rc2014_spi.h" | |||
#include "at28.h" | |||
#define RAMSTART 0x8000 | |||
#define ACIA_CTL_PORT 0x80 | |||
@@ -31,6 +32,7 @@ static ACIA acia; | |||
static SIO sio; | |||
static SDC sdc; | |||
static SPI spi; | |||
static AT28 at28; | |||
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); } | |||
} | |||
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() | |||
{ | |||
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[]) | |||
{ | |||
FILE *fp = NULL; | |||
int ch; | |||
bool use_at28 = false; | |||
if (argc < 2) { | |||
usage(); | |||
@@ -140,11 +150,14 @@ int main(int argc, char *argv[]) | |||
sdc_init(&sdc); | |||
spi_init(&spi, spix_sdc); | |||
while ((ch = getopt(argc, argv, "sc:")) != -1) { | |||
while ((ch = getopt(argc, argv, "sec:")) != -1) { | |||
switch (ch) { | |||
case 's': | |||
use_sio = true; | |||
break; | |||
case 'e': | |||
use_at28 = true; | |||
break; | |||
case 'c': | |||
fprintf(stderr, "Setting up SD card image with %s\n", optarg); | |||
sdc.fp = fopen(optarg, "r+"); | |||
@@ -193,6 +206,11 @@ int main(int argc, char *argv[]) | |||
m->iowr[SDC_SPI] = iowr_spi; | |||
m->iord[SDC_CTL] = iord_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; | |||
while (emul_step()) { | |||