@@ -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. | |||
@@ -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. |
@@ -0,0 +1 @@ | |||
/classic |
@@ -0,0 +1,4 @@ | |||
OBJS = acia.o classic.o ../../emul.o ../../libz80/libz80.o | |||
classic: $(OBJS) | |||
$(CC) $(OBJS) -o $@ |
@@ -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. |
@@ -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); | |||
} | |||
@@ -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); |
@@ -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; | |||
} |