From 953e040231e6a7c501526c13497692fd03e2ad66 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Tue, 8 Dec 2020 20:07:53 -0500 Subject: [PATCH] emul/z80: add AT28 EEPROM emulator This will facilitate the development of a solution for cross-compiling directly to EEPROM. --- emul/z80/Makefile | 2 +- emul/z80/README.md | 9 ++++++--- emul/z80/at28.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ emul/z80/at28.h | 37 +++++++++++++++++++++++++++++++++++++ emul/z80/emul.c | 8 ++++---- emul/z80/emul.h | 10 +++++++--- emul/z80/rc2014.c | 22 ++++++++++++++++++++-- 7 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 emul/z80/at28.c create mode 100644 emul/z80/at28.h diff --git a/emul/z80/Makefile b/emul/z80/Makefile index c492b0f..9fa65a7 100644 --- a/emul/z80/Makefile +++ b/emul/z80/Makefile @@ -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 diff --git a/emul/z80/README.md b/emul/z80/README.md index 8bc9f64..77ae9a8 100644 --- a/emul/z80/README.md +++ b/emul/z80/README.md @@ -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 diff --git a/emul/z80/at28.c b/emul/z80/at28.c new file mode 100644 index 0000000..43ce547 --- /dev/null +++ b/emul/z80/at28.c @@ -0,0 +1,54 @@ +#include +#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); + } +} diff --git a/emul/z80/at28.h b/emul/z80/at28.h new file mode 100644 index 0000000..54dcbc1 --- /dev/null +++ b/emul/z80/at28.h @@ -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); diff --git a/emul/z80/emul.c b/emul/z80/emul.c index a53395b..49b13dc 100644 --- a/emul/z80/emul.c +++ b/emul/z80/emul.c @@ -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; diff --git a/emul/z80/emul.h b/emul/z80/emul.h index 9e55999..3ca2246 100644 --- a/emul/z80/emul.h +++ b/emul/z80/emul.h @@ -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); diff --git a/emul/z80/rc2014.c b/emul/z80/rc2014.c index a4fa518..cffe545 100644 --- a/emul/z80/rc2014.c +++ b/emul/z80/rc2014.c @@ -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()) {