nearly works
This commit is contained in:
parent
d53c543ff4
commit
da56e9c8cc
53
programs/echo_keyboard.txt
Normal file
53
programs/echo_keyboard.txt
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
%define KBD_STATUS 0x1ffc
|
||||
%define KBD_CODE 0x1ffe
|
||||
%define VCD 0x2000
|
||||
|
||||
%define KBD_FLAG_IS_RELEASE 0x1
|
||||
|
||||
mov rsp, 0x1000
|
||||
mov rbp, rsp
|
||||
|
||||
; setup vcd
|
||||
mov r0, VCD
|
||||
mov [0x700], r0
|
||||
lvcd 0x700
|
||||
|
||||
; setup kbd
|
||||
mov r0, KBD_STATUS
|
||||
mov [0x702], r0
|
||||
mov r0, KBD_CODE
|
||||
mov [0x704], r0
|
||||
mov r0, key_press_int
|
||||
mov [0x706], r0
|
||||
lkbd 0x702
|
||||
|
||||
; init counter
|
||||
mov r0, 0
|
||||
mov [0x600], r0
|
||||
|
||||
main_loop:
|
||||
hlt
|
||||
jmp main_loop
|
||||
|
||||
key_press_int:
|
||||
mov r0, [KBD_STATUS]
|
||||
and r0, r0, KBD_FLAG_IS_RELEASE
|
||||
jnz r0, .leave
|
||||
|
||||
mov r0, [0x600]
|
||||
add r0, r0, VCD
|
||||
mov r1, [KBC_CODE]
|
||||
add r1, r1, 61
|
||||
mov byte [r0], r1
|
||||
|
||||
mov r0, [0x600]
|
||||
add r0, r0, 1
|
||||
mov [0x600], r0
|
||||
|
||||
.leave:
|
||||
reti
|
||||
|
||||
|
||||
; vim: syntax=nasm
|
||||
|
||||
@ -184,6 +184,9 @@ void Builder::store_byte_reg(Reg dst, uint16_t offset, Reg op2)
|
||||
i.imm_without_flag(offset);
|
||||
i.op2_reg(op2);
|
||||
i.build(*this);
|
||||
std::println("store_byte_reg = {:08b}{:08b}",
|
||||
this->m_data[m_ip - 4],
|
||||
this->m_data[m_ip - 3]);
|
||||
}
|
||||
|
||||
void Builder::store_byte_imm(uint16_t dst, Reg op2)
|
||||
@ -327,3 +330,37 @@ void Builder::binary_imm(Reg dst, Reg op1, uint16_t op2, Op op)
|
||||
i.imm(op2);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
void Builder::reti()
|
||||
{
|
||||
auto i = InsBuilder(Op::RetI);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
void Builder::lvcd_reg(Reg op1)
|
||||
{
|
||||
auto i = InsBuilder(Op::LVCD);
|
||||
i.op1_reg(op1);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
void Builder::lvcd_imm(uint16_t op1)
|
||||
{
|
||||
auto i = InsBuilder(Op::LVCD);
|
||||
i.imm(op1);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
void Builder::lkbd_reg(Reg op1)
|
||||
{
|
||||
auto i = InsBuilder(Op::LKBD);
|
||||
i.op1_reg(op1);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
void Builder::lkbd_imm(uint16_t op1)
|
||||
{
|
||||
auto i = InsBuilder(Op::LKBD);
|
||||
i.imm(op1);
|
||||
i.build(*this);
|
||||
}
|
||||
|
||||
@ -53,6 +53,11 @@ public:
|
||||
void add_imm(Reg dst, Reg op1, uint16_t op2);
|
||||
void sub_imm(Reg dst, Reg op1, uint16_t op2);
|
||||
void rsub_imm(Reg dst, Reg op1, uint16_t op2);
|
||||
void reti();
|
||||
void lvcd_reg(Reg op1);
|
||||
void lvcd_imm(uint16_t op1);
|
||||
void lkbd_reg(Reg op1);
|
||||
void lkbd_imm(uint16_t op1);
|
||||
|
||||
uint16_t ip() const
|
||||
{
|
||||
|
||||
@ -60,8 +60,7 @@ constexpr uint64_t char_data(uint8_t ch)
|
||||
|
||||
}
|
||||
|
||||
auto IoDevice::create()
|
||||
-> std::expected<std::unique_ptr<IoDevice>, std::string>
|
||||
auto IoDevice::create() -> std::expected<std::unique_ptr<IoDevice>, std::string>
|
||||
{
|
||||
auto profile = ScreenProfile();
|
||||
auto width = profile.width_raw_px();
|
||||
@ -167,8 +166,7 @@ auto IoDevice::set_char(uint16_t offset, uint8_t value)
|
||||
return {};
|
||||
}
|
||||
|
||||
auto IoDevice::poll_event()
|
||||
-> std::unique_ptr<IoEvent>
|
||||
auto IoDevice::poll_event() -> std::unique_ptr<IoEvent>
|
||||
{
|
||||
std::lock_guard lock(mx);
|
||||
|
||||
@ -176,23 +174,45 @@ auto IoDevice::poll_event()
|
||||
if (SDL_PollEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
return std::make_unique<IoEvent>(IoEvent::Type::Quit, IoEvent::Quit{});
|
||||
return std::make_unique<IoEvent>(
|
||||
IoEvent::Type::Quit, IoEvent::Quit {});
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_ESCAPE) {
|
||||
return std::make_unique<IoEvent>(IoEvent::Type::Quit, IoEvent::Quit{});
|
||||
return std::make_unique<IoEvent>(
|
||||
IoEvent::Type::Quit, IoEvent::Quit {});
|
||||
}
|
||||
return std::make_unique<IoEvent>(
|
||||
IoEvent::Type::KeyPress,
|
||||
IoEvent::KeyPress { static_cast<uint16_t>(event.key.keysym.scancode) });
|
||||
return std::make_unique<IoEvent>(IoEvent::Type::KeyPress,
|
||||
IoEvent::KeyEvent {
|
||||
.key = static_cast<uint16_t>(event.key.keysym.scancode),
|
||||
.shift_pressed
|
||||
= (event.key.keysym.mod & KMOD_SHIFT) != 0,
|
||||
.ctrl_pressed = (event.key.keysym.mod & KMOD_CTRL) != 0,
|
||||
.alt_pressed = (event.key.keysym.mod & KMOD_LALT) != 0,
|
||||
.altgr_pressed
|
||||
= (event.key.keysym.mod & KMOD_RALT) != 0,
|
||||
|
||||
});
|
||||
case SDL_KEYUP:
|
||||
return std::make_unique<IoEvent>(
|
||||
IoEvent::Type::KeyRelease,
|
||||
IoEvent::KeyRelease { static_cast<uint16_t>(event.key.keysym.scancode) });
|
||||
return std::make_unique<IoEvent>(IoEvent::Type::KeyRelease,
|
||||
IoEvent::KeyEvent {
|
||||
.key = static_cast<uint16_t>(event.key.keysym.scancode),
|
||||
.shift_pressed
|
||||
= (event.key.keysym.mod & KMOD_SHIFT) != 0,
|
||||
.ctrl_pressed = (event.key.keysym.mod & KMOD_CTRL) != 0,
|
||||
.alt_pressed = (event.key.keysym.mod & KMOD_LALT) != 0,
|
||||
.altgr_pressed
|
||||
= (event.key.keysym.mod & KMOD_RALT) != 0,
|
||||
});
|
||||
// default:
|
||||
// std::println(stderr, "warning: unhandled event '{}'", event.type);
|
||||
// std::println(stderr, "warning: unhandled event '{}'",
|
||||
// event.type);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void IoDevice::set_title(const std::string& title)
|
||||
{
|
||||
SDL_SetWindowTitle(m_window, title.c_str());
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ namespace vc5 {
|
||||
|
||||
namespace events {
|
||||
|
||||
|
||||
}
|
||||
|
||||
class IoEvent {
|
||||
@ -26,23 +25,36 @@ public:
|
||||
KeyRelease,
|
||||
};
|
||||
|
||||
struct Quit{};
|
||||
struct Quit { };
|
||||
|
||||
struct KeyPress{ uint16_t key; };
|
||||
struct KeyEvent {
|
||||
uint16_t key;
|
||||
bool shift_pressed;
|
||||
bool ctrl_pressed;
|
||||
bool alt_pressed;
|
||||
bool altgr_pressed;
|
||||
};
|
||||
|
||||
struct KeyRelease{ uint16_t key; };
|
||||
|
||||
using Data = std::variant<Quit, KeyPress, KeyRelease>;
|
||||
using Data = std::variant<Quit, KeyEvent>;
|
||||
|
||||
explicit IoEvent(Type type, Data data)
|
||||
: m_type(type)
|
||||
, m_data(std::move(data))
|
||||
{}
|
||||
{
|
||||
}
|
||||
|
||||
Type type() const { return m_type; }
|
||||
auto as_quit() -> Quit& { return std::get<Quit>(m_data); }
|
||||
auto as_key_press() -> KeyPress& { return std::get<KeyPress>(m_data); }
|
||||
auto as_key_release() -> KeyRelease& { return std::get<KeyRelease>(m_data); }
|
||||
Type type() const
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
auto as_quit() -> Quit&
|
||||
{
|
||||
return std::get<Quit>(m_data);
|
||||
}
|
||||
auto as_key_event() -> KeyEvent&
|
||||
{
|
||||
return std::get<KeyEvent>(m_data);
|
||||
}
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
@ -55,14 +67,32 @@ struct ScreenProfile {
|
||||
int m_width_ch = 40;
|
||||
int m_height_ch = 24;
|
||||
|
||||
constexpr int px_size_raw_px() const { return m_px_size_raw_px; }
|
||||
constexpr int ch_size_px() const { return m_ch_size_px; }
|
||||
constexpr int px_size_raw_px() const
|
||||
{
|
||||
return m_px_size_raw_px;
|
||||
}
|
||||
constexpr int ch_size_px() const
|
||||
{
|
||||
return m_ch_size_px;
|
||||
}
|
||||
|
||||
constexpr int width_ch() const { return m_width_ch; }
|
||||
constexpr int height_ch() const { return m_height_ch; }
|
||||
constexpr int width_ch() const
|
||||
{
|
||||
return m_width_ch;
|
||||
}
|
||||
constexpr int height_ch() const
|
||||
{
|
||||
return m_height_ch;
|
||||
}
|
||||
|
||||
constexpr int width_raw_px() const { return px_size_raw_px() * ch_size_px() * width_ch(); }
|
||||
constexpr int height_raw_px() const { return px_size_raw_px() * ch_size_px() * height_ch(); }
|
||||
constexpr int width_raw_px() const
|
||||
{
|
||||
return px_size_raw_px() * ch_size_px() * width_ch();
|
||||
}
|
||||
constexpr int height_raw_px() const
|
||||
{
|
||||
return px_size_raw_px() * ch_size_px() * height_ch();
|
||||
}
|
||||
};
|
||||
|
||||
class IoDevice {
|
||||
@ -87,6 +117,8 @@ public:
|
||||
|
||||
auto poll_event() -> std::unique_ptr<IoEvent>;
|
||||
|
||||
void set_title(const std::string& title);
|
||||
|
||||
private:
|
||||
std::mutex mx;
|
||||
|
||||
|
||||
92
src/main.cpp
92
src/main.cpp
@ -1,16 +1,16 @@
|
||||
#include "builder.hpp"
|
||||
#include "io_device.hpp"
|
||||
#include "vm.hpp"
|
||||
#include <SDL2/SDL_main.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <print>
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <print>
|
||||
#include <string>
|
||||
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@ -21,13 +21,6 @@ int main()
|
||||
{
|
||||
auto device = IoDevice::create().value();
|
||||
|
||||
auto message = std::string("A-0123456789");
|
||||
uint16_t offset = 0;
|
||||
for (auto ch : message) {
|
||||
device->set_char(offset, static_cast<uint8_t>(ch)).value();
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
constexpr R r0 = R::R0;
|
||||
constexpr R r1 = R::R1;
|
||||
constexpr R r2 = R::R2;
|
||||
@ -39,32 +32,67 @@ int main()
|
||||
constexpr R rfl = R::Rfl;
|
||||
constexpr R rip = R::Rip;
|
||||
|
||||
auto program = std::array<uint8_t, 65535>();
|
||||
auto program = std::array<uint8_t, 65536>();
|
||||
|
||||
auto l = Builder(program.data());
|
||||
|
||||
l.mov_imm(r0, 4);
|
||||
l.mov_imm(rsp, 0x1000);
|
||||
l.mov_reg(rbp, rsp);
|
||||
|
||||
auto l0 = l.ip();
|
||||
l.cmp_imm(r0, 0);
|
||||
l.mov_reg(r1, rfl);
|
||||
l.and_imm(r1, r1, 1 << std::to_underlying(Flag::Eq));
|
||||
l.mov_imm(r0, 0x2000);
|
||||
l.store_word_imm(0x700, r0);
|
||||
l.lvcd_imm(0x700);
|
||||
|
||||
auto i0 = l.ip();
|
||||
l.jnz_imm(r1, 0);
|
||||
l.mov_imm(r0, 0x1ffc);
|
||||
l.store_word_imm(0x702, r0);
|
||||
l.mov_imm(r0, 0x1ffe);
|
||||
l.store_word_imm(0x704, r0);
|
||||
auto to_set_key_press_int = l.ip();
|
||||
l.mov_imm(r0, 0);
|
||||
l.store_word_imm(0x706, r0);
|
||||
l.lkbd_imm(0x702);
|
||||
|
||||
l.sub_imm(r0, r0, 1);
|
||||
l.jmp_imm(l0);
|
||||
l.mov_imm(r0, 0);
|
||||
l.store_word_imm(0x600, r0);
|
||||
|
||||
auto l1 = l.ip();
|
||||
auto main_loop = l.ip();
|
||||
l.hlt();
|
||||
l.jmp_imm(main_loop);
|
||||
|
||||
l.set_ip(i0 + 2);
|
||||
l.push(l1);
|
||||
auto key_press_int = l.ip();
|
||||
l.load_word_imm(r0, 0x1ffc);
|
||||
l.and_imm(r0, r0, 1);
|
||||
auto to_set_key_press_int_leave = l.ip();
|
||||
l.jnz_imm(r0, 0);
|
||||
|
||||
auto vm = VM();
|
||||
l.load_word_imm(r0, 0x600);
|
||||
l.add_imm(r0, r0, 0x2000);
|
||||
// l.load_word_imm(r1, 0x1ffe);
|
||||
// l.add_imm(r1, r1, 61);
|
||||
l.mov_imm(r1, 'A');
|
||||
l.nop();
|
||||
l.nop();
|
||||
l.store_byte_reg(r0, 0, r1);
|
||||
l.nop();
|
||||
l.nop();
|
||||
|
||||
l.load_word_imm(r0, 0x600);
|
||||
l.add_imm(r0, r0, 1);
|
||||
l.store_word_imm(0x600, r0);
|
||||
|
||||
auto key_press_int_leave = l.ip();
|
||||
l.reti();
|
||||
|
||||
l.set_ip(to_set_key_press_int + 2);
|
||||
l.push(key_press_int);
|
||||
l.set_ip(to_set_key_press_int_leave + 2);
|
||||
l.push(key_press_int_leave);
|
||||
|
||||
auto vm = VM(*device);
|
||||
vm.load(0, program.data(), program.size());
|
||||
|
||||
device->set_title("vc5");
|
||||
|
||||
vm.run();
|
||||
|
||||
std::println("--- regs ---");
|
||||
@ -82,19 +110,9 @@ int main()
|
||||
for (uint16_t i = 0; i < 32; i += 2) {
|
||||
std::println("{: 4x}\t{}", i, vm.word(i));
|
||||
}
|
||||
|
||||
|
||||
while (true) {
|
||||
auto event = device->poll_event();
|
||||
|
||||
if (event && event->type() == IoEvent::Type::Quit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" const char* __asan_default_options(void)
|
||||
{
|
||||
return "detect_leaks=0";
|
||||
}
|
||||
|
||||
|
||||
348
src/vm.cpp
348
src/vm.cpp
@ -1,53 +1,178 @@
|
||||
#include "vm.hpp"
|
||||
#include <cstdint>
|
||||
#include <print>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
using namespace vc5;
|
||||
|
||||
void VM::load(uint16_t offset, const uint8_t* data, size_t data_size)
|
||||
{
|
||||
std::memcpy(&m_mem[offset], data, data_size);
|
||||
}
|
||||
|
||||
int VM::run()
|
||||
{
|
||||
m_on = true;
|
||||
while (m_on) {
|
||||
if (not m_halted) {
|
||||
int result = run_instruction();
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
|
||||
poll_events();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t VM::reg(Reg reg) const
|
||||
{
|
||||
return m_regs[std::to_underlying(reg)];
|
||||
}
|
||||
|
||||
uint16_t VM::word(uint16_t addr) const
|
||||
{
|
||||
if ((addr & 0b1) != 0) {
|
||||
throw std::invalid_argument(
|
||||
std::format("invalid address alignment, addr = {}", addr));
|
||||
}
|
||||
|
||||
uint16_t value = 0;
|
||||
value |= static_cast<uint16_t>(m_mem[addr]) << 8;
|
||||
value |= m_mem[addr + 1];
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t VM::byte(uint16_t addr) const
|
||||
{
|
||||
return m_mem[addr];
|
||||
}
|
||||
|
||||
void VM::set_reg(Reg reg, uint16_t value)
|
||||
{
|
||||
m_regs[std::to_underlying(reg)] = value;
|
||||
}
|
||||
|
||||
void VM::set_word(uint16_t addr, uint16_t value)
|
||||
{
|
||||
if ((addr & 0b1) != 0) {
|
||||
throw std::invalid_argument(
|
||||
std::format("invalid address alignment, addr = {}", addr));
|
||||
}
|
||||
|
||||
m_mem[addr] = value >> 8;
|
||||
m_mem[addr + 1] = value & 0xff;
|
||||
}
|
||||
|
||||
void VM::set_byte(uint16_t addr, uint8_t value)
|
||||
{
|
||||
m_mem[addr] = value;
|
||||
}
|
||||
|
||||
uint16_t VM::eat_word()
|
||||
{
|
||||
auto ins = word(*m_rip);
|
||||
*m_rip += 2;
|
||||
return ins;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
uint16_t mem_addr(Ins& ins, uint16_t imm)
|
||||
{
|
||||
bool is_imm = ins >> 6 & 1;
|
||||
struct InsReader {
|
||||
InsReader(VM& vm)
|
||||
: vm(&vm)
|
||||
, ins(vm.eat_word())
|
||||
{
|
||||
}
|
||||
|
||||
VM* vm;
|
||||
uint16_t ins;
|
||||
|
||||
auto to_u16() const -> uint16_t
|
||||
{
|
||||
return ins;
|
||||
}
|
||||
|
||||
auto dst_reg() const -> Reg
|
||||
{
|
||||
return static_cast<Reg>(ins >> 13 & 0b111);
|
||||
}
|
||||
|
||||
auto op1_reg() const -> Reg
|
||||
{
|
||||
return static_cast<Reg>(ins >> 10 & 0b111);
|
||||
}
|
||||
|
||||
auto op2_reg() const -> Reg
|
||||
{
|
||||
return static_cast<Reg>(ins >> 7 & 0b111);
|
||||
}
|
||||
|
||||
auto op1() const -> uint16_t
|
||||
{
|
||||
return vm->reg(op1_reg());
|
||||
}
|
||||
|
||||
auto op2() const -> uint16_t
|
||||
{
|
||||
|
||||
return vm->reg(op2_reg());
|
||||
}
|
||||
|
||||
auto op1_or_imm() -> uint16_t
|
||||
{
|
||||
return reg_val_or_imm(6, 10, 0b111);
|
||||
}
|
||||
|
||||
auto op2_or_imm() -> uint16_t
|
||||
{
|
||||
return reg_val_or_imm(6, 7, 0b111);
|
||||
}
|
||||
|
||||
auto reg_val_or_imm(
|
||||
uint16_t is_imm_bit, uint16_t reg_bit, uint16_t reg_mask) -> uint16_t
|
||||
{
|
||||
bool is_imm = (ins >> is_imm_bit & 1) != 0;
|
||||
if (is_imm) {
|
||||
return vm->eat_word();
|
||||
} else {
|
||||
return vm->reg(static_cast<Reg>(ins >> reg_bit & reg_mask));
|
||||
}
|
||||
}
|
||||
|
||||
auto mem_addr(uint16_t imm) const -> uint16_t
|
||||
{
|
||||
bool is_imm = to_u16() >> 6 & 1;
|
||||
if (is_imm) {
|
||||
return imm;
|
||||
}
|
||||
|
||||
auto base = ins.op2();
|
||||
auto base = op2();
|
||||
auto offset = static_cast<int16_t>(imm);
|
||||
if (offset >= 0) {
|
||||
return base + static_cast<uint16_t>(offset);
|
||||
} else {
|
||||
return base - static_cast<uint16_t>(-offset);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int VM::run()
|
||||
{
|
||||
m_halted = false;
|
||||
while (not m_halted) {
|
||||
int result = run_instruction();
|
||||
if (result != 0)
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int VM::run_instruction()
|
||||
{
|
||||
auto ip = *m_rip;
|
||||
if (ip > 64)
|
||||
if (ip > 0x400)
|
||||
return -1;
|
||||
|
||||
auto ins = Ins(*this);
|
||||
auto op = static_cast<Op>(ins & 0b11'1111);
|
||||
std::println("[{: 4x}]: {:04x} {}, r0 = {}",
|
||||
auto ins = InsReader(*this);
|
||||
auto op = static_cast<Op>(ins.to_u16() & 0b11'1111);
|
||||
std::println(" [{: 4x}]: {:04x} {}, r0 = {}",
|
||||
ip,
|
||||
ins.operator uint16_t(),
|
||||
ins.to_u16(),
|
||||
op_str(op),
|
||||
m_regs[reg_value(Reg::R0)]);
|
||||
m_regs[std::to_underlying(Reg::R0)]);
|
||||
|
||||
switch (op) {
|
||||
case Op::Nop:
|
||||
@ -70,55 +195,64 @@ int VM::run_instruction()
|
||||
break;
|
||||
}
|
||||
case Op::Mov: {
|
||||
auto dst = static_cast<uint16_t>(ins >> 12 & 0b1111);
|
||||
auto dst = static_cast<Reg>(ins.to_u16() >> 12 & 0b1111);
|
||||
auto src = ins.reg_val_or_imm(6, 7, 0b1111);
|
||||
m_regs[dst] = src;
|
||||
set_reg(dst, src);
|
||||
break;
|
||||
}
|
||||
case Op::LoadWord: {
|
||||
auto addr = mem_addr(ins, eat());
|
||||
auto addr = ins.mem_addr(eat_word());
|
||||
|
||||
if ((addr & 0b1) != 0) {
|
||||
std::println(stderr, "error: invalid address alignment");
|
||||
*m_rfl |= 1 << flag_value(Flag::Err);
|
||||
std::println(stderr,
|
||||
"error: invalid address alignment, addr = {}",
|
||||
addr);
|
||||
*m_rfl |= 1 << std::to_underlying(Flag::Err);
|
||||
m_on = false;
|
||||
goto halt_execution;
|
||||
}
|
||||
|
||||
auto reg = ins.dst_reg();
|
||||
|
||||
uint16_t value = 0;
|
||||
value |= static_cast<uint16_t>(m_mem[addr]) << 16;
|
||||
value |= m_mem[addr + 1];
|
||||
|
||||
m_regs[reg] = value;
|
||||
set_reg(reg, word(addr));
|
||||
break;
|
||||
}
|
||||
case Op::StoreWord: {
|
||||
auto addr = mem_addr(ins, eat());
|
||||
auto addr = ins.mem_addr(eat_word());
|
||||
|
||||
if ((addr & 0b1) != 0) {
|
||||
std::println(stderr, "error: invalid address alignment");
|
||||
*m_rfl |= 1 << flag_value(Flag::Err);
|
||||
std::println(stderr,
|
||||
"error: invalid address alignment, addr = {}",
|
||||
addr);
|
||||
*m_rfl |= 1 << std::to_underlying(Flag::Err);
|
||||
m_on = false;
|
||||
goto halt_execution;
|
||||
}
|
||||
|
||||
auto src = ins.op2();
|
||||
m_mem[addr] = src >> 16;
|
||||
m_mem[addr + 1] = src & 0xff;
|
||||
|
||||
if (m_vcd_loaded) {
|
||||
vcd_maybe_send_byte(addr, src >> 8);
|
||||
vcd_maybe_send_byte(addr + 1, src & 0xff);
|
||||
}
|
||||
|
||||
set_word(addr, src);
|
||||
|
||||
break;
|
||||
}
|
||||
case Op::LoadByte: {
|
||||
auto addr = mem_addr(ins, eat());
|
||||
auto addr = ins.mem_addr(eat_word());
|
||||
auto reg = ins.dst_reg();
|
||||
|
||||
m_regs[reg] = m_mem[addr];
|
||||
set_reg(reg, byte(addr));
|
||||
break;
|
||||
}
|
||||
case Op::StoreByte: {
|
||||
auto addr = mem_addr(ins, eat());
|
||||
auto addr = ins.mem_addr(eat_word());
|
||||
auto src = ins.op2();
|
||||
|
||||
if (m_vcd_loaded) {
|
||||
vcd_maybe_send_byte(addr, src & 0xff);
|
||||
}
|
||||
|
||||
m_mem[addr] = src & 0xff;
|
||||
break;
|
||||
}
|
||||
@ -127,19 +261,19 @@ int VM::run_instruction()
|
||||
auto op2 = ins.op2_or_imm();
|
||||
|
||||
if (op1 == op2) {
|
||||
*m_rfl |= 1u << flag_value(Flag::Eq);
|
||||
*m_rfl |= 1u << std::to_underlying(Flag::Eq);
|
||||
} else {
|
||||
*m_rfl &= (uint16_t)~(1u << flag_value(Flag::Eq));
|
||||
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Eq));
|
||||
}
|
||||
if (op1 < op2) {
|
||||
*m_rfl |= 1u << flag_value(Flag::Be);
|
||||
*m_rfl |= 1u << std::to_underlying(Flag::Be);
|
||||
} else {
|
||||
*m_rfl &= (uint16_t)~(1u << flag_value(Flag::Be));
|
||||
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Be));
|
||||
}
|
||||
if ((int16_t)op1 < (int16_t)op2) {
|
||||
*m_rfl |= 1u << flag_value(Flag::Lt);
|
||||
*m_rfl |= 1u << std::to_underlying(Flag::Lt);
|
||||
} else {
|
||||
*m_rfl &= (uint16_t)~(1u << flag_value(Flag::Lt));
|
||||
*m_rfl &= (uint16_t)~(1u << std::to_underlying(Flag::Lt));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -157,7 +291,7 @@ int VM::run_instruction()
|
||||
auto op2 = ins.op2_or_imm();
|
||||
auto dst_reg = ins.dst_reg();
|
||||
|
||||
uint16_t* dst = &m_regs[dst_reg];
|
||||
uint16_t* dst = &m_regs[std::to_underlying(dst_reg)];
|
||||
|
||||
switch (op) {
|
||||
case Op::Or:
|
||||
@ -196,19 +330,116 @@ int VM::run_instruction()
|
||||
|
||||
break;
|
||||
}
|
||||
case Op::RetI: {
|
||||
*m_rsp -= 2;
|
||||
auto return_addr = word(*m_rsp);
|
||||
|
||||
*m_rip = return_addr;
|
||||
|
||||
m_interrupts_enabled = true;
|
||||
break;
|
||||
}
|
||||
case Op::LVCD: {
|
||||
auto op1 = ins.op1_or_imm();
|
||||
m_vcd_descriptor = VcdDescript {
|
||||
.map_base_addr = word(op1),
|
||||
};
|
||||
m_vcd_loaded = true;
|
||||
|
||||
break;
|
||||
}
|
||||
case Op::LKBD: {
|
||||
auto op1 = ins.op1_or_imm();
|
||||
m_kbd_descriptor = KbdDescript {
|
||||
.status_addr = word(op1),
|
||||
.keycode_addr = word(op1 + 2),
|
||||
.int_handler = word(op1 + 4),
|
||||
};
|
||||
m_kbd_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
halt_execution:
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t VM::eat()
|
||||
void VM::poll_events()
|
||||
{
|
||||
uint16_t ins = 0;
|
||||
ins |= static_cast<uint16_t>(m_mem[*m_rip]) << 8;
|
||||
ins |= m_mem[*m_rip + 1];
|
||||
*m_rip += 2;
|
||||
return ins;
|
||||
while (true) {
|
||||
auto event = m_device->poll_event();
|
||||
|
||||
if (not event)
|
||||
break;
|
||||
|
||||
if (event->type() == IoEvent::Type::Quit) {
|
||||
m_on = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (event->type() == IoEvent::Type::KeyPress
|
||||
or event->type() == IoEvent::Type::KeyRelease) {
|
||||
if (m_kbd_loaded) {
|
||||
auto& d = m_kbd_descriptor;
|
||||
|
||||
uint16_t flags = 0;
|
||||
if (event->type() == IoEvent::Type::KeyRelease) {
|
||||
flags |= 1 << std::to_underlying(KbdStatusFlag::Type);
|
||||
}
|
||||
if (event->as_key_event().shift_pressed) {
|
||||
flags |= 1 << std::to_underlying(KbdStatusFlag::Shift);
|
||||
}
|
||||
if (event->as_key_event().ctrl_pressed) {
|
||||
flags |= 1 << std::to_underlying(KbdStatusFlag::Ctrl);
|
||||
}
|
||||
if (event->as_key_event().alt_pressed) {
|
||||
flags |= 1 << std::to_underlying(KbdStatusFlag::Alt);
|
||||
}
|
||||
if (event->as_key_event().altgr_pressed) {
|
||||
flags |= 1 << std::to_underlying(KbdStatusFlag::AltGr);
|
||||
}
|
||||
|
||||
set_word(d.status_addr, flags);
|
||||
set_word(d.keycode_addr, event->as_key_event().key);
|
||||
interrupt(d.int_handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VM::vcd_maybe_send_byte(uint16_t addr, uint8_t value)
|
||||
{
|
||||
if (not m_vcd_loaded)
|
||||
return;
|
||||
|
||||
constexpr uint16_t screen_size = 40 * 24;
|
||||
|
||||
const auto& d = m_vcd_descriptor;
|
||||
|
||||
std::println(" addr = {}", addr);
|
||||
std::println(" printing = {}",
|
||||
addr >= d.map_base_addr and addr < d.map_base_addr + screen_size);
|
||||
|
||||
if (addr >= d.map_base_addr and addr < d.map_base_addr + screen_size) {
|
||||
|
||||
std::println(
|
||||
"printing {} or '{}' at {}", value, static_cast<char>(value), addr);
|
||||
|
||||
m_device->set_char(addr - d.map_base_addr, value).value();
|
||||
}
|
||||
}
|
||||
|
||||
void VM::interrupt(uint16_t handler_addr)
|
||||
{
|
||||
if (not m_interrupts_enabled)
|
||||
return;
|
||||
|
||||
set_word(*m_rsp, *m_rip);
|
||||
*m_rsp += 2;
|
||||
|
||||
*m_rip = handler_addr;
|
||||
|
||||
m_halted = false;
|
||||
m_interrupts_enabled = false;
|
||||
}
|
||||
|
||||
const char* vc5::op_str(Op op)
|
||||
@ -254,7 +485,12 @@ const char* vc5::op_str(Op op)
|
||||
return "Sub";
|
||||
case Op::RSub:
|
||||
return "RSub";
|
||||
case Op::RetI:
|
||||
return "RetI";
|
||||
case Op::LVCD:
|
||||
return "LVCD";
|
||||
case Op::LKBD:
|
||||
return "LKBD";
|
||||
}
|
||||
return ">.<";
|
||||
}
|
||||
|
||||
|
||||
146
src/vm.hpp
146
src/vm.hpp
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "io_device.hpp"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@ -30,6 +31,13 @@ enum class Op : uint16_t {
|
||||
Add,
|
||||
Sub,
|
||||
RSub,
|
||||
|
||||
// return from interrupt
|
||||
RetI,
|
||||
// load video character display
|
||||
LVCD,
|
||||
// load keyboard
|
||||
LKBD,
|
||||
};
|
||||
|
||||
const char* op_str(Op op);
|
||||
@ -47,11 +55,6 @@ enum class Reg : uint16_t {
|
||||
Rip = 9,
|
||||
};
|
||||
|
||||
constexpr uint16_t reg_value(Reg reg)
|
||||
{
|
||||
return std::to_underlying(reg);
|
||||
}
|
||||
|
||||
enum class Flag : uint16_t {
|
||||
Zero = 0,
|
||||
Eq = 1,
|
||||
@ -60,114 +63,71 @@ enum class Flag : uint16_t {
|
||||
Err = 4,
|
||||
};
|
||||
|
||||
constexpr uint16_t flag_value(Flag flag)
|
||||
{
|
||||
return std::to_underlying(flag);
|
||||
}
|
||||
struct VcdDescript {
|
||||
uint16_t map_base_addr;
|
||||
};
|
||||
|
||||
struct KbdDescript {
|
||||
uint16_t status_addr;
|
||||
uint16_t keycode_addr;
|
||||
uint16_t int_handler;
|
||||
};
|
||||
|
||||
enum class KbdStatusFlag : uint16_t {
|
||||
/// 0 Press
|
||||
/// 1 Release
|
||||
Type = 0,
|
||||
Shift = 1,
|
||||
Ctrl = 2,
|
||||
Alt = 3,
|
||||
AltGr = 4,
|
||||
};
|
||||
|
||||
class VM {
|
||||
public:
|
||||
void load(uint16_t offset, const uint8_t* data, size_t data_size)
|
||||
VM(IoDevice& device)
|
||||
: m_device(&device)
|
||||
{
|
||||
std::memcpy(&m_mem[offset], data, data_size);
|
||||
}
|
||||
|
||||
void load(uint16_t offset, const uint8_t* data, size_t data_size);
|
||||
int run();
|
||||
|
||||
uint16_t reg(Reg reg) const
|
||||
{
|
||||
return m_regs[reg_value(reg)];
|
||||
};
|
||||
auto reg(Reg reg) const -> uint16_t;
|
||||
auto word(uint16_t addr) const -> uint16_t;
|
||||
auto byte(uint16_t addr) const -> uint8_t;
|
||||
|
||||
uint16_t word(uint16_t addr) const
|
||||
{
|
||||
uint16_t value = 0;
|
||||
value |= static_cast<uint16_t>(m_mem[addr]) << 8;
|
||||
value |= m_mem[addr + 1];
|
||||
return value;
|
||||
}
|
||||
void set_reg(Reg reg, uint16_t value);
|
||||
void set_word(uint16_t addr, uint16_t value);
|
||||
void set_byte(uint16_t addr, uint8_t value);
|
||||
|
||||
uint8_t byte(uint16_t addr) const
|
||||
{
|
||||
return m_mem[addr];
|
||||
}
|
||||
uint16_t eat_word();
|
||||
|
||||
private:
|
||||
friend struct Ins;
|
||||
|
||||
int run_instruction();
|
||||
uint16_t eat();
|
||||
void poll_events();
|
||||
|
||||
void vcd_maybe_send_byte(uint16_t addr, uint8_t value);
|
||||
|
||||
void interrupt(uint16_t handler_addr);
|
||||
|
||||
std::array<uint8_t, 65536> m_mem = {};
|
||||
std::array<uint16_t, 10> m_regs = {};
|
||||
|
||||
uint16_t* m_rfl = &m_regs[reg_value(Reg::Rfl)];
|
||||
uint16_t* m_rip = &m_regs[reg_value(Reg::Rip)];
|
||||
uint16_t* m_rsp = &m_regs[std::to_underlying(Reg::Rsp)];
|
||||
uint16_t* m_rfl = &m_regs[std::to_underlying(Reg::Rfl)];
|
||||
uint16_t* m_rip = &m_regs[std::to_underlying(Reg::Rip)];
|
||||
|
||||
IoDevice* m_device;
|
||||
|
||||
bool m_on = true;
|
||||
bool m_halted = false;
|
||||
};
|
||||
bool m_interrupts_enabled = true;
|
||||
bool m_vcd_loaded = false;
|
||||
bool m_kbd_loaded = false;
|
||||
|
||||
struct Ins {
|
||||
Ins(VM& vm)
|
||||
: vm(&vm)
|
||||
, ins(vm.eat())
|
||||
{
|
||||
}
|
||||
|
||||
VM* vm;
|
||||
uint16_t ins;
|
||||
|
||||
operator uint16_t()
|
||||
{
|
||||
return ins;
|
||||
}
|
||||
|
||||
inline uint16_t dst_reg() const
|
||||
{
|
||||
return ins >> 13 & 0b111;
|
||||
}
|
||||
|
||||
inline uint16_t op1_reg() const
|
||||
{
|
||||
return ins >> 10 & 0b111;
|
||||
}
|
||||
|
||||
inline uint16_t op2_reg() const
|
||||
{
|
||||
return ins >> 7 & 0b111;
|
||||
}
|
||||
|
||||
inline uint16_t op1() const
|
||||
{
|
||||
return vm->m_regs[op1_reg()];
|
||||
}
|
||||
|
||||
inline uint16_t op2() const
|
||||
{
|
||||
|
||||
return vm->m_regs[op2_reg()];
|
||||
}
|
||||
|
||||
inline uint16_t op1_or_imm()
|
||||
{
|
||||
return reg_val_or_imm(6, 10, 0b111);
|
||||
}
|
||||
|
||||
inline uint16_t op2_or_imm()
|
||||
{
|
||||
return reg_val_or_imm(6, 7, 0b111);
|
||||
}
|
||||
|
||||
inline uint16_t reg_val_or_imm(
|
||||
uint16_t is_imm_bit, uint16_t reg_bit, uint16_t reg_mask)
|
||||
{
|
||||
bool is_imm = (ins >> is_imm_bit & 1) != 0;
|
||||
if (is_imm) {
|
||||
return vm->eat();
|
||||
} else {
|
||||
return vm->m_regs[ins >> reg_bit & reg_mask];
|
||||
}
|
||||
}
|
||||
VcdDescript m_vcd_descriptor {};
|
||||
KbdDescript m_kbd_descriptor {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user