Added memory map, partial keyboard input and disk support

This commit is contained in:
Dreaded_X 2021-01-14 22:20:24 +01:00
parent 3e5b1372b0
commit 6d6a304bd2

View File

@ -5,6 +5,8 @@
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <string>
#include <queue>
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <SDL2/SDL_error.h> #include <SDL2/SDL_error.h>
@ -14,15 +16,328 @@
#include <SDL2/SDL_main.h> #include <SDL2/SDL_main.h>
#include <SDL2/SDL_render.h> #include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.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 "Z/types/base.h"
#include "Z80.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> template <int W, int H>
class Emulator { class Emulator {
public: 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 // SDL
int render_flags = SDL_RENDERER_ACCELERATED; int render_flags = SDL_RENDERER_ACCELERATED;
int window_flags = 0; int window_flags = 0;
@ -54,14 +369,6 @@ class Emulator {
} }
// Z80 // 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.context = this;
z.read = z_read; z.read = z_read;
z.write = z_write; z.write = z_write;
@ -70,6 +377,8 @@ class Emulator {
z.int_data = z_int_data; z.int_data = z_int_data;
z80_reset(&z); z80_reset(&z);
SDL_StartTextInput();
} }
~Emulator() { ~Emulator() {
@ -82,26 +391,39 @@ class Emulator {
static zuint8 z_read(void* context, zuint16 address) { static zuint8 z_read(void* context, zuint16 address) {
auto e = (Emulator<W,H>*)context; 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) { static void z_write(void* context, zuint16 address, zuint8 value) {
auto e = (Emulator<W,H>*)context; 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) { switch (port & 0xFF) {
case 0x03: case 0x03:
return 0x01; return 0x01;
// char // Input
case 0x1E: case 0x1E: {
return 0x00; char c = e->input.front();
e->input.pop();
return c;
}
// has_input // Has input
case 0x1F: 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: default:
SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, "IO Read @ 0x%X\n", (port & 0xFF)); 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; auto e = (Emulator<W,H>*)context;
switch (port & 0xFF) { switch (port & 0xFF) {
case 0x00:
case 0x01:
e->memory.config(port & 0xFF);
break;
case 0x02: 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; break;
default: default:
@ -129,21 +470,11 @@ class Emulator {
} }
void update() { 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 draw_screen() {
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() {
SDL_Rect src; SDL_Rect src;
src.w = 16; src.w = 16;
src.h = 16; src.h = 16;
@ -156,7 +487,7 @@ class Emulator {
for (int x = 0; x < W; ++x) { for (int x = 0; x < W; ++x) {
for (int y = 0; y < H; ++y) { 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.x = (c % 16) * 16;
src.y = (c / 16) * 16; src.y = (c / 16) * 16;
@ -176,6 +507,26 @@ class Emulator {
case SDL_QUIT: case SDL_QUIT:
return false; 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: default:
break; break;
} }
@ -198,26 +549,25 @@ class Emulator {
SDL_Window* window = nullptr; SDL_Window* window = nullptr;
SDL_Texture* font = nullptr; SDL_Texture* font = nullptr;
std::array<char, W*H> buffer = {0}; Screen<W, H> screen;
struct {
int x = 0;
int y = 0;
} cursor;
Z80 z; Z80 z;
std::array<uint8_t, 0xFFFF> memory; Memory memory;
Disk disk;
std::queue<char> input;
bool ctrl = false;
}; };
int main() { 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()) { while (e.handle_events()) {
e.prepare(); e.prepare();
e.update(); e.update();
e.draw_buffer(); e.draw_screen();
e.render(); e.render();
} }