make program work sorta

This commit is contained in:
sfja 2026-01-25 02:57:05 +01:00
parent 7878663268
commit eab5a313ab
9 changed files with 404 additions and 109 deletions

View File

@ -1,13 +1,20 @@
const KBD_STATUS 0x1ffc
const KBD_CODE 0x1ffe
const VCD 0x2000
const fl_zero 0x1
const fl_eq 0x2
const fl_be 0x4
const fl_lt 0x8
const fl_err 0xa
const FL_EQ 0x2
const vcd_base 0x2000
const KBD_FLAG_IS_RELEASE 0x1
const kbd_status 0x1ffc
const kbd_code 0x1ffe
const kbd_flag_is_release 0x1
const counter 0x600
_start:
mov rsp, 0x1000
mov rbp, rsp
@ -18,7 +25,7 @@ const KBD_FLAG_IS_RELEASE 0x1
; setup video character display (VCD)
; descriptor table structure:
; [addr + 0] memory map base
mov r0, VCD
mov r0, vcd_base
mov [0x700], r0
lvcd 0x700
@ -27,9 +34,9 @@ const KBD_FLAG_IS_RELEASE 0x1
; [addr + 0] status address
; [addr + 2] keycode address
; [addr + 4] keyboard interrupt handler
mov r0, KBD_STATUS
mov r0, kbd_status
mov [0x702], r0
mov r0, KBD_CODE
mov r0, kbd_code
mov [0x704], r0
mov r0, key_press_int
mov [0x706], r0
@ -37,33 +44,91 @@ const KBD_FLAG_IS_RELEASE 0x1
; character counter
mov r0, 0
mov [0x600], r0
mov [counter], r0
main_loop:
hlt
jmp main_loop
key_press_int:
mov r0, [KBD_STATUS]
and r0, r0, KBD_FLAG_IS_RELEASE
mov r0, [kbd_status]
and r0, kbd_flag_is_release
jnz r0, .leave
mov r0, [0x600]
add r0, r0, VCD
mov r1, [KBD_CODE]
mov r0, [counter]
add r0, vcd_base
mov r1, [kbd_code]
cmp r1, 44 ; spacebar
mov r2, rfl
and r2, r2, FL_EQ
jnz r2, .incr
add r1, r1, 61
mov byte [r0], r1
.incr:
mov r0, [0x600]
add r0, r0, 1
mov [0x600], r0
and r2, fl_eq
; jnz r2, .incr
; add r1, 61
; mov byte [r0], r1
;.incr:
; mov r0, [counter]
; add r0, 1
; mov [counter], r0
jnz r2, .print_space
add r1, 61 ; scancode letter -> ascii
mov [rsp], r0
add rsp, 2
call print_char
jmp .leave
.print_space:
mov r0, ' '
mov [rsp], r0
add rsp, 2
call print_char
.leave:
reti
print_char:
mov [rsp], rbp
mov rbp, rsp
add rsp, 2
mov r1, [rbp-6]
add r0, vcd_base
mov r1, [kbd_code]
cmp r1, ' '
mov r2, rfl
and r2, fl_eq
jnz r2, .incr
add r1, 61
mov byte [r0], r1
.incr:
mov r0, [counter]
add r0, 1
mov [counter], r0
.leave:
sub rsp, 2
mov rbp, [rsp]
ret
;print_int:
; mov [rsp], rbp
; add rsp, 2
; mov rbp, rsp
; add rsp, rsp, 2
;
; mov r1, [rbp-2]
;
; mov r2, 10000
; mov r3, 0
; jmp .c_10000
;.b_10000:
; add r3, 1
; sub r1, r2
;.c_10000:
; cmp r2, r1
; mov r0, rfl
; and r0, fl_eq | fl_lt
; jnz r0, .b_10000
; add r3, 48
;scancode_to_char:
; cmp r1, 61
;

View File

