Stub in support for commander x16

This commit is contained in:
Jason Turner 2021-09-09 22:35:04 -06:00
parent 95f50f59af
commit 165c63701a
8 changed files with 394 additions and 7 deletions

7
examples/hello_x16.cpp Normal file
View File

@ -0,0 +1,7 @@
#include <x16.hpp>
int main()
{
x16::vera::puts({15,20}, petscii::PETSCII("HELLO X16 FROM C++!"));
}

View File

@ -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,

View File

@ -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) {

View File

@ -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

280
include/np_int.hpp Normal file
View File

@ -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());
}

View File

@ -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

View File

@ -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

View File

@ -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(