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 <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();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user