Compare commits

...

9 Commits

7 changed files with 309 additions and 5 deletions

View File

@ -0,0 +1,7 @@
; @language: Z80 ASM
; Simple possible code test:
; outputs a single character "A" on IO port 0, then halts
ld a,'A'
out (1),a
halt

2
pat80-emulator/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build

View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.10.0)
project(pat80-emulator VERSION 0.1.0 LANGUAGES C)
add_executable(pat80-emulator main.c)
find_package(Z80 REQUIRED)
target_link_libraries(pat80-emulator Z80)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR})
target_link_libraries(pat80-emulator ${CURSES_LIBRARIES})

View File

@ -0,0 +1,17 @@
{
"version": 8,
"configurePresets": [
{
"name": "gcc",
"displayName": "GCC 14.2.1 x86_64-pc-linux-gnu",
"description": "Using compilers: C = /usr/bin/gcc, CXX = /usr/bin/g++",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"cacheVariables": {
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
"CMAKE_C_COMPILER": "/usr/bin/gcc",
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
"CMAKE_BUILD_TYPE": "Debug"
}
}
]
}

View File

@ -1,7 +1,19 @@
# PAT80 Emulator
This folder contains a submodule (you should fetch it if you need to run the os in an emulator).
# Pat80 emulator
Uses cburbridge's Z80 emulator written in python. It opens some windows showing the emulated computer's memory map, the cpu registers state and parallel terminal to interact with the os.
## Setup
Install the required Z80 emulator by redcode following the instructions for your Linux distribution at https://github.com/redcode/Z80
Make sure to install not only the `libz80` package, but also `libz80-dev`.
Create the build directory: `mkdir build`
Create the makefile with cmake: `cd build && cmake ..`
## Build
Enter the `build` directory and run `make`.
An executable file named `pat80-emulator` is created.
## Usage
To run the os in the emulator, head to `pat80-computer/software/z80-assembly/os/Makefile` and run `make run` to build the rom from assembly and start the emulator.
Enter the `build` directory
Run `./pat80-emulator`

256
pat80-emulator/main.c Normal file
View File

