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:
parent
eed67c4768
commit
25fc0a3c72
@ -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
10
emul/hw/README.md
Normal 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
1
emul/hw/rc2014/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/classic
|
4
emul/hw/rc2014/Makefile
Normal file
4
emul/hw/rc2014/Makefile
Normal 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
11
emul/hw/rc2014/README.md
Normal 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
82
emul/hw/rc2014/acia.c
Normal 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
39
emul/hw/rc2014/acia.h
Normal 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
126
emul/hw/rc2014/classic.c
Normal 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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user