From 25fc0a3c72a7b5f5b683bc2852a2c314869f7d42 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Tue, 31 Dec 2019 22:03:48 -0500 Subject: [PATCH] Add RC2014 classic emulation --- emul/README.md | 14 ++++-- emul/hw/README.md | 10 ++++ emul/hw/rc2014/.gitignore | 1 + emul/hw/rc2014/Makefile | 4 ++ emul/hw/rc2014/README.md | 11 ++++ emul/hw/rc2014/acia.c | 82 ++++++++++++++++++++++++++++++ emul/hw/rc2014/acia.h | 39 ++++++++++++++ emul/hw/rc2014/classic.c | 126 ++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 283 insertions(+), 4 deletions(-) create mode 100644 emul/hw/README.md create mode 100644 emul/hw/rc2014/.gitignore create mode 100644 emul/hw/rc2014/Makefile create mode 100644 emul/hw/rc2014/README.md create mode 100644 emul/hw/rc2014/acia.c create mode 100644 emul/hw/rc2014/acia.h create mode 100644 emul/hw/rc2014/classic.c diff --git a/emul/README.md b/emul/README.md index 51c692d..c0970f7 100644 --- a/emul/README.md +++ b/emul/README.md @@ -3,6 +3,16 @@ This folder contains a couple of tools running under the [libz80][libz80] emulator. +## Not real hardware + +In the few emulated apps described below, we don't try to emulate real hardware +because the goal here is to facilitate userspace development. + +These apps run on imaginary hardware and use many cheats to simplify I/Os. + +For real hardware emulation (which helps developing drivers), see the `hw` +folder. + ## Build First, make sure that the `libz80` git submodule is checked out. If not, run @@ -21,10 +31,6 @@ device that is suitable for Collapse OS's filesystem to run on. Through that, it becomes easier to develop userspace applications for Collapse OS. -We don't try to emulate real hardware to ease the development of device drivers -because so far, I don't see the advantage of emulation versus running code on -the real thing. - By default, the shell initialized itself with a CFS device containing the contents of `cfsin/` at launch (it's packed on the fly). You can specify an alternate CFS device file (it has to be packaed already) through the `-f` flag. diff --git a/emul/hw/README.md b/emul/hw/README.md new file mode 100644 index 0000000..4900d19 --- /dev/null +++ b/emul/hw/README.md @@ -0,0 +1,10 @@ +# Hardware emulation + +In this folder, we emulate some of the hardware used in recipes. The emulation +is done in a simplistic manner, just enough to verify that the driver code for +it works generally well. No tricky stuff implemented. + +This kind of emulation is useful for detecting obvious regressions without +having to get the code on actual hardware for the upteenth time. + +To use, go to the appropriate subfolder and read README there. diff --git a/emul/hw/rc2014/.gitignore b/emul/hw/rc2014/.gitignore new file mode 100644 index 0000000..44e73b6 --- /dev/null +++ b/emul/hw/rc2014/.gitignore @@ -0,0 +1 @@ +/classic diff --git a/emul/hw/rc2014/Makefile b/emul/hw/rc2014/Makefile new file mode 100644 index 0000000..f5f613c --- /dev/null +++ b/emul/hw/rc2014/Makefile @@ -0,0 +1,4 @@ +OBJS = acia.o classic.o ../../emul.o ../../libz80/libz80.o + +classic: $(OBJS) + $(CC) $(OBJS) -o $@ diff --git a/emul/hw/rc2014/README.md b/emul/hw/rc2014/README.md new file mode 100644 index 0000000..2cef11f --- /dev/null +++ b/emul/hw/rc2014/README.md @@ -0,0 +1,11 @@ +# RC2014 emulation + +This emulates a RC2014 classic with 8K of ROM, 32K of RAM and an ACIA hooked to +stdin/stdout. + +Run `make` to build. + +## Usage + +Run `./classic /path/to/rom` (for example, `os.bin` from RC2014's recipe). +Serial I/O is hooked to stdin/stdout. `CTRL+D` to quit. diff --git a/emul/hw/rc2014/acia.c b/emul/hw/rc2014/acia.c new file mode 100644 index 0000000..abe79de --- /dev/null +++ b/emul/hw/rc2014/acia.c @@ -0,0 +1,82 @@ +#include "acia.h" + +static void _check_irq(ACIA *acia) +{ + // do we have RDRF? + if ((acia->status & 0x01) && (acia->control & 0x80)) { + acia->status |= 0x80; + } + // do we have TDRE? + if ((acia->status & 0x02) && ((acia->control & 0xe0) == 0xe0)) { + acia->status |= 0x80; + } +} + +void acia_init(ACIA *acia) +{ + acia->status = 0x02; // TDRE + acia->control = 0x00; + acia->rx = 0; + acia->tx = 0; + acia->in_int = false; +} + +bool acia_has_irq(ACIA *acia) +{ + if (acia->in_int) { + return false; + } + acia->in_int = acia->status & 0x80; + return acia->in_int; +} + +bool acia_hasrx(ACIA *acia) +{ + return acia->status & 0x01; // RDRF +} + +bool acia_hastx(ACIA *acia) +{ + return !(acia->status & 0x02); // TRDE +} + +uint8_t acia_read(ACIA *acia) +{ + acia->status |= 0x02; // TRDE high + _check_irq(acia); + return acia->tx; +} + +void acia_write(ACIA *acia, uint8_t val) +{ + acia->status |= 0x01; // RDRF high + acia->rx = val; + _check_irq(acia); +} + +uint8_t acia_ctl_rd(ACIA *acia) +{ + return acia->status; +} + +void acia_ctl_wr(ACIA *acia, uint8_t val) +{ + acia->control = val; + _check_irq(acia); +} + +uint8_t acia_data_rd(ACIA *acia) +{ + acia->status &= ~0x81; // RDRF and IRQ low + acia->in_int = false; + return acia->rx; +} + +void acia_data_wr(ACIA *acia, uint8_t val) +{ + acia->tx = val; + acia->status &= ~0x82; // TRDE and IRQ low + acia->in_int = false; + _check_irq(acia); +} + diff --git a/emul/hw/rc2014/acia.h b/emul/hw/rc2014/acia.h new file mode 100644 index 0000000..1125598 --- /dev/null +++ b/emul/hw/rc2014/acia.h @@ -0,0 +1,39 @@ +#include +#include + +typedef struct { + // Bit 7: interrupt status, low when interrupt request pending. + // Bit 6: Parity error + // Bit 5: Receiver overrun + // Bit 4: Framing error + // Bit 3: Clear To Send + // Bit 2: Data Carrier Detected + // Bit 1: Transmit Data Register Empty (TDRE) + // Bit 0: Receive Data Register Full (RDRF) + // We care about bits 7, 1, 0, maybe 5 later. + uint8_t status; + // Bit 7: interrupt enable + // Bits 6:5: RTS + transmit interrupt enable + // Bits 4:2: parity + stop bit + // Bits 1:0: speed divider + // We don't actually care about any of those except the interrupt enable + // bits. + uint8_t control; + uint8_t rx; + uint8_t tx; + // Will be set to true the first time acia_has_irq() is called when IRQ is + // set. Then, as long as it stays true, acia_has_irq() will return false. + // When IRQ status is reset, so is in_int. + bool in_int; +} ACIA; + +void acia_init(ACIA *acia); +bool acia_has_irq(ACIA *acia); +bool acia_hasrx(ACIA *acia); +bool acia_hastx(ACIA *acia); +uint8_t acia_read(ACIA *acia); +void acia_write(ACIA *acia, uint8_t val); +uint8_t acia_ctl_rd(ACIA *acia); +void acia_ctl_wr(ACIA *acia, uint8_t val); +uint8_t acia_data_rd(ACIA *acia); +void acia_data_wr(ACIA *acia, uint8_t val); diff --git a/emul/hw/rc2014/classic.c b/emul/hw/rc2014/classic.c new file mode 100644 index 0000000..e7ce493 --- /dev/null +++ b/emul/hw/rc2014/classic.c @@ -0,0 +1,126 @@ +/* RC2014 classic + * + * - 8K of ROM in range 0x0000-0x2000 + * - 32K of RAM in range 0x8000-0xffff + * - ACIA in ports 0x80 (ctl) and 0x81 (data) + * + * ACIA is hooked to stdin/stdout. CTRL+D exits when in TTY mode. + */ + +#include +#include +#include +#include +#include "../../emul.h" +#include "acia.h" + +#define RAMSTART 0x8000 +#define ACIA_CTL_PORT 0x80 +#define ACIA_DATA_PORT 0x81 +#define MAX_ROMSIZE 0x2000 + +static ACIA acia; + +static uint8_t iord_acia_ctl() +{ + return acia_ctl_rd(&acia); +} + +static uint8_t iord_acia_data() +{ + return acia_data_rd(&acia); +} + +static void iowr_acia_ctl(uint8_t val) +{ + acia_ctl_wr(&acia, val); +} + +static void iowr_acia_data(uint8_t val) +{ + acia_data_wr(&acia, val); +} + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + fprintf(stderr, "Usage: ./classic /path/to/rom\n"); + return 1; + } + FILE *fp = fopen(argv[1], "r"); + if (fp == NULL) { + fprintf(stderr, "Can't open %s\n", optarg); + return 1; + } + Machine *m = emul_init(); + m->ramstart = RAMSTART; + int i = 0; + int c; + while ((c = fgetc(fp)) != EOF && i < MAX_ROMSIZE) { + m->mem[i++] = c & 0xff; + } + pclose(fp); + if (i == MAX_ROMSIZE) { + fprintf(stderr, "ROM image too large.\n"); + return 1; + } + bool tty = isatty(fileno(stdin)); + struct termios term, saved_term; + if (tty) { + // Turn echo off: the shell takes care of its own echoing. + if (tcgetattr(0, &term) == -1) { + printf("Can't setup terminal.\n"); + return 1; + } + saved_term = term; + term.c_lflag &= ~ECHO; + term.c_lflag &= ~ICANON; + term.c_cc[VMIN] = 0; + term.c_cc[VTIME] = 0; + tcsetattr(0, TCSADRAIN, &term); + } + + acia_init(&acia); + m->iord[ACIA_CTL_PORT] = iord_acia_ctl; + m->iord[ACIA_DATA_PORT] = iord_acia_data; + m->iowr[ACIA_CTL_PORT] = iowr_acia_ctl; + m->iowr[ACIA_DATA_PORT] = iowr_acia_data; + + char tosend = 0; + while (emul_step()) { + // Do we have an interrupt? + if (acia_has_irq(&acia)) { + Z80INT(&m->cpu, 0); + } + // Is the RC2014 transmitting? + if (acia_hastx(&acia)) { + putchar(acia_read(&acia)); + fflush(stdout); + } + // Do we have something to send? + if (!tosend) { + char c; + if (read(fileno(stdin), &c, 1) == 1) { + if (c == 4) { // CTRL+D + // Stop here + break; + } + tosend = c; + } else if (!tty) { + // This means we reached EOF + break; + } + } + if (tosend && !acia_hasrx(&acia)) { + acia_write(&acia, tosend); + tosend = 0; + } + } + + if (tty) { + printf("Done!\n"); + tcsetattr(0, TCSADRAIN, &saved_term); + emul_printdebug(); + } + return 0; +}