mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-23 12:48:05 +11:00
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
|
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
|
||||||
|
@ -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
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];
|
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;
|
||||||
|
@ -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);
|
||||||
|
@ -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()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user