@ -101,7 +101,11 @@ auto Assembler::assemble_file()
}
assert(value->ty == EOT::Imm);
m_syms[std::string(stmt->ident)] = value->as_imm();
if (auto result
= define_sym(std::string(stmt->ident), value->as_imm());
not result) {
error(stmt->loc, result.error());
}
} else if (parser.next_is_align()) {
auto stmt = parser.parse_align();
@ -172,9 +176,20 @@ auto Assembler::assemble_file()
m_second_pass = true;
for (const auto& line : lines) {
if (line->labels) {
for (const auto& label : *line->labels) {
if (not label.is_local) {
m_superlabel = label.ident;
}
}
}
assemble_line(*line);
}
if (m_failed)
return std::unexpected("assembling failed");
return std::move(m_program);
}
@ -182,10 +197,17 @@ void Assembler::define_labels(const std::vector<Label>& labels)
{
for (const auto& label : labels) {
if (label.is_local) {
m_syms[label_ident(label)] = m_builder.ip();
if (auto result = define_sym(label_ident(label), m_builder.ip());
not result) {
error(label.loc, result.error());
}
} else {
m_superlabel = label.ident;
m_syms[std::string(label.ident)] = m_builder.ip();
if (auto result
= define_sym(std::string(label.ident), m_builder.ip());
not result) {
error(label.loc, result.error());
}
}
}
}
@ -205,36 +227,62 @@ auto Assembler::sublabel_ident(std::string_view ident) const -> std::string
namespace {
enum class Mnemonic {
// clang-format off
db, dw, nop,
hlt, jmp, jnz,
mov, cmp,
or_, and_, xor_,
shl, rshl, shr,
rshr, add, sub,
rsub, reti, lvcd,
lkbd, dskr, dskw,
// clang-format on
db,
dw,
nop,
hlt,
jmp,
jnz,
mov,
cmp,
or_,
and_,
xor_,
shl,
rshl,
shr,
rshr,
add,
sub,
rsub,
call,
ret,
reti,
lvcd,
lkbd,
dskr,
dskw,
};
using M = Mnemonic;
static const auto mnemonic_map
= std::unordered_map<std::string_view, Mnemonic> {
// clang-format off
{ "db", M::db }, { "dw", M::dw },
{ "nop", M::nop }, { "hlt", M::hlt },
{ "jmp", M::jmp }, { "jnz", M::jnz },
{ "mov", M::mov }, { "cmp", M::cmp },
{ "or", M::or_ }, { "and", M::and_ },
{ "xor", M::xor_ }, { "shl", M::shl },
{ "rshl", M::rshl }, { "shr", M::shr },
{ "rshr", M::rshr }, { "add", M::add },
{ "sub", M::sub }, { "rsub", M::rsub },
{ "reti", M::reti }, { "lvcd", M::lvcd },
{ "lkbd", M::lkbd }, { "dskr", M::dskr },
{ "db", M::db },
{ "dw", M::dw },
{ "nop", M::nop },
{ "hlt", M::hlt },
{ "jmp", M::jmp },
{ "jnz", M::jnz },
{ "mov", M::mov },
{ "cmp", M::cmp },
{ "or", M::or_ },
{ "and", M::and_ },
{ "xor", M::xor_ },
{ "shl", M::shl },
{ "rshl", M::rshl },
{ "shr", M::shr },
{ "rshr", M::rshr },
{ "add", M::add },
{ "sub", M::sub },
{ "rsub", M::rsub },
{ "call", M::call },
{ "ret", M::ret },
{ "reti", M::reti },
{ "lvcd", M::lvcd },
{ "lkbd", M::lkbd },
{ "dskr", M::dskr },
{ "dskw", M::dskw },
// clang-format on
};
}
@ -412,11 +460,24 @@ void Assembler::assemble_line(const Line& line)
case M::add:
case M::sub:
case M::rsub: {
if (arg_count_wrong(line, 3))
return;
auto dst = eval_operand(*line.args[0]);
auto op1 = eval_operand(*line.args[1]);
auto op2 = eval_operand(*line.args[2]);
auto dst = std::unique_ptr<EvaledOperand> {};
auto op1_store = std::unique_ptr<EvaledOperand> {};
auto op2 = std::unique_ptr<EvaledOperand> {};
EvaledOperand* op1;
if (line.args.size() == 3) {
dst = eval_operand(*line.args[0]);
op1_store = eval_operand(*line.args[1]);
op1 = &*op1_store;
op2 = eval_operand(*line.args[2]);
} else if (line.args.size() == 2) {
dst = eval_operand(*line.args[0]);
op1 = &*dst;
op2 = eval_operand(*line.args[1]);
} else {
operation_not_supported();
}
if (not dst or not op1 or not op2)
return;
@ -501,6 +562,28 @@ void Assembler::assemble_line(const Line& line)
}
break;
}
case Mnemonic::call: {
if (arg_count_wrong(line, 1))
return;
auto op = eval_operand(*line.args[0]);
if (not op)
return;
if (op->is_reg()) {
l.call_reg(op->as_reg());
} else if (op->is_imm()) {
l.call_imm(op->as_imm());
} else {
operation_not_supported();
}
break;
}
case Mnemonic::ret: {
if (arg_count_wrong(line, 0))
return;
l.ret();
break;
}
case M::reti: {
if (arg_count_wrong(line, 0))
return;
@ -715,32 +798,28 @@ auto Assembler::eval_operand_to_imm(const Expr& expr)
auto loc = expr.loc;
switch (expr.ty) {
case Expr::Ty::Ident: {
auto ident = std::string(expr.as_ident());
if (!m_syms.contains(ident)) {
auto result = get_sym(std::string(expr.as_ident()));
if (not result) {
if (not m_second_pass)
return std::make_unique<EO>(loc, EOT::Imm, uint16_t { 0 });
error(expr.loc,
std::format("symbol \"{}\" not defined", expr.as_ident()));
error(expr.loc, result.error());
return nullptr;
}
auto value = m_syms[ident];
return std::make_unique<EO>(loc, EOT::Imm, value);
return std::make_unique<EO>(loc, EOT::Imm, *result);
}
case Expr::Ty::SubLabel: {
auto ident = sublabel_ident(expr.as_ident());
if (!m_syms.contains(ident)) {
auto result = get_sym(sublabel_ident(expr.as_ident()));
if (not result) {
if (not m_second_pass)
return std::make_unique<EO>(loc, EOT::Imm, uint16_t { 0 });
error(expr.loc,
std::format("symbol \"{}\" not defined", expr.as_ident()));
error(expr.loc, result.error());
return nullptr;
}
auto value = m_syms[ident];
return std::make_unique<EO>(loc, EOT::Imm, value);
return std::make_unique<EO>(loc, EOT::Imm, *result);
}
case Expr::Ty::Reg:
error(loc, "registers cannot be part of an expression");
@ -830,6 +909,32 @@ bool Assembler::arg_count_wrong(const Line& ins, size_t count)
return false;
}
auto Assembler::define_sym(std::string ident, uint16_t value)
-> std::expected<void, std::string>
{
std::println("define_sym(\"{}\", {})", ident, value);
if (m_syms.contains(ident)) {
return std::unexpected(
std::format("symbol \"{}\" already defined", ident));
}
m_syms[std::move(ident)] = value;
return {};
}
auto Assembler::get_sym(const std::string& ident)
-> std::expected<uint16_t, std::string>
{
std::println(" get_sym(\"{}\")", ident);
if (not m_syms.contains(ident)) {
return std::unexpected(std::format("symbol \"{}\" not defined", ident));
}
return m_syms[ident];
}
void Assembler::error(Loc loc, std::string_view message)
{
m_failed = true;
@ -1003,15 +1108,19 @@ auto Parser::parse_binary(int prec) -> std::unique_ptr<Expr>
auto left = parse_binary(prec - 1);
auto should_continue = false;
auto should_continue = true;
while (should_continue) {
should_continue = false;
for (auto [op, ty, p] : ops) {
if (prec >= p and eat(op)) {
auto right = parse_binary(prec - 1);
left = std::make_unique<Expr>(loc,
ty,
Expr::Binary { std::move(left), std::move(right) });
should_continue = true;
break;
}
}
}
@ -1022,13 +1131,14 @@ auto Parser::parse_binary(int prec) -> std::unique_ptr<Expr>
auto Parser::parse_prefix() -> std::unique_ptr<Expr>
{
auto loc = current_loc();
auto expr = parse_operand();
auto next = [&]() { return parse_operand(); };
if (eat('-')) {
return std::make_unique<Expr>(loc, Expr::Ty::Negate, std::move(expr));
return std::make_unique<Expr>(loc, Expr::Ty::Negate, next());
} else if (eat('!')) {
return std::make_unique<Expr>(loc, Expr::Ty::Not, std::move(expr));
return std::make_unique<Expr>(loc, Expr::Ty::Not, next());
} else {
return expr;
return next();
}
}

View File

@ -234,6 +234,11 @@ namespace asmer {
/// true means fail
bool arg_count_wrong(const Line& ins, size_t count);
auto define_sym(std::string ident, uint16_t value)
-> std::expected<void, std::string>;
auto get_sym(const std::string& ident)
-> std::expected<uint16_t, std::string>;
void error(Loc loc, std::string_view message);
std::string_view m_filename;

View File

@ -373,6 +373,29 @@ void Builder::binary_imm(Reg dst, Reg op1, uint16_t op2, Op op)
i.build(*this);
}
void Builder::call_reg(Reg op1)
{
LOG;
auto i = InsBuilder(Op::Call);
i.op1_reg(op1);
i.build(*this);
}
void Builder::call_imm(uint16_t op1)
{
LOG;
auto i = InsBuilder(Op::Call);
i.imm(op1);
i.build(*this);
}
void Builder::ret()
{
LOG;
auto i = InsBuilder(Op::Ret);
i.build(*this);
}
void Builder::reti()
{
LOG;

View File

@ -53,6 +53,9 @@ 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 call_reg(Reg op1);
void call_imm(uint16_t op1);
void ret();
void reti();
void lvcd_reg(Reg op1);
void lvcd_imm(uint16_t op1);

View File

@ -70,6 +70,8 @@ auto IoDevice::create() -> std::expected<std::unique_ptr<IoDevice>, std::string>
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
return std::unexpected(SDL_GetError());
}
SDL_StopTextInput();
}
SDL_Window* window;
@ -204,8 +206,8 @@ auto IoDevice::poll_event() -> std::unique_ptr<IoEvent>
= (event.key.keysym.mod & KMOD_RALT) != 0,
});
// default:
// std::println(stderr, "warning: unhandled event '{}'",
// event.type);
// std::println(
// stderr, "warning: unhandled event '{}'", event.type);
}
}

