emul/z80: add AT28 EEPROM emulator

This will facilitate the development of a solution for
cross-compiling directly to EEPROM.
This commit is contained in:
Virgil Dupras 2020-12-08 20:07:53 -05:00
parent 74f46c1288
commit 953e040231
7 changed files with 129 additions and 13 deletions

View File

@ -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

View File

@ -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

54
emul/z80/at28.c Normal file
View File

@ -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);
}
}

37
emul/z80/at28.h Normal file
View File

@ -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);

View File

@ -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.memRead = emul_mem_read;
m.cpu.memWrite = mem_write; 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;

View File

@ -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]; IORD iord[LEN8BIT];
IOWR iowr[0x100]; 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);

View File

@ -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()) {