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:
parent
74f46c1288
commit
953e040231
@ -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
|
||||
|
54
emul/z80/at28.c
Normal file
54
emul/z80/at28.c
Normal 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
37
emul/z80/at28.h
Normal 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);
|
@ -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()) {
|
||||
|
Loading…
Reference in New Issue
Block a user