diff --git a/examples/16bit_counter.cpp b/examples/16bit_counter.cpp new file mode 100644 index 0000000..118899f --- /dev/null +++ b/examples/16bit_counter.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include + +enum Colors : uint8_t { WHITE = 0x01 }; + +inline volatile uint8_t &memory_loc(const uint16_t loc) { + return *reinterpret_cast(loc); +} + +inline void poke(const uint16_t loc, const uint8_t value) { + memory_loc(loc) = value; +} + +inline std::uint8_t peek(const std::uint16_t loc) { return memory_loc(loc); } + +inline void decrement_border_color() { --memory_loc(0xd020); } + +inline void increment_border_color() { ++memory_loc(0xd020); } + +inline bool joystick_down() { + uint8_t joystick_state = memory_loc(0xDC00); + return (joystick_state & 2) == 0; +} + +void use_data(std::array &data); + +inline void puts(uint8_t x, uint8_t y, std::string_view str) { + const auto start = 0x400 + (y * 40 + x); + + std::memcpy(const_cast(&memory_loc(start)), str.data(), + str.size()); +} + + +inline void put_hex(uint8_t x, uint8_t y, uint8_t value) +{ + +} + +inline void put_hex(uint8_t x, uint8_t y, uint16_t value) +{ +} + +inline void putc(uint8_t x, uint8_t y, uint8_t c) { + const auto start = 0x400 + (y * 40 + x); + poke(start, c); +} + +struct Clock { + using milliseconds = std::chrono::duration; + + // return elapsed time since last restart + [[nodiscard]] milliseconds restart() { + // stop Timer A + poke(0xDC0E, 0b00000000); + + // last value + const auto previous_value = static_cast( + peek(0xDC04) | (static_cast(peek(0xDC05)) << 8)); + + // reset timer + poke(0xDC04, 0xFF); + poke(0xDC05, 0xFF); + + // restart timer A + poke(0xDC0E, 0b00010001); + + return milliseconds{0xFFFF - previous_value}; + } + + Clock() { [[maybe_unused]] const auto value = restart(); } +}; + +int main() { + + // static constexpr std::array data{0}; + // std::memcpy(const_cast(&memory_loc(0x400)), data.data(), + // data.size()); + + /* + puts(5, 5, "hello world"); + puts(10, 10, "hellooooo world"); + + Clock game_clock{}; + */ + + std::uint16_t us_elapsed = 0; + + int y = 0; + while (true) { + // const auto us_elapsed = game_clock.restart().count(); + + for (int i = 0; i < 4; ++i) { + const auto nibble = static_cast((us_elapsed >> (i * 4)) & 0xF); + if (nibble <= 9) { + putc(6 - i, y, nibble + 48); + } else { + putc(6 - i, y, nibble - 9); + } + } + + if (y++ == 5) { + y = 0; + } + ++us_elapsed; + increment_border_color(); + } + + /* + const auto background_color = [](Colors col) { + memory_loc(0xd021) = static_cast(col); + }; + + background_color(Colors::WHITE); + + while(true) { + if (joystick_down()) { + increment_border_color(); + } else { + decrement_border_color(); + } + } + */ +} diff --git a/src/main.cpp b/src/main.cpp index 8c1be3f..7a341b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -107,6 +107,8 @@ struct mos6502 : ASMLine ldy, tay, tya, + tax, + txa, cpy, eor, sta, @@ -152,6 +154,8 @@ struct mos6502 : ASMLine case OpCode::ldy: case OpCode::tay: case OpCode::tya: + case OpCode::tax: + case OpCode::txa: case OpCode::cpy: case OpCode::eor: case OpCode::sta: @@ -194,6 +198,8 @@ struct mos6502 : ASMLine case OpCode::ldy: case OpCode::tay: case OpCode::tya: + case OpCode::tax: + case OpCode::txa: case OpCode::eor: case OpCode::sta: case OpCode::sty: @@ -256,6 +262,10 @@ struct mos6502 : ASMLine return "tay"; case OpCode::tya: return "tya"; + case OpCode::tax: + return "tax"; + case OpCode::txa: + return "txa"; case OpCode::cpy: return "cpy"; case OpCode::eor: @@ -571,15 +581,18 @@ struct AVR : ASMLine clr, cpse, cpi, - brlo - + brlo, + add, + adc, + cpc, + brsh, + breq }; static OpCode parse_opcode(Type t, const std::string &o) { switch (t) { case Type::Label: - return OpCode::unknown; case Type::Directive: return OpCode::unknown; case Type::Instruction: { @@ -612,6 +625,11 @@ struct AVR : ASMLine if (o == "cpse") return OpCode::cpse; if (o == "cpi") return OpCode::cpi; if (o == "brlo") return OpCode::brlo; + if (o == "add") return OpCode::add; + if (o == "adc") return OpCode::adc; + if (o == "cpc") return OpCode::cpc; + if (o == "brsh") return OpCode::brsh; + if (o == "breq") return OpCode::breq; } } throw std::runtime_error("Unknown opcode: " + o); @@ -622,14 +640,14 @@ struct AVR : ASMLine if (reg_name == 'X') { return 26; } - if (reg_name == 'Y') { return 28; } - if (reg_name == 'Z') { return 30; } + + throw std::runtime_error("Unknown register name"); } static Operand get_register(const char reg_name, const int byte = 0) @@ -763,7 +781,7 @@ void fixup_16_bit_N_Z_flags(std::vector &instructions) // if it's not 0, then branch down, we know the result is not 0 and not negative instructions.emplace_back(mos6502::OpCode::bne, Operand(Operand::Type::literal, set_flag_label)); // if the higher order byte is 0, test the lower order byte, which was saved for us in Y - instructions.emplace_back(mos6502::OpCode::tya); + instructions.emplace_back(mos6502::OpCode::txa); // if low order is not negative, we know it's 0 or not 0 instructions.emplace_back(mos6502::OpCode::bpl, Operand(Operand::Type::literal, set_flag_label)); // if low order byte is negative, shift right by one bit, then we'll get the proper Z/N flags @@ -782,7 +800,7 @@ void subtract_16_bit(std::vector &instructions, int reg, const std::uin instructions.emplace_back(mos6502::OpCode::lda, AVR::get_register(reg + 1)); instructions.emplace_back(mos6502::OpCode::sbc, Operand(Operand::Type::literal, "#" + std::to_string((value >> 8) & 0xFF))); instructions.emplace_back(mos6502::OpCode::sta, AVR::get_register(reg + 1)); - instructions.emplace_back(mos6502::OpCode::tay); + instructions.emplace_back(mos6502::OpCode::tax); fixup_16_bit_N_Z_flags(instructions); } @@ -882,7 +900,7 @@ void translate_instruction(std::vector &instructions, const AVR::OpCode instructions.emplace_back(mos6502::OpCode::sta, AVR::get_register(o1_reg_num)); // temporarily store lower order (not carried substraction) byte into Y for checking // later if this is a two byte subtraction operation - instructions.emplace_back(mos6502::OpCode::tay); + instructions.emplace_back(mos6502::OpCode::tax); return; } case AVR::OpCode::st: { @@ -985,14 +1003,24 @@ void translate_instruction(std::vector &instructions, const AVR::OpCode return; } case AVR::OpCode::cpi: { + // note that this will leave the C flag in the 6502 borrow state, not normal carry state instructions.emplace_back(mos6502::OpCode::lda, AVR::get_register(o1_reg_num)); instructions.emplace_back(mos6502::OpCode::sec); instructions.emplace_back(mos6502::OpCode::sbc, Operand(o2.type, fixup_8bit_literal(o2.value))); return; } case AVR::OpCode::brlo: { - instructions.emplace_back(mos6502::OpCode::bcc, o1); - return; + + if (o1.value == ".+2") { + // assumes 6502 'borrow' for Carry flag instead of carry, so bcc instead of bcs + std::string new_label_name = "skip_next_instruction_" + std::to_string(instructions.size()); + instructions.emplace_back(mos6502::OpCode::bcc, Operand(Operand::Type::literal, new_label_name)); + instructions.emplace_back(ASMLine::Type::Directive, new_label_name); + return; + } else { + instructions.emplace_back(mos6502::OpCode::bcc, o1); + return; + } } case AVR::OpCode::swap: { // from http://www.6502.org/source/general/SWN.html @@ -1014,6 +1042,46 @@ void translate_instruction(std::vector &instructions, const AVR::OpCode return; } + case AVR::OpCode::add: { + instructions.emplace_back(mos6502::OpCode::clc); + instructions.emplace_back(mos6502::OpCode::lda, AVR::get_register(o1_reg_num)); + instructions.emplace_back(mos6502::OpCode::adc, AVR::get_register(o2_reg_num)); + instructions.emplace_back(mos6502::OpCode::sta, AVR::get_register(o1_reg_num)); + return; + } + case AVR::OpCode::adc: { + instructions.emplace_back(mos6502::OpCode::lda, AVR::get_register(o1_reg_num)); + instructions.emplace_back(mos6502::OpCode::adc, AVR::get_register(o2_reg_num)); + instructions.emplace_back(mos6502::OpCode::sta, AVR::get_register(o1_reg_num)); + return; + } + case AVR::OpCode::cpc: { + // this instruction seems to need to be used in the case after a sbc operation, where the + // carry flag is set to the appropriate borrow state, so I'm going to not invert + // the carry flag here, and assume that it's set how the 6502 wants it to be + // set from the previous operation + + instructions.emplace_back(mos6502::OpCode::lda, AVR::get_register(o1_reg_num)); + instructions.emplace_back(mos6502::OpCode::sbc, AVR::get_register(o2_reg_num)); + return; + } + case AVR::OpCode::brsh: { + if (o1.value == ".+2") { + // assumes 6502 'borrow' for Carry flag instead of carry, so bcs instead of bcc + std::string new_label_name = "skip_next_instruction_" + std::to_string(instructions.size()); + instructions.emplace_back(mos6502::OpCode::bcs, Operand(Operand::Type::literal, new_label_name)); + instructions.emplace_back(ASMLine::Type::Directive, new_label_name); + return; + } + + throw std::runtime_error("Unable to handle unknown brsh offset"); + } + + case AVR::OpCode::breq: { + instructions.emplace_back(mos6502::OpCode::beq, o1); + return; + } + } throw std::runtime_error("Could not translate unhandled instruction"); @@ -1485,6 +1553,13 @@ bool fix_long_branches(std::vector &instructions, int &branch_patch_cou instructions.insert(std::next(std::begin(instructions), op + 2), mos6502(ASMLine::Type::Label, new_pos)); instructions[op].comment = instructions[op + 1].comment = instructions[op + 2].comment = comment; return true; + } else if (instructions[op].opcode == mos6502::OpCode::beq) { + const auto comment = instructions[op].comment; + instructions[op] = mos6502(mos6502::OpCode::bne, Operand(Operand::Type::literal, new_pos)); + instructions.insert(std::next(std::begin(instructions), op + 1), mos6502(mos6502::OpCode::jmp, Operand(Operand::Type::literal, going_to))); + instructions.insert(std::next(std::begin(instructions), op + 2), mos6502(ASMLine::Type::Label, new_pos)); + instructions[op].comment = instructions[op + 1].comment = instructions[op + 2].comment = comment; + return true; } else if (instructions[op].opcode == mos6502::OpCode::bcc) { const auto comment = instructions[op].comment; instructions[op] = mos6502(mos6502::OpCode::bcs, Operand(Operand::Type::literal, new_pos)); @@ -1494,7 +1569,7 @@ bool fix_long_branches(std::vector &instructions, int &branch_patch_cou instructions[op].comment = instructions[op + 1].comment = instructions[op + 2].comment = comment; return true; } else { - throw std::runtime_error("Don't know how to reorg this branch"); + throw std::runtime_error("Don't know how to reorg this branch: " + instructions[op].to_string()); } } } @@ -1673,7 +1748,7 @@ void run(std::istream &input) new_instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, "main")); - bool skip_next_instruction = false; + int instructions_to_skip = 0; std::string next_label_name; for (const auto &i : instructions) { to_mos6502(i, new_instructions); @@ -1682,13 +1757,19 @@ void run(std::istream &input) const auto last_instruction = new_instructions.back(); const auto last_instruction_loc = new_instructions.size() - 1; - if (skip_next_instruction) { + if (instructions_to_skip == 1) { new_instructions.emplace_back(ASMLine::Type::Label, next_label_name); - skip_next_instruction = false; } + --instructions_to_skip; + if (last_instruction.type == ASMLine::Type::Directive && last_instruction.text.starts_with("skip_next_instruction")) { - skip_next_instruction = true; + instructions_to_skip = 1; + next_label_name = last_instruction.text; + new_instructions.erase(std::next(new_instructions.begin(), static_cast(last_instruction_loc))); + } + if (last_instruction.type == ASMLine::Type::Directive && last_instruction.text.starts_with("skip_next_2_instructions")) { + instructions_to_skip = 2; next_label_name = last_instruction.text; new_instructions.erase(std::next(new_instructions.begin(), static_cast(last_instruction_loc))); }