@ -0,0 +1,256 @@
/**
* PAT80 Emulator
*
* Emulates a PAT80.
*/
#include <Z/constants/pointer.h> /* Z_NULL */
#include <Z80.h>
#include <string.h>
#include <stdlib.h>
#include <ncurses.h>
#define ROM_SIZE 0x8000 /* 32 KiB */
#define MEMORY_SIZE 0xFFFF /* 64 KiB */
#define TERMINAL_WIDTH 60
#define TERMINAL_HEIGHT 25
#define SPACING_BETWEEN_WINDOWS 3
#define INSTRUCTION_WINDOW_HEIGHT 3
typedef struct {
void* context;
zuint8 (* read)(void *context);
void (* write)(void *context, zuint8 value);
} Device;
typedef struct {
zusize cycles;
zuint8 memory[65536];
Z80 cpu;
WINDOW *terminal_win;
WINDOW *status_win;
} Machine;
static zuint8 machine_cpu_read(Machine *self, zuint16 address) {
return address < MEMORY_SIZE ? self->memory[address] : 0xFF;
}
static void machine_cpu_write(Machine *self, zuint16 address, zuint8 value) {
if (address >= ROM_SIZE && address < MEMORY_SIZE)
self->memory[address] = value;
}
static zuint8 machine_cpu_in(Machine *self, zuint16 port) {
// Pat80 has 8 devices, decoded based on the 3 most significant IO addr bits.
// Note the Z80 has 16 bit address bus, but only the first 8 are used as IO addr,
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
// A4-A0 may be used by the single device, at its own discretion.
zuint16 bitmask = 7; // 0000000000000111
int decoded = port & bitmask;
if (decoded <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal
// Read char from stin
char c = getch();
// Intercept emulator commands
switch (c) {
case 27:
// ESC: shutdown emulator
exit(0); // TODO: Shutdown ncurses and emulator cleanly
return 0;
default:
// Deliver keypress to pat80
return c;
}
}
if (decoded <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489)
wprintw(self->status_win, "sound_cmd[IN]: Not supported!\n");
return 0x00;
}
if (decoded <= 0x5F) {
// Port 2 (0x40 to 0x5F)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 2\n");
return 0x00;
}
if (decoded <= 0x7F) {
// Port 3 (0x60 to 0x7F)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 3\n");
return 0x00;
}
if (decoded <= 0x9F) {
// Port 4 (0x80 to 0x9F)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 4\n");
return 0x00;
}
if (decoded <= 0x5F) {
// Port 5 (0xA0 to 0xBF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 5\n");
return 0x00;
}
if (decoded <= 0x5F) {
// Port 6 (0xC0 to 0xDF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 6\n");
return 0x00;
}
if (decoded <= 0x5F) {
// Port 7 (0xE0 to 0xFF)
wprintw(self->status_win, "IO_ERROR_IN: No device at port 7\n");
} else {
wprintw(self->status_win, "IO_ERROR_IN: Invalid port address: %#04x\n", port);
}
// TODO: place this in a refresh cycle between CPU instructions
refresh();
wrefresh(self->terminal_win);
wrefresh(self->status_win);
}
static void machine_cpu_out(Machine *self, zuint16 port, zuint8 value) {
// Pat80 has 8 devices, decoded based on the 3 most significant IO addr bits.
// Note the Z80 has 16 bit address bus, but only the first 8 are used as IO addr,
// so the 3 most significant IO addr bits in this case are A7, A6, A5. The bits
// A4-A0 may be used by the single device, at its own discretion.
zuint16 bitmask = 0xE0; // 0000000011100000
int decoded = port & bitmask;
if (decoded <= 0x1F) {
// Port 0 (0x00 to 0x1F): terminal
wprintw(self->terminal_win, "%c", value);
} else if (decoded <= 0x3F) {
// Port 1 (0x20 to 0x3F): sound card (sn76489)
wprintw(self->status_win, "sound_cmd[%#04x]\n", value);
} else if (decoded <= 0x5F) {
// Port 2 (0x40 to 0x5F)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 2\n");
} else if (decoded <= 0x7F) {
// Port 3 (0x60 to 0x7F)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 3\n");
} else if (decoded <= 0x9F) {
// Port 4 (0x80 to 0x9F)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 4\n");
} else if (decoded <= 0x5F) {
// Port 5 (0xA0 to 0xBF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 5\n");
} else if (decoded <= 0x5F) {
// Port 6 (0xC0 to 0xDF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 6\n");
} else if (decoded <= 0x5F) {
// Port 7 (0xE0 to 0xFF)
wprintw(self->status_win, "IO_ERROR_OUT: No device at port 7\n");
} else {
wprintw(self->status_win, "IO_ERROR_OUT: Invalid port address: %#04x\n", port);
}
// TODO: place this in a refresh cycle between CPU instructions
refresh();
wrefresh(self->terminal_win);
wrefresh(self->status_win);
}
void machine_initialize(Machine *self) {
self->cpu.context = self;
self->cpu.fetch_opcode =
self->cpu.fetch =
self->cpu.nop =
self->cpu.read = (Z80Read )machine_cpu_read;
self->cpu.write = (Z80Write)machine_cpu_write;
self->cpu.in = (Z80Read )machine_cpu_in;
self->cpu.out = (Z80Write)machine_cpu_out;
self->cpu.halt = Z_NULL;
self->cpu.nmia = Z_NULL;
self->cpu.inta = Z_NULL;
self->cpu.int_fetch = Z_NULL;
self->cpu.ld_i_a = Z_NULL;
self->cpu.ld_r_a = Z_NULL;
self->cpu.reti = Z_NULL;
self->cpu.retn = Z_NULL;
self->cpu.hook = Z_NULL;
self->cpu.illegal = Z_NULL;
self->cpu.options = Z80_MODEL_ZILOG_NMOS;
}
void machine_power(Machine *self, zbool state) {
if (state)
{
self->cycles = 0;
memset(self->memory, 0, MEMORY_SIZE);
}
z80_power(&self->cpu, state);
}
void machine_reset(Machine *self) {
z80_instant_reset(&self->cpu);
}
void machine_run(Machine *self) {
z80_run(&self->cpu, Z80_MAXIMUM_CYCLES);
}
int main(int argc, char *argv[]) {
// Parse arguments
if (argc < 2) {
printf("Usage: %s [romFile]\n", argv[0]);
exit(0);
}
char* romFilePath = argv[1];
// Init ncurses
initscr(); // Start curses mode
raw(); // Line buffering disabled (get character without waiting for ENTER key)
keypad(stdscr, TRUE); // We get F1, F2 etc..
noecho(); // Don't echo() while we do getch
start_color(); // Use colors
init_pair(1, COLOR_WHITE, COLOR_BLUE); // Terminal window color
init_pair(2, COLOR_YELLOW, COLOR_BLACK); // Status window color
int x,y;
getmaxyx(stdscr, y,x);
// Setup virtual Pat80 computer
Z80 pat80Cpu = {};
Machine pat80 = {
/*zusize*/ .cycles = 0,
/*Z80*/ .cpu = pat80Cpu,
.terminal_win = newwin(TERMINAL_HEIGHT, TERMINAL_WIDTH, INSTRUCTION_WINDOW_HEIGHT, 0),
.status_win = newwin(y, x - TERMINAL_WIDTH - SPACING_BETWEEN_WINDOWS, INSTRUCTION_WINDOW_HEIGHT, TERMINAL_WIDTH + SPACING_BETWEEN_WINDOWS)
};
wbkgd(pat80.terminal_win, COLOR_PAIR(1)); // Ncurses: set terminal window color
wbkgd(pat80.status_win, COLOR_PAIR(2));
scrollok(pat80.terminal_win, TRUE); // Ncurses: Allow scrolling when reached end of window
scrollok(pat80.status_win, TRUE);
attron(A_BOLD); // Print instructions
printw("Emulator commands\n");
attroff(A_BOLD);
printw("ESC: Exit");
machine_initialize(&pat80);
machine_power(&pat80, Z_TRUE);
// Load ROM into memory
FILE *romFile;
romFile = fopen(romFilePath,"rb");
if (romFile == NULL) {
printf("Unable to open rom file at %s", romFilePath);
exit(1);
}
fread(&pat80.memory,ROM_SIZE,1,romFile); // load rom from file into memory, up >
fclose(romFile);
// Start emulated computer
machine_reset(&pat80);
machine_run(&pat80);
// Stop ncurses
delwin(pat80.terminal_win);
delwin(pat80.status_win);
endwin();
return 0;
}

@ -1 +0,0 @@
Subproject commit 46d5391d5c890b2608389db5f2e8e470ef99f254