mirror of https://github.com/lefticus/6502-cpp.git
Stub in support for commander x16
This commit is contained in:
parent
95f50f59af
commit
165c63701a
|
@ -0,0 +1,7 @@
|
|||
#include <x16.hpp>
|
||||
|
||||
int main()
|
||||
{
|
||||
x16::vera::puts({15,20}, petscii::PETSCII("HELLO X16 FROM C++!"));
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include <cstdint>
|
||||
|
||||
namespace petscii {
|
||||
constexpr static std::uint8_t uppercase[] = {
|
||||
constexpr static std::array<std::uint8_t, 256*8> uppercase = {
|
||||
0x3C, 0x66, 0x6E, 0x6E, 0x60, 0x62, 0x3C, 0x00,
|
||||
0x18, 0x3C, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x00,
|
||||
0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x7C, 0x00,
|
||||
|
|
|
@ -167,7 +167,7 @@ template<geometry::size Size> static constexpr auto from_pixels_to_petscii(const
|
|||
{
|
||||
petscii::Graphic<geometry::size{ Size.width / 8, Size.height / 8 }> result{};
|
||||
|
||||
constexpr auto charset = load_charset(uppercase);
|
||||
constexpr auto charset = petscii::load_charset(petscii::uppercase);
|
||||
|
||||
for (uint8_t x = 0; x < pixels.width(); x += 8) {
|
||||
for (uint8_t y = 0; y < pixels.height(); y += 8) {
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef INC_6502_C_CPP_X16
|
||||
#define INC_6502_C_CPP_X16
|
||||
|
||||
#include <6502.hpp>
|
||||
#include <geometry.hpp>
|
||||
#include <span>
|
||||
#include <petscii.hpp>
|
||||
|
||||
namespace x16::vera {
|
||||
static constexpr std::uint16_t ADDR_L = 0x9f20;
|
||||
static constexpr std::uint16_t ADDR_M = 0x9f21;
|
||||
static constexpr std::uint16_t ADDR_H = 0x9f22;
|
||||
static constexpr std::uint16_t DATA0 = 0x9f23;
|
||||
|
||||
// this assumes we are already in a text mode, initialized by the kernal
|
||||
static void puts(const geometry::point &loc, std::span<const char> str) {
|
||||
mos6502::poke(ADDR_L, loc.x * 2);
|
||||
mos6502::poke(ADDR_M, loc.y);
|
||||
mos6502::poke(ADDR_H, 0x20); // auto increment by 2 after each poke of the character, we are skipping the attribute byte
|
||||
|
||||
for (const auto c : str) {
|
||||
mos6502::poke(DATA0, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
template<std::integral Int> struct np_int
|
||||
{
|
||||
Int val;
|
||||
|
||||
// intentionally implicit conversions
|
||||
// from the underlying type allowed
|
||||
[[gnu::always_inline]] constexpr np_int(Int i) noexcept : val{ i } {}
|
||||
|
||||
[[gnu::always_inline]] constexpr explicit operator Int() noexcept { return val; }
|
||||
|
||||
[[gnu::always_inline]] constexpr auto operator<=>(const np_int &) const noexcept = default;
|
||||
|
||||
template<typename Other>
|
||||
[[gnu::always_inline]] constexpr auto operator<=>(const np_int<Other> &other) const noexcept
|
||||
requires(std::is_signed_v<Int> == std::is_signed_v<Other>)
|
||||
{
|
||||
return val == other.val ? std::strong_ordering::equal
|
||||
: val < other.val ? std::strong_ordering::less
|
||||
: std::strong_ordering::greater;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T> constexpr bool np_or_integral_v = std::is_integral_v<T>;
|
||||
|
||||
template<typename T> constexpr bool np_or_integral_v<np_int<T>> = true;
|
||||
|
||||
template<typename T> concept np_or_integral = np_or_integral_v<T>;
|
||||
|
||||
template<std::integral Int> [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(Int i) noexcept
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
template<typename T> [[nodiscard, gnu::always_inline, gnu::const]] constexpr auto val(np_int<T> i) noexcept
|
||||
{
|
||||
return i.val;
|
||||
}
|
||||
|
||||
template<typename LHS, typename RHS> consteval auto calculate_common_int()
|
||||
{
|
||||
constexpr auto size = std::max(sizeof(LHS), sizeof(RHS));
|
||||
if constexpr (std::is_unsigned_v<LHS> && std::is_unsigned_v<RHS>) {
|
||||
if constexpr (size == 1) {
|
||||
return std::uint8_t{};
|
||||
} else if constexpr (size == 2) {
|
||||
return std::uint16_t{};
|
||||
} else if constexpr (size == 4) {
|
||||
return std::uint32_t{};
|
||||
} else if constexpr (size == 8) {
|
||||
return std::uint64_t{};
|
||||
}
|
||||
} else {
|
||||
if constexpr (size == 1) {
|
||||
return std::int8_t{};
|
||||
} else if constexpr (size == 2) {
|
||||
return std::int16_t{};
|
||||
} else if constexpr (size == 4) {
|
||||
return std::int32_t{};
|
||||
} else if constexpr (size == 8) {
|
||||
return std::int64_t{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS> using common_int_t = decltype(calculate_common_int<LHS, RHS>());
|
||||
|
||||
template<std::integral Int>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator>>(np_int<Int> lhs,
|
||||
np_or_integral auto rhs) noexcept
|
||||
{
|
||||
return np_int<Int>{ static_cast<Int>(lhs.val >> static_cast<Int>(rhs)) };
|
||||
}
|
||||
|
||||
template<std::integral Int>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator<<(np_int<Int> lhs,
|
||||
np_or_integral auto rhs) noexcept
|
||||
{
|
||||
return np_int<Int>{ static_cast<Int>(lhs.val << static_cast<Int>(rhs)) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator+(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
|
||||
{
|
||||
using Type = common_int_t<LHS, RHS>;
|
||||
return np_int{ static_cast<Type>(lhs.val + rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator-(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
|
||||
{
|
||||
using Type = common_int_t<LHS, RHS>;
|
||||
return np_int{ static_cast<Type>(lhs.val - rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator*(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
|
||||
{
|
||||
using Type = common_int_t<LHS, RHS>;
|
||||
return np_int{ static_cast<Type>(lhs.val * rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator/(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
|
||||
{
|
||||
using Type = common_int_t<LHS, RHS>;
|
||||
return np_int{ static_cast<Type>(lhs.val / rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator|(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
|
||||
{
|
||||
return np_int{ static_cast<LHS>(lhs.val | rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator&(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
|
||||
{
|
||||
return np_int{ static_cast<LHS>(lhs.val & rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator%(np_int<LHS> lhs, np_int<RHS> rhs) noexcept
|
||||
{
|
||||
using Type = common_int_t<LHS, RHS>;
|
||||
return np_int{ static_cast<Type>(lhs.val % rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS, std::integral RHS>
|
||||
[[nodiscard, gnu::always_inline, gnu::const]] constexpr auto operator^(np_int<LHS> lhs, np_int<LHS> rhs) noexcept
|
||||
{
|
||||
return np_int{ static_cast<LHS>(lhs.val ^ rhs.val) };
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator+=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val += static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator-=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val -= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator/=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val /= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator*=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val /= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator%=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val %= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator&=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val &= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator|=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val |= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator^=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val ^= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator<<=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val <<= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
template<std::integral LHS>
|
||||
[[gnu::always_inline]] constexpr auto &operator>>=(np_int<LHS> &lhs, np_or_integral auto rhs) noexcept
|
||||
{
|
||||
lhs.val >>= static_cast<LHS>(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
using uint_np8_t = np_int<std::uint8_t>;
|
||||
using uint_np16_t = np_int<std::uint16_t>;
|
||||
using uint_np32_t = np_int<std::uint32_t>;
|
||||
using uint_np64_t = np_int<std::uint64_t>;
|
||||
|
||||
using int_np8_t = np_int<std::int8_t>;
|
||||
using int_np16_t = np_int<std::int16_t>;
|
||||
using int_np32_t = np_int<std::int32_t>;
|
||||
using int_np64_t = np_int<std::int64_t>;
|
||||
|
||||
template<typename T>
|
||||
constexpr bool np_meets_requirements =
|
||||
sizeof(T) == sizeof(np_int<T>)
|
||||
&& std::is_trivially_destructible_v<np_int<T>> &&std::is_trivially_move_constructible_v<np_int<T>>
|
||||
&&std::is_trivially_copy_constructible_v<np_int<T>> &&std::is_trivially_copy_assignable_v<np_int<T>>
|
||||
&&std::is_trivially_move_assignable_v<np_int<T>>;
|
||||
|
||||
static_assert(np_meets_requirements<std::uint8_t>);
|
||||
static_assert(np_meets_requirements<std::uint16_t>);
|
||||
static_assert(np_meets_requirements<std::uint32_t>);
|
||||
static_assert(np_meets_requirements<std::uint64_t>);
|
||||
|
||||
static_assert(np_meets_requirements<std::int8_t>);
|
||||
static_assert(np_meets_requirements<std::int16_t>);
|
||||
static_assert(np_meets_requirements<std::int32_t>);
|
||||
static_assert(np_meets_requirements<std::int64_t>);
|
||||
|
||||
// ensures that First paired with any of Rest, in any order
|
||||
// results in the same type as First again
|
||||
template<std::integral First, std::integral... Rest>
|
||||
constexpr bool is_same_combinations_v = (std::is_same_v<First, common_int_t<First, Rest>> && ...)
|
||||
&& (std::is_same_v<First, common_int_t<Rest, First>> && ...);
|
||||
|
||||
static_assert(is_same_combinations_v<std::uint8_t, std::uint8_t>);
|
||||
static_assert(is_same_combinations_v<std::uint16_t, std::uint8_t, std::uint16_t>);
|
||||
static_assert(is_same_combinations_v<std::uint32_t, std::uint8_t, std::uint16_t, std::uint32_t>);
|
||||
static_assert(is_same_combinations_v<std::uint64_t, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>);
|
||||
|
||||
static_assert(is_same_combinations_v<std::int8_t, std::uint8_t>);
|
||||
static_assert(is_same_combinations_v<std::int16_t, std::uint8_t, std::uint16_t>);
|
||||
static_assert(is_same_combinations_v<std::int32_t, std::uint8_t, std::uint16_t, std::uint32_t>);
|
||||
static_assert(is_same_combinations_v<std::int64_t, std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>);
|
||||
|
||||
static_assert(is_same_combinations_v<std::int8_t, std::int8_t>);
|
||||
static_assert(is_same_combinations_v<std::int16_t, std::int8_t, std::int16_t>);
|
||||
static_assert(is_same_combinations_v<std::int32_t, std::int8_t, std::int16_t, std::int32_t>);
|
||||
static_assert(is_same_combinations_v<std::int64_t, std::int8_t, std::int16_t, std::int32_t, std::int64_t>);
|
||||
|
||||
auto left_shift(uint_np8_t value, int val) { return value << val; }
|
||||
|
||||
auto left_shift(std::uint8_t value, int val) { return static_cast<std::uint8_t>(value << val); }
|
||||
|
||||
std::uint8_t unsigned_value();
|
||||
std::int8_t signed_value();
|
||||
|
||||
|
||||
int main(int argc, const char **)
|
||||
{
|
||||
uint_np8_t a{ 15 };
|
||||
auto value = a + uint_np16_t{ 10 };
|
||||
|
||||
value += uint_np16_t{ 16 };
|
||||
|
||||
[[maybe_unused]] const auto comparison = value < uint_np8_t{ 3 };
|
||||
// return static_cast<std::uint16_t>(value);
|
||||
|
||||
int i = 1;
|
||||
[[maybe_unused]] int v = (i << argc);
|
||||
|
||||
|
||||
return std::cmp_less(unsigned_value(), signed_value());
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef INC_6502_C_X16_HPP
|
||||
#define INC_6502_C_X16_HPP
|
||||
|
||||
#include "../personality.hpp"
|
||||
|
||||
struct X16 : Personality
|
||||
{
|
||||
|
||||
void insert_autostart_sequence(std::vector<mos6502> &new_instructions) const override
|
||||
{
|
||||
constexpr static auto start_address = 0x0801;
|
||||
|
||||
// first 2 bytes is the load address for a PRG file.
|
||||
new_instructions.emplace_back(ASMLine::Type::Directive, ".word " + std::to_string(start_address));
|
||||
new_instructions.emplace_back(ASMLine::Type::Directive, "* = " + std::to_string(start_address));
|
||||
new_instructions.emplace_back(ASMLine::Type::Directive, "; jmp to start of program with BASIC");
|
||||
new_instructions.emplace_back(ASMLine::Type::Directive, ".byt $0B,$08,$0A,$00,$9E,$32,$30,$36,$31,$00,$00,$00");
|
||||
}
|
||||
|
||||
[[nodiscard]] Operand get_register(const int reg_num) const override
|
||||
{
|
||||
switch (reg_num) {
|
||||
// based on c64, they are basically the same system, but I'm using a different file so they can fork from each other
|
||||
case 0: return Operand(Operand::Type::literal, "$4e");// unused, int->fp routine pointer
|
||||
case 1: return Operand(Operand::Type::literal, "$4f");
|
||||
case 2: return Operand(Operand::Type::literal, "$50");// unused
|
||||
case 3: return Operand(Operand::Type::literal, "$51");// unused
|
||||
case 4: return Operand(Operand::Type::literal, "$52");// bit buffer for rs232
|
||||
case 5: return Operand(Operand::Type::literal, "$53");// counter for rs232
|
||||
case 6: return Operand(Operand::Type::literal, "$54");// unused
|
||||
case 7: return Operand(Operand::Type::literal, "$55");// unused
|
||||
case 8: return Operand(Operand::Type::literal, "$56");// unused
|
||||
case 9: return Operand(Operand::Type::literal, "$57");// unused
|
||||
case 10: return Operand(Operand::Type::literal, "$58");// Current BASIC line number
|
||||
case 11: return Operand(Operand::Type::literal, "$59");// Current BASIC line number
|
||||
case 12: return Operand(Operand::Type::literal, "$5a");// arithmetic register #3
|
||||
case 13: return Operand(Operand::Type::literal, "$5b");
|
||||
case 14: return Operand(Operand::Type::literal, "$5c");
|
||||
case 15: return Operand(Operand::Type::literal, "$5d");
|
||||
case 16: return Operand(Operand::Type::literal, "$5e");
|
||||
case 17: return Operand(Operand::Type::literal, "$5f");
|
||||
case 18: return Operand(Operand::Type::literal, "$60");
|
||||
case 19: return Operand(Operand::Type::literal, "$61");
|
||||
case 20: return Operand(Operand::Type::literal, "$62");
|
||||
case 21: return Operand(Operand::Type::literal, "$63");
|
||||
case 22: return Operand(Operand::Type::literal, "$64");
|
||||
case 23: return Operand(Operand::Type::literal, "$65");
|
||||
case 24: return Operand(Operand::Type::literal, "$66");
|
||||
case 25: return Operand(Operand::Type::literal, "$67");
|
||||
case 26: return Operand(Operand::Type::literal, "$68");
|
||||
case 27: return Operand(Operand::Type::literal, "$69");
|
||||
case 28: return Operand(Operand::Type::literal, "$6a");
|
||||
case 29: return Operand(Operand::Type::literal, "$6b");
|
||||
case 30: return Operand(Operand::Type::literal, "$6c");
|
||||
case 31: return Operand(Operand::Type::literal, "$6d");
|
||||
}
|
||||
throw std::runtime_error("Unhandled register number: " + std::to_string(reg_num));
|
||||
}
|
||||
};
|
||||
|
||||
#endif// INC_6502_C_X16_HPP
|
|
@ -19,6 +19,7 @@
|
|||
#include "include/lib1funcs.hpp"
|
||||
#include "include/optimizer.hpp"
|
||||
#include "include/personalities/c64.hpp"
|
||||
#include "include/personalities/x16.hpp"
|
||||
|
||||
int to_int(const std::string_view sv)
|
||||
{
|
||||
|
@ -1311,12 +1312,12 @@ std::vector<mos6502> run(const Personality &personality, std::istream &input, co
|
|||
return new_instructions;
|
||||
}
|
||||
|
||||
enum struct Target { C64 };
|
||||
enum struct Target { C64, X16 };
|
||||
|
||||
int main(const int argc, const char **argv)
|
||||
{
|
||||
spdlog::set_level(spdlog::level::warn);
|
||||
const std::map<std::string, Target> targets{ { "C64", Target::C64 } };
|
||||
const std::map<std::string, Target> targets{ { "C64", Target::C64 }, { "X16", Target::X16 } };
|
||||
CLI::App app{ "C++ Compiler for 6502 processors" };
|
||||
|
||||
std::filesystem::path filename{};
|
||||
|
@ -1401,9 +1402,18 @@ int main(const int argc, const char **argv)
|
|||
std::ifstream input(avr_output_file);
|
||||
|
||||
|
||||
C64 personality;
|
||||
|
||||
const auto new_instructions = run(personality, input, optimize);
|
||||
const auto new_instructions = [&]() {
|
||||
switch (target) {
|
||||
case Target::C64:
|
||||
return run(C64{}, input, optimize);
|
||||
case Target::X16:
|
||||
return run(X16{}, input, optimize);
|
||||
default:
|
||||
spdlog::critical("Unhandled target type");
|
||||
return std::vector<mos6502>{};
|
||||
}
|
||||
}();
|
||||
|
||||
{
|
||||
// make sure file is closed before we try to re-open it with xa
|
||||
|
|
|
@ -80,7 +80,7 @@ quit
|
|||
|
||||
|
||||
REQUIRE(system(fmt::format(
|
||||
"{} -f {} -t C64 {} {}", mos6502_cpp_executable, source_filename, optimization_level, optimize_6502)
|
||||
"{} {} -t C64 {} {}", mos6502_cpp_executable, source_filename, optimization_level, optimize_6502)
|
||||
.c_str())
|
||||
== EXIT_SUCCESS);
|
||||
REQUIRE(
|
||||
|
|
Loading…
Reference in New Issue