mirror of
https://github.com/lefticus/6502-cpp.git
synced 2024-06-26 00:29:30 +00:00
Start adding tests
This commit is contained in:
parent
681e35ba5f
commit
f64fa3cce6
|
@ -58,7 +58,6 @@ std::string_view strip_offset(std::string_view s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
std::string fixup_8bit_literal(const std::string &s)
|
std::string fixup_8bit_literal(const std::string &s)
|
||||||
{
|
{
|
||||||
if (s[0] == '$') { return "#" + std::to_string(static_cast<uint8_t>(parse_8bit_literal(s))); }
|
if (s[0] == '$') { return "#" + std::to_string(static_cast<uint8_t>(parse_8bit_literal(s))); }
|
||||||
|
@ -234,8 +233,8 @@ struct AVR : ASMLine
|
||||||
};
|
};
|
||||||
|
|
||||||
void indirect_load(std::vector<mos6502> &instructions,
|
void indirect_load(std::vector<mos6502> &instructions,
|
||||||
const std::string & from_address_low_byte,
|
const std::string &from_address_low_byte,
|
||||||
const std::string & to_address,
|
const std::string &to_address,
|
||||||
const int offset = 0)
|
const int offset = 0)
|
||||||
{
|
{
|
||||||
instructions.emplace_back(mos6502::OpCode::ldy, Operand(Operand::Type::literal, fmt::format("#{}", offset)));
|
instructions.emplace_back(mos6502::OpCode::ldy, Operand(Operand::Type::literal, fmt::format("#{}", offset)));
|
||||||
|
@ -245,8 +244,8 @@ void indirect_load(std::vector<mos6502> &instructions,
|
||||||
}
|
}
|
||||||
|
|
||||||
void indirect_store(std::vector<mos6502> &instructions,
|
void indirect_store(std::vector<mos6502> &instructions,
|
||||||
const std::string & from_address,
|
const std::string &from_address,
|
||||||
const std::string & to_address_low_byte,
|
const std::string &to_address_low_byte,
|
||||||
const int offset = 0)
|
const int offset = 0)
|
||||||
{
|
{
|
||||||
instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, from_address));
|
instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, from_address));
|
||||||
|
@ -293,7 +292,7 @@ void add_16_bit(const Personality &personality, std::vector<mos6502> &instructio
|
||||||
}
|
}
|
||||||
|
|
||||||
void subtract_16_bit(const Personality &personality,
|
void subtract_16_bit(const Personality &personality,
|
||||||
std::vector<mos6502> & instructions,
|
std::vector<mos6502> &instructions,
|
||||||
int reg,
|
int reg,
|
||||||
const std::uint16_t value)
|
const std::uint16_t value)
|
||||||
{
|
{
|
||||||
|
@ -324,10 +323,10 @@ void increment_16_bit(const Personality &personality, std::vector<mos6502> &inst
|
||||||
}
|
}
|
||||||
|
|
||||||
void translate_instruction(const Personality &personality,
|
void translate_instruction(const Personality &personality,
|
||||||
std::vector<mos6502> & instructions,
|
std::vector<mos6502> &instructions,
|
||||||
const AVR::OpCode op,
|
const AVR::OpCode op,
|
||||||
const Operand & o1,
|
const Operand &o1,
|
||||||
const Operand & o2)
|
const Operand &o2)
|
||||||
{
|
{
|
||||||
const auto translate_register_number = [](const Operand ®) {
|
const auto translate_register_number = [](const Operand ®) {
|
||||||
if (reg.value == "__zero_reg__") {
|
if (reg.value == "__zero_reg__") {
|
||||||
|
@ -366,14 +365,16 @@ void translate_instruction(const Personality &personality,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw std::runtime_error("Unhandled call");
|
throw std::runtime_error("Unhandled call");
|
||||||
case AVR::OpCode::icall:
|
case AVR::OpCode::icall: {
|
||||||
{
|
|
||||||
std::string new_label_name = "return_from_icall_" + std::to_string(instructions.size());
|
std::string new_label_name = "return_from_icall_" + std::to_string(instructions.size());
|
||||||
instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, fmt::format("#>({}-1)", new_label_name)));
|
instructions.emplace_back(
|
||||||
|
mos6502::OpCode::lda, Operand(Operand::Type::literal, fmt::format("#>({}-1)", new_label_name)));
|
||||||
instructions.emplace_back(mos6502::OpCode::pha);
|
instructions.emplace_back(mos6502::OpCode::pha);
|
||||||
instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, fmt::format("#<({}-1)", new_label_name)));
|
instructions.emplace_back(
|
||||||
|
mos6502::OpCode::lda, Operand(Operand::Type::literal, fmt::format("#<({}-1)", new_label_name)));
|
||||||
instructions.emplace_back(mos6502::OpCode::pha);
|
instructions.emplace_back(mos6502::OpCode::pha);
|
||||||
instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, "(" + personality.get_register(AVR::get_register_number('Z')).value + ")"));
|
instructions.emplace_back(mos6502::OpCode::jmp,
|
||||||
|
Operand(Operand::Type::literal, "(" + personality.get_register(AVR::get_register_number('Z')).value + ")"));
|
||||||
instructions.emplace_back(ASMLine::Type::Label, new_label_name);
|
instructions.emplace_back(ASMLine::Type::Label, new_label_name);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -452,9 +453,7 @@ void translate_instruction(const Personality &personality,
|
||||||
fixup_16_bit_N_Z_flags(instructions);
|
fixup_16_bit_N_Z_flags(instructions);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case AVR::OpCode::inc:
|
case AVR::OpCode::inc: instructions.emplace_back(mos6502::OpCode::inc, personality.get_register(o1_reg_num)); return;
|
||||||
instructions.emplace_back(mos6502::OpCode::inc, personality.get_register(o1_reg_num));
|
|
||||||
return;
|
|
||||||
|
|
||||||
case AVR::OpCode::subi: {
|
case AVR::OpCode::subi: {
|
||||||
// to do: deal with Carry bit (and other flags) nonsense from AVR
|
// to do: deal with Carry bit (and other flags) nonsense from AVR
|
||||||
|
@ -1040,13 +1039,18 @@ int main(const int argc, const char **argv)
|
||||||
->required(true)
|
->required(true)
|
||||||
->transform(CLI::CheckedTransformer(targets, CLI::ignore_case));
|
->transform(CLI::CheckedTransformer(targets, CLI::ignore_case));
|
||||||
|
|
||||||
|
std::string optimization_level;
|
||||||
|
app.add_option("-O", optimization_level, "Optimization level to pass to GCC instance")
|
||||||
|
->required(true)
|
||||||
|
->check(CLI::IsMember({ "s", "0", "1", "2", "3" }));
|
||||||
|
|
||||||
|
|
||||||
CLI11_PARSE(app, argc, argv)
|
CLI11_PARSE(app, argc, argv)
|
||||||
|
|
||||||
|
|
||||||
const std::string_view include_directories = "-I ~/avr-libstdcpp/include/";
|
const std::string_view include_directories = "-I ~/avr-libstdcpp/include/";
|
||||||
const std::string_view warning_flags = "-Wall -Wextra";
|
const std::string_view warning_flags = "-Wall -Wextra";
|
||||||
const std::string_view avr = "avr3";
|
const std::string_view avr = "avr3";
|
||||||
const std::string_view optimization_level = "3";
|
|
||||||
|
|
||||||
const auto make_output_file_name = [](auto input_filename, const auto &new_extension) {
|
const auto make_output_file_name = [](auto input_filename, const auto &new_extension) {
|
||||||
input_filename.replace_extension(new_extension);
|
input_filename.replace_extension(new_extension);
|
||||||
|
|
|
@ -6,7 +6,7 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(catch_main STATIC catch_main.cpp)
|
add_library(catch_main STATIC catch_main.cpp)
|
||||||
target_link_libraries(catch_main PUBLIC CONAN_PKG::catch2)
|
target_link_libraries(catch_main PUBLIC CONAN_PKG::catch2 CONAN_PKG::fmt)
|
||||||
target_link_libraries(catch_main PRIVATE project_options)
|
target_link_libraries(catch_main PRIVATE project_options)
|
||||||
|
|
||||||
add_executable(tests tests.cpp)
|
add_executable(tests tests.cpp)
|
||||||
|
@ -17,15 +17,19 @@ target_link_libraries(tests PRIVATE project_warnings project_options catch_main)
|
||||||
catch_discover_tests(
|
catch_discover_tests(
|
||||||
tests
|
tests
|
||||||
TEST_PREFIX
|
TEST_PREFIX
|
||||||
"unittests."
|
"approval_tests."
|
||||||
|
PROPERTIES
|
||||||
|
ENVIRONMENT CXX_6502=$<TARGET_FILE:6502-c++> ENVIRONMENT X64=/usr/bin/x64
|
||||||
REPORTER
|
REPORTER
|
||||||
xml
|
xml
|
||||||
OUTPUT_DIR
|
OUTPUT_DIR
|
||||||
.
|
.
|
||||||
OUTPUT_PREFIX
|
OUTPUT_PREFIX
|
||||||
"unittests."
|
"approval_tests."
|
||||||
OUTPUT_SUFFIX
|
OUTPUT_SUFFIX
|
||||||
.xml)
|
.xml
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
# Add a file containing a set of constexpr tests
|
# Add a file containing a set of constexpr tests
|
||||||
add_executable(constexpr_tests constexpr_tests.cpp)
|
add_executable(constexpr_tests constexpr_tests.cpp)
|
||||||
|
|
115
test/tests.cpp
115
test/tests.cpp
|
@ -1,14 +1,115 @@
|
||||||
#include <catch2/catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
|
|
||||||
unsigned int Factorial(unsigned int number)
|
#include <fmt/format.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
enum struct OptimizationLevel : char { O0='0', O1='1', O2='2', O3='3', Os='s' };
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> execute_c64_program(const std::string_view &name,
|
||||||
|
const std::string_view script,
|
||||||
|
[[maybe_unused]] OptimizationLevel o,
|
||||||
|
std::uint16_t start_address_dump,
|
||||||
|
std::uint16_t end_address_dump)
|
||||||
{
|
{
|
||||||
return number <= 1 ? number : Factorial(number - 1) * number;
|
|
||||||
|
const char *x64_executable = std::getenv("X64");
|
||||||
|
REQUIRE(x64_executable != nullptr);
|
||||||
|
const char *mos6502_cpp_executable = std::getenv("CXX_6502");
|
||||||
|
REQUIRE(mos6502_cpp_executable != nullptr);
|
||||||
|
|
||||||
|
const auto optimization_level = [&]() -> std::string_view {
|
||||||
|
switch(o) {
|
||||||
|
case OptimizationLevel::Os: return "-Os";
|
||||||
|
case OptimizationLevel::O1: return "-O1";
|
||||||
|
case OptimizationLevel::O2: return "-O2";
|
||||||
|
case OptimizationLevel::O3: return "-O3";
|
||||||
|
case OptimizationLevel::O0: return "-O0";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
}();
|
||||||
|
const auto source_filename{ fmt::format("{}{}.cpp", name, optimization_level) };
|
||||||
|
const auto vice_script_filename{fmt::format("{}{}-vice_script", name, optimization_level)};
|
||||||
|
const auto prg_filename{ fmt::format("{}{}.prg", name, optimization_level) };
|
||||||
|
const auto ram_dump_filename{ fmt::format("{}{}-ram_dump", name, optimization_level) };
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream source(source_filename);
|
||||||
|
source << script;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::ofstream vice_script(vice_script_filename);
|
||||||
|
vice_script << fmt::format(
|
||||||
|
R"(
|
||||||
|
z 100000
|
||||||
|
l "{}" 0
|
||||||
|
keybuf run\n
|
||||||
|
z 100000
|
||||||
|
bsave "{}" 0 {:x} {:x}
|
||||||
|
quit
|
||||||
|
)",
|
||||||
|
prg_filename,
|
||||||
|
ram_dump_filename,
|
||||||
|
start_address_dump,
|
||||||
|
end_address_dump);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
REQUIRE(system(fmt::format("{} -f {} -t C64 {}", mos6502_cpp_executable, source_filename, optimization_level).c_str()) == EXIT_SUCCESS);
|
||||||
|
REQUIRE(system(fmt::format("xvfb-run {} +saveres -warp -moncommands {}", x64_executable, vice_script_filename).c_str()) == EXIT_SUCCESS);
|
||||||
|
|
||||||
|
std::ifstream memory_dump(ram_dump_filename, std::ios::binary);
|
||||||
|
|
||||||
|
std::vector<char> data;
|
||||||
|
data.resize(static_cast<std::size_t>(end_address_dump - start_address_dump + 1));
|
||||||
|
memory_dump.read(data.data(), std::ssize(data));
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> return_value{ data.begin(), data.end() };
|
||||||
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Factorials are computed", "[factorial]")
|
TEST_CASE("Can write to screen memory")
|
||||||
{
|
{
|
||||||
REQUIRE(Factorial(1) == 1);
|
constexpr static std::string_view program =
|
||||||
REQUIRE(Factorial(2) == 2);
|
R"(
|
||||||
REQUIRE(Factorial(3) == 6);
|
int main()
|
||||||
REQUIRE(Factorial(10) == 3628800);
|
{
|
||||||
|
*reinterpret_cast<volatile unsigned char *>(0x400) = 10;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto o_level = GENERATE(OptimizationLevel::O0, OptimizationLevel::O1, OptimizationLevel::O2, OptimizationLevel::O3, OptimizationLevel::Os);
|
||||||
|
const auto result = execute_c64_program("write_to_screen_memory", program, o_level, 0x400, 0x400);
|
||||||
|
|
||||||
|
REQUIRE(result.size() == 1);
|
||||||
|
CHECK(result[0] == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Can write to screen memory via function call")
|
||||||
|
{
|
||||||
|
constexpr static std::string_view program =
|
||||||
|
R"(
|
||||||
|
|
||||||
|
void poke(unsigned int location, unsigned char value) {
|
||||||
|
*reinterpret_cast<volatile unsigned char *>(location) = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
poke(0x400, 10);
|
||||||
|
poke(0x401, 11);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
const auto o_level = GENERATE(
|
||||||
|
OptimizationLevel::O0, OptimizationLevel::O1, OptimizationLevel::O2, OptimizationLevel::O3, OptimizationLevel::Os);
|
||||||
|
const auto result = execute_c64_program("write_to_screen_memory_via_function", program, o_level, 0x400, 0x401);
|
||||||
|
|
||||||
|
REQUIRE(result.size() == 2);
|
||||||
|
CHECK(result[0] == 10);
|
||||||
|
CHECK(result[0] == 11);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user