Added memory map, partial keyboard input and disk support
This commit is contained in:
parent
3e5b1372b0
commit
6d6a304bd2
434
src/main.cpp
434
src/main.cpp
|
@ -5,6 +5,8 @@
|
|||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <queue>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_error.h>
|
||||
|
@ -14,15 +16,328 @@
|
|||
#include <SDL2/SDL_main.h>
|
||||
#include <SDL2/SDL_render.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <string>
|
||||
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_keyboard.h"
|
||||
#include "SDL_keycode.h"
|
||||
#include "Z/types/base.h"
|
||||
#include "Z80.h"
|
||||
|
||||
#define SD_PAGES 1
|
||||
#define CPM_PAGE_SIZE 128
|
||||
#define SD_PAGE_SIZE 512
|
||||
#define CPM_PAGES (SD_PAGE_SIZE/CPM_PAGE_SIZE*SD_PAGES)
|
||||
|
||||
template <int W, int H>
|
||||
class Screen {
|
||||
public:
|
||||
// @todo This should handle the input properly (same as fpga)
|
||||
void input(char c) {
|
||||
if (escape == 1) {
|
||||
if (c == '[') {
|
||||
escape = 2;
|
||||
} else {
|
||||
escape = 0;
|
||||
}
|
||||
} else if (escape) {
|
||||
switch (c) {
|
||||
// For now we are only going to implement what we actually use
|
||||
case 'K':
|
||||
// Assume parameter 0
|
||||
clear_eol();
|
||||
escape = 0;
|
||||
break;
|
||||
|
||||
case 'H':
|
||||
if (escape_parameter_1 == 0) {
|
||||
escape_parameter_1 = 1;
|
||||
}
|
||||
|
||||
if (escape_parameter_2 == 0) {
|
||||
escape_parameter_2 = 1;
|
||||
}
|
||||
|
||||
cursor.x = escape_parameter_1 - 1;
|
||||
cursor.y = escape_parameter_2 - 1;
|
||||
escape = 0;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
if (escape_parameter_1 == 0) {
|
||||
reverse(0);
|
||||
set_foreground(0b111);
|
||||
set_background(0);
|
||||
} else if (escape_parameter_1 == 7) {
|
||||
reverse(1);
|
||||
} else if (escape_parameter_1 >= 30 && escape_parameter_1 <= 37) {
|
||||
set_foreground(escape_parameter_1 - 30);
|
||||
} else if (escape_parameter_1 >= 40 && escape_parameter_1 <= 47) {
|
||||
set_background(escape_parameter_1 - 40);
|
||||
} else if (escape_parameter_1 >= 90 && escape_parameter_1 <= 97) {
|
||||
set_foreground(escape_parameter_1 - 90 + 8);
|
||||
} else if (escape_parameter_1 >= 100 && escape_parameter_1 <= 107) {
|
||||
set_background(escape_parameter_1 - 100 + 8);
|
||||
}
|
||||
escape = 0;
|
||||
break;
|
||||
|
||||
case 'J':
|
||||
// Assume parameter 2
|
||||
clear_screen();
|
||||
escape = 0;
|
||||
break;
|
||||
|
||||
case '0' ... '9':
|
||||
escape_parameter_1 *= 10;
|
||||
escape_parameter_1 += (c - 48);
|
||||
break;
|
||||
|
||||
case ';':
|
||||
escape_parameter_2 = escape_parameter_1;
|
||||
escape_parameter_1 = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
escape = 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (c) {
|
||||
case '\n':
|
||||
cursor.y++;
|
||||
if (cursor.y >= 45) {
|
||||
cursor.y--;
|
||||
cursor.scroll = (cursor.scroll + 1) % 45;
|
||||
clear_eol();
|
||||
}
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
cursor.x = 0;
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
previous();
|
||||
break;
|
||||
|
||||
case 0x1B:
|
||||
escape = 1;
|
||||
escape_parameter_1 = 0;
|
||||
escape_parameter_2 = 0;
|
||||
// Handle escape code
|
||||
break;
|
||||
|
||||
default:
|
||||
write(cursor.x, cursor.y, c);
|
||||
next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char get(int x, int y) {
|
||||
return buffer[y*W + x];
|
||||
}
|
||||
|
||||
private:
|
||||
void set_foreground(int color) {
|
||||
|
||||
}
|
||||
|
||||
void set_background(int color) {
|
||||
|
||||
}
|
||||
|
||||
void reverse(bool enable) {
|
||||
|
||||
}
|
||||
|
||||
void clear_screen() {
|
||||
reverse(0);
|
||||
set_foreground(0b111);
|
||||
set_background(0);
|
||||
|
||||
for (char& c : buffer) {
|
||||
c = 0x00;
|
||||
}
|
||||
|
||||
cursor = {0, 0, 0};
|
||||
}
|
||||
|
||||
void clear_eol() {
|
||||
reverse(0);
|
||||
set_foreground(0b111);
|
||||
set_background(0);
|
||||
|
||||
for (int i = cursor.x; i < W; ++i) {
|
||||
write(i, cursor.y, 0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void write(int x, int y, char c) {
|
||||
buffer[y*W + x] = c;
|
||||
}
|
||||
|
||||
void next() {
|
||||
cursor.x++;
|
||||
if (cursor.x >= W) {
|
||||
cursor.y++;
|
||||
cursor.x %= W;
|
||||
}
|
||||
|
||||
if (cursor.y >= H) {
|
||||
cursor.y--;
|
||||
cursor.scroll = (cursor.scroll + 1) % H;
|
||||
clear_eol();
|
||||
}
|
||||
}
|
||||
|
||||
void previous() {
|
||||
cursor.x--;
|
||||
|
||||
if (cursor.x < 0) {
|
||||
cursor.y--;
|
||||
cursor.x %= W;
|
||||
}
|
||||
|
||||
if (cursor.y < 0) {
|
||||
cursor.y = 0;
|
||||
cursor.x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::array<char, W*H> buffer = {0};
|
||||
|
||||
int escape = 0;
|
||||
int escape_parameter_1 = 0;
|
||||
int escape_parameter_2 = 0;
|
||||
|
||||
struct {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int scroll = 0;
|
||||
} cursor;
|
||||
};
|
||||
|
||||
class Memory {
|
||||
public:
|
||||
Memory(std::filesystem::path rom_name) {
|
||||
std::ifstream file(rom_name, std::ios::in | std::ios::binary);
|
||||
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open: " + rom_name.string());
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
if (size > rom.size()) {
|
||||
throw std::runtime_error("ROM file is larger than avaiable ram");
|
||||
}
|
||||
|
||||
file.read(reinterpret_cast<char*>(rom.data()), size);
|
||||
}
|
||||
|
||||
void config(int i) {
|
||||
cfg = i;
|
||||
}
|
||||
|
||||
void write(uint16_t address, uint8_t value) {
|
||||
if (cfg == 0 && address < 0x1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
ram[address] = value;
|
||||
}
|
||||
|
||||
uint8_t read(uint16_t address) {
|
||||
if (cfg == 0 && address < 0x1000) {
|
||||
return rom[address];
|
||||
}
|
||||
return ram[address];
|
||||
}
|
||||
|
||||
private:
|
||||
int cfg = 0;
|
||||
|
||||
// We do not initialize ram as we want it to contain random value
|
||||
std::array<uint8_t, 0xFFFF> ram;
|
||||
std::array<uint8_t, 0x1000> rom{};
|
||||
};
|
||||
|
||||
enum Action {
|
||||
NONE = 0x30,
|
||||
READ = 0x20,
|
||||
WRITE = 0x30
|
||||
};
|
||||
|
||||
class Disk {
|
||||
public:
|
||||
Disk(std::filesystem::path disk_name) : file(disk_name, std::ios::in | std::ios::out | std::ios::binary) {
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Failed to open: " + disk_name.string());
|
||||
}
|
||||
}
|
||||
|
||||
void set_lba(uint8_t value, int part) {
|
||||
lba_part[part] = value;
|
||||
}
|
||||
|
||||
void set_action(Action a) {
|
||||
if (a == Action::READ || a == Action::WRITE) {
|
||||
int lba = (lba_part[0]) + (lba_part[1] << 8) + (lba_part[2] << 16);
|
||||
|
||||
file.seekg(lba*128, std::ios::beg);
|
||||
file.seekp(lba*128, std::ios::beg);
|
||||
|
||||
action = a;
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void write(uint8_t value) {
|
||||
if (action == Action::WRITE) {
|
||||
file.write(reinterpret_cast<char*>(&value), 1);
|
||||
counter++;
|
||||
|
||||
if (counter >= CPM_PAGE_SIZE) {
|
||||
action = Action::NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read() {
|
||||
uint8_t value = 0;
|
||||
if (action == Action::READ) {
|
||||
file.read(reinterpret_cast<char*>(&value), 1);
|
||||
counter++;
|
||||
|
||||
if (counter >= CPM_PAGE_SIZE) {
|
||||
action = Action::NONE;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
bool is_ready() {
|
||||
return action != Action::NONE;
|
||||
}
|
||||
|
||||
private:
|
||||
std::fstream file;
|
||||
|
||||
Action action = Action::NONE;
|
||||
int lba_part[3] = {0};
|
||||
size_t counter = 0;
|
||||
};
|
||||
|
||||
template <int W, int H>
|
||||
class Emulator {
|
||||
public:
|
||||
Emulator(std::filesystem::path rom_name) {
|
||||
Emulator(std::filesystem::path rom_name, std::filesystem::path disk_name) : memory(rom_name), disk(disk_name) {
|
||||
// SDL
|
||||
int render_flags = SDL_RENDERER_ACCELERATED;
|
||||
int window_flags = 0;
|
||||
|
@ -54,14 +369,6 @@ class Emulator {
|
|||
}
|
||||
|
||||
// Z80
|
||||
std::ifstream rom_file(rom_name, std::ios::in | std::ios::binary);
|
||||
|
||||
rom_file.seekg(0, std::ios::end);
|
||||
size_t rom_size = rom_file.tellg();
|
||||
rom_file.seekg(0, std::ios::beg);
|
||||
|
||||
rom_file.read(reinterpret_cast<char*>(memory.data()), rom_size);
|
||||
|
||||
z.context = this;
|
||||
z.read = z_read;
|
||||
z.write = z_write;
|
||||
|
@ -70,6 +377,8 @@ class Emulator {
|
|||
z.int_data = z_int_data;
|
||||
|
||||
z80_reset(&z);
|
||||
|
||||
SDL_StartTextInput();
|
||||
}
|
||||
|
||||
~Emulator() {
|
||||
|
@ -82,26 +391,39 @@ class Emulator {
|
|||
|
||||
static zuint8 z_read(void* context, zuint16 address) {
|
||||
auto e = (Emulator<W,H>*)context;
|
||||
return e->memory[address];
|
||||
return e->memory.read(address);
|
||||
}
|
||||
|
||||
static void z_write(void* context, zuint16 address, zuint8 value) {
|
||||
auto e = (Emulator<W,H>*)context;
|
||||
e->memory[address] = value;
|
||||
e->memory.write(address, value);
|
||||
}
|
||||
|
||||
static zuint8 z_in(void* /* context */, zuint16 port) {
|
||||
static zuint8 z_in(void* context, zuint16 port) {
|
||||
auto e = (Emulator<W,H>*)context;
|
||||
|
||||
switch (port & 0xFF) {
|
||||
case 0x03:
|
||||
return 0x01;
|
||||
|
||||
// char
|
||||
case 0x1E:
|
||||
return 0x00;
|
||||
// Input
|
||||
case 0x1E: {
|
||||
char c = e->input.front();
|
||||
e->input.pop();
|
||||
return c;
|
||||
}
|
||||
|
||||
// has_input
|
||||
// Has input
|
||||
case 0x1F:
|
||||
return 0x00;
|
||||
return e->input.size() ? 0x01 : 0x00;
|
||||
|
||||
// Data
|
||||
case 0x08:
|
||||
return e->disk.read();
|
||||
|
||||
// Has data
|
||||
case 0x0f:
|
||||
return e->disk.is_ready() ? 0x08 : 0x00;
|
||||
|
||||
default:
|
||||
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "IO Read @ 0x%X\n", (port & 0xFF));
|
||||
|
@ -114,8 +436,27 @@ class Emulator {
|
|||
auto e = (Emulator<W,H>*)context;
|
||||
|
||||
switch (port & 0xFF) {
|
||||
case 0x00:
|
||||
case 0x01:
|
||||
e->memory.config(port & 0xFF);
|
||||
break;
|
||||
|
||||
case 0x02:
|
||||
e->input(value);
|
||||
e->screen.input(value);
|
||||
break;
|
||||
|
||||
case 0x08:
|
||||
e->disk.write(value);
|
||||
break;
|
||||
|
||||
case 0x0b:
|
||||
case 0x0c:
|
||||
case 0x0d:
|
||||
e->disk.set_lba(value, (port & 0xFF) - 0x0b);
|
||||
break;
|
||||
|
||||
case 0x0f:
|
||||
e->disk.set_action((Action)value);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -129,21 +470,11 @@ class Emulator {
|
|||
}
|
||||
|
||||
void update() {
|
||||
z80_run(&z, 1);
|
||||
// Run for 1000 cycles
|
||||
z80_run(&z, 1000);
|
||||
}
|
||||
|
||||
// @todo This should handle the input properly (same as fpga)
|
||||
void input(char c) {
|
||||
buffer[cursor.y*W + cursor.x] = c;
|
||||
|
||||
cursor.x++;
|
||||
if (cursor.x > W) {
|
||||
cursor.x = 0;
|
||||
cursor.y++;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_buffer() {
|
||||
void draw_screen() {
|
||||
SDL_Rect src;
|
||||
src.w = 16;
|
||||
src.h = 16;
|
||||
|
@ -156,7 +487,7 @@ class Emulator {
|
|||
|
||||
for (int x = 0; x < W; ++x) {
|
||||
for (int y = 0; y < H; ++y) {
|
||||
char c = buffer[y*W + x];
|
||||
char c = screen.get(x, y);
|
||||
src.x = (c % 16) * 16;
|
||||
src.y = (c / 16) * 16;
|
||||
|
||||
|
@ -176,6 +507,26 @@ class Emulator {
|
|||
case SDL_QUIT:
|
||||
return false;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_RETURN) {
|
||||
input.push('\r');
|
||||
} else if (event.key.keysym.sym == SDLK_BACKSPACE) {
|
||||
input.push(0x08);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SDL_TEXTINPUT: {
|
||||
char c = event.text.text[0];
|
||||
// @todo For some reason we cannot capture ctrl???
|
||||
if (SDL_GetModState() & KMOD_ALT && c == 'c') {
|
||||
input.push(0x03);
|
||||
} else {
|
||||
input.push(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -198,26 +549,25 @@ class Emulator {
|
|||
SDL_Window* window = nullptr;
|
||||
SDL_Texture* font = nullptr;
|
||||
|
||||
std::array<char, W*H> buffer = {0};
|
||||
|
||||
struct {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
} cursor;
|
||||
Screen<W, H> screen;
|
||||
|
||||
Z80 z;
|
||||
std::array<uint8_t, 0xFFFF> memory;
|
||||
Memory memory;
|
||||
Disk disk;
|
||||
|
||||
std::queue<char> input;
|
||||
bool ctrl = false;
|
||||
};
|
||||
|
||||
int main() {
|
||||
Emulator<80, 45> e("../../software/monitor/.build/rom_monitor.bin");
|
||||
Emulator<80, 45> e("../../software/monitor/.build/rom_monitor.bin", "../create-disk/disk.img");
|
||||
|
||||
while (e.handle_events()) {
|
||||
e.prepare();
|
||||
|
||||
e.update();
|
||||
|
||||
e.draw_buffer();
|
||||
e.draw_screen();
|
||||
|
||||
e.render();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user