mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-27 12:28:06 +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]
|
||||
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.
|
||||
|
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