1
0
mirror of https://github.com/hsoft/collapseos.git synced 2024-11-30 21:48:08 +11:00

Add RC2014 classic emulation

This commit is contained in:
Virgil Dupras 2019-12-31 22:03:48 -05:00
parent eed67c4768
commit 25fc0a3c72
8 changed files with 283 additions and 4 deletions

View File

@ -3,6 +3,16 @@
This folder contains a couple of tools running under the [libz80][libz80] This folder contains a couple of tools running under the [libz80][libz80]
emulator. 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 ## Build
First, make sure that the `libz80` git submodule is checked out. If not, run 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 Through that, it becomes easier to develop userspace applications for Collapse
OS. 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 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 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. alternate CFS device file (it has to be packaed already) through the `-f` flag.

10
emul/hw/README.md Normal file
View File

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

1
emul/hw/rc2014/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/classic

4
emul/hw/rc2014/Makefile Normal file
View File

@ -0,0 +1,4 @@
OBJS = acia.o classic.o ../../emul.o ../../libz80/libz80.o
classic: $(OBJS)
$(CC) $(OBJS) -o $@

11
emul/hw/rc2014/README.md Normal file
View File

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

82
emul/hw/rc2014/acia.c Normal file
View File

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

39
emul/hw/rc2014/acia.h Normal file
View File

@ -0,0 +1,39 @@
#include <stdint.h>
#include <stdbool.h>
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);

126
emul/hw/rc2014/classic.c Normal file
View File

@ -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 <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#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;
}