View File

@ -6,7 +6,9 @@
#include <print>
#include <stdexcept>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
using namespace vc5;
using namespace std::literals;
@ -16,6 +18,93 @@ void VM::load(uint16_t offset, const uint8_t* data, size_t data_size)
std::memcpy(&m_mem[offset], data, data_size);
}
namespace {
class Debugger {
public:
explicit Debugger(VM& vm)
: vm(&vm)
{
}
void run_on_line(bool halted)
{
if (halted) {
m_halted_last_call = true;
return;
}
if (m_continue) {
if (m_halted_last_call and m_break_on_halt) {
m_continue = false;
} else {
auto ip = vm->reg(Reg::Rip);
for (const auto& addr : m_break_on_address) {
if (addr == ip) {
m_continue = false;
break;
}
}
}
if (m_continue)
return;
}
m_halted_last_call = false;
auto line = std::string();
do {
std::print("> ");
std::cout.flush();
std::getline(std::cin, line);
if (line == "r") {
for (uint16_t reg = 0; reg < 10; ++reg) {
constexpr auto reg_strs = std::array {
"r0"sv,
"r1"sv,
"r2"sv,
"r3"sv,
"r4"sv,
"r5"sv,
"rbp"sv,
"rsp"sv,
"rfl"sv,
"rip"sv,
};
std::println(" {: <3} | {:04x} {: 5}",
reg_strs[reg],
vm->reg(static_cast<Reg>(reg)),
vm->reg(static_cast<Reg>(reg)));
}
} else if (line == "c") {
m_continue = true;
break;
} else if (line == "brhlt") {
m_break_on_halt = true;
} else if (line.starts_with("p")) {
uint16_t v
= std::strtoul(line.c_str() + 2, nullptr, 16) & 0xfffe;
std::println("[{: 4x}] {: 4x}", v, vm->word(v));
} else if (line.starts_with("br")) {
uint16_t v
= std::strtoul(line.c_str() + 3, nullptr, 16) & 0xfffe;
m_break_on_address.push_back(v);
}
} while (!line.empty());
}
private:
VM* vm;
std::vector<uint16_t> m_break_on_address {};
bool m_halted_last_call = false;
bool m_break_on_halt = false;
bool m_continue = false;
};
}
int VM::run()
{
constexpr uint16_t bootloader_size = 512;
@ -23,6 +112,8 @@ int VM::run()
m_disk->read(&m_mem[i * m_disk->block_size], i);
}
auto debugger = Debugger(*this);
m_on = true;
while (m_on) {
if (not m_halted) {
@ -31,42 +122,12 @@ int VM::run()
return result;
}
// if (not m_halted) {
// auto line = std::string();
// do {
// std::print("> ");
// std::cout.flush();
// std::getline(std::cin, line);
//
// if (line == "r") {
// for (uint16_t reg = 0; reg < 10; ++reg) {
// constexpr auto reg_strs = std::array {
// "R0"sv,
// "R1"sv,
// "R2"sv,
// "R3"sv,
// "R4"sv,
// "R5"sv,
// "Rbp"sv,
// "Rsp"sv,
// "Rfl"sv,
// "Rip"sv,
// };
// std::println(" {: <3} | {:04x} {: 5}",
// reg_strs[reg],
// m_regs[reg],
// m_regs[reg]);
// }
// } else if (line.starts_with("p")) {
// uint16_t v
// = std::strtoul(line.c_str() + 2, nullptr, 16) &
// 0xfffe;
// std::println("[{: 4x}] {: 4x}", v, word(v));
// }
// } while (!line.empty());
// }
// debugger.run_on_line(m_halted);
poll_events();
if (m_halted) {
std::this_thread::sleep_for(15ms);
}
}
return 0;
}
@ -255,7 +316,7 @@ int VM::run_instruction()
break;
}
case Op::LoadWord: {
auto addr = ins.mem_addr(ins.op2(), eat_word());
auto addr = ins.mem_addr(ins.op1(), eat_word());
if ((addr & 0b1) != 0) {
std::println(stderr,
@ -293,7 +354,7 @@ int VM::run_instruction()
break;
}
case Op::LoadByte: {
auto addr = ins.mem_addr(ins.op2(), eat_word());
auto addr = ins.mem_addr(ins.op1(), eat_word());
auto reg = ins.dst_reg();
set_reg(reg, byte(addr));
@ -384,6 +445,22 @@ int VM::run_instruction()
break;
}
case Op::Call: {
auto op1 = ins.op1_or_imm();
auto return_addr = word(*m_rip);
set_word(*m_rsp, return_addr);
*m_rsp -= 2;
*m_rip = op1;
break;
}
case Op::Ret: {
*m_rsp -= 2;
auto return_addr = word(*m_rsp);
*m_rip = return_addr;
break;
}
case Op::RetI: {
*m_rsp -= 2;
auto return_addr = word(*m_rsp);
@ -469,6 +546,7 @@ void VM::poll_events()
set_word(d.keycode_addr, event->as_key_event().key);
interrupt(d.int_handler);
}
continue;
}
}
}
@ -544,6 +622,10 @@ const char* vc5::op_str(Op op)
return "Sub";
case Op::RSub:
return "RSub";
case Op::Call:
return "Call";
case Op::Ret:
return "Ret";
case Op::RetI:
return "RetI";
case Op::LVCD:

View File

@ -33,6 +33,9 @@ enum class Op : uint16_t {
Sub,
RSub,
Call,
Ret,
// return from interrupt
RetI,
// load video character display

View File

@ -9,7 +9,7 @@ endif
syn keyword Keyword const align db dw
syn keyword Keyword nop hlt jmp jnz mov cmp or and xor shl rshl shr rshr add sub rsub
syn keyword Keyword reti lkbd lvcd dskr dskw
syn keyword Keyword call ret reti lkbd lvcd dskr dskw
syn keyword Operator r0 r1 r2 r3 r4 r5 rbp rsp rfl rip
syn match Operator '+'
@ -27,6 +27,8 @@ syn match Number '[1-9][0-9]*'
syn match Number '0b[01]*'
syn match Number '0x[0-9a-fA-F]*'
syn match Character "'\\\?.'"
syn region String start=+"+ skip=+\\\\\|\\"+ end=+"+
syn match Comment ";.*$"