Browse Source

emul/z80: add AT28 EEPROM emulator

This will facilitate the development of a solution for
cross-compiling directly to EEPROM.
master
Virgil Dupras 3 years ago
parent
commit
953e040231
7 changed files with 129 additions and 13 deletions
  1. +1
    -1
      emul/z80/Makefile
  2. +6
    -3
      emul/z80/README.md
  3. +54
    -0
      emul/z80/at28.c
  4. +37
    -0
      emul/z80/at28.h
  5. +4
    -4
      emul/z80/emul.c
  6. +7
    -3
      emul/z80/emul.h
  7. +20
    -2
      emul/z80/rc2014.c

+ 1
- 1
emul/z80/Makefile View File

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


+ 6
- 3
emul/z80/README.md View File

@@ -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
- 0
emul/z80/at28.c 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
- 0
emul/z80/at28.h 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);

+ 4
- 4
emul/z80/emul.c 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];
}

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;


+ 7
- 3
emul/z80/emul.h View File

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

+ 20
- 2
emul/z80/rc2014.c View File

@@ -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…
Cancel
Save