Compare commits
9 Commits
14176984b8
...
24158df261
Author | SHA1 | Date | |
---|---|---|---|
24158df261 | |||
ed3ed7bd03 | |||
ec678cb3d0 | |||
b667561da9 | |||
882f258ad8 | |||
4bf29638ca | |||
6778f54f11 | |||
e136bfef33 | |||
1e62fbc306 |
@ -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
2
pat80-emulator/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
build
|
||||
|
11
pat80-emulator/CMakeLists.txt
Normal file
11
pat80-emulator/CMakeLists.txt
Normal 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})
|
17
pat80-emulator/CMakePresets.json
Normal file
17
pat80-emulator/CMakePresets.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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
256
pat80-emulator/main.c
Normal 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
|
Loading…
x
Reference in New Issue
Block a user