From f64fa3cce6b6cefecb77805da01613f5de1729e2 Mon Sep 17 00:00:00 2001 From: Jason Turner Date: Wed, 19 May 2021 14:28:05 -0600 Subject: [PATCH] Start adding tests --- src/6502-c++.cpp | 80 +++++++++++++++--------------- test/CMakeLists.txt | 12 +++-- test/tests.cpp | 115 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 158 insertions(+), 49 deletions(-) diff --git a/src/6502-c++.cpp b/src/6502-c++.cpp index ddcd480..77e1c39 100644 --- a/src/6502-c++.cpp +++ b/src/6502-c++.cpp @@ -58,7 +58,6 @@ std::string_view strip_offset(std::string_view s) } - std::string fixup_8bit_literal(const std::string &s) { if (s[0] == '$') { return "#" + std::to_string(static_cast(parse_8bit_literal(s))); } @@ -216,9 +215,9 @@ struct AVR : ASMLine } } - AVR(const int t_line_num, + AVR(const int t_line_num, std::string_view t_line_text, - Type t, + Type t, std::string_view t_opcode, std::string_view o1 = "", std::string_view o2 = "") @@ -226,17 +225,17 @@ struct AVR : ASMLine opcode(parse_opcode(t, t_opcode)), operand1(parse_operand(o1)), operand2(parse_operand(o2)) {} - int line_num; + int line_num; std::string line_text; - OpCode opcode; - Operand operand1; - Operand operand2; + OpCode opcode; + Operand operand1; + Operand operand2; }; void indirect_load(std::vector &instructions, - const std::string & from_address_low_byte, - const std::string & to_address, - const int offset = 0) + const std::string &from_address_low_byte, + const std::string &to_address, + const int offset = 0) { instructions.emplace_back(mos6502::OpCode::ldy, Operand(Operand::Type::literal, fmt::format("#{}", offset))); instructions.emplace_back( @@ -245,9 +244,9 @@ void indirect_load(std::vector &instructions, } void indirect_store(std::vector &instructions, - const std::string & from_address, - const std::string & to_address_low_byte, - const int offset = 0) + const std::string &from_address, + const std::string &to_address_low_byte, + const int offset = 0) { instructions.emplace_back(mos6502::OpCode::lda, Operand(Operand::Type::literal, from_address)); instructions.emplace_back(mos6502::OpCode::ldy, Operand(Operand::Type::literal, fmt::format("#{}", offset))); @@ -293,9 +292,9 @@ void add_16_bit(const Personality &personality, std::vector &instructio } void subtract_16_bit(const Personality &personality, - std::vector & instructions, - int reg, - const std::uint16_t value) + std::vector &instructions, + int reg, + const std::uint16_t value) { instructions.emplace_back(mos6502::OpCode::sec); instructions.emplace_back(mos6502::OpCode::lda, personality.get_register(reg)); @@ -324,10 +323,10 @@ void increment_16_bit(const Personality &personality, std::vector &inst } void translate_instruction(const Personality &personality, - std::vector & instructions, - const AVR::OpCode op, - const Operand & o1, - const Operand & o2) + std::vector &instructions, + const AVR::OpCode op, + const Operand &o1, + const Operand &o2) { const auto translate_register_number = [](const Operand ®) { if (reg.value == "__zero_reg__") { @@ -366,17 +365,19 @@ void translate_instruction(const Personality &personality, return; } throw std::runtime_error("Unhandled call"); - case AVR::OpCode::icall: - { - 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::pha); - 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::jmp, Operand(Operand::Type::literal, "(" + personality.get_register(AVR::get_register_number('Z')).value + ")")); - instructions.emplace_back(ASMLine::Type::Label, new_label_name); - return; - } + case AVR::OpCode::icall: { + 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::pha); + 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::jmp, + Operand(Operand::Type::literal, "(" + personality.get_register(AVR::get_register_number('Z')).value + ")")); + instructions.emplace_back(ASMLine::Type::Label, new_label_name); + return; + } case AVR::OpCode::rcall: if (o1.value != ".") { instructions.emplace_back(mos6502::OpCode::jsr, o1); @@ -452,9 +453,7 @@ void translate_instruction(const Personality &personality, fixup_16_bit_N_Z_flags(instructions); return; } - case AVR::OpCode::inc: - instructions.emplace_back(mos6502::OpCode::inc, personality.get_register(o1_reg_num)); - return; + case AVR::OpCode::inc: instructions.emplace_back(mos6502::OpCode::inc, personality.get_register(o1_reg_num)); return; case AVR::OpCode::subi: { // to do: deal with Carry bit (and other flags) nonsense from AVR @@ -985,7 +984,7 @@ std::vector run(const Personality &personality, std::istream &input) new_instructions.emplace_back(mos6502::OpCode::jmp, Operand(Operand::Type::literal, "main")); - int instructions_to_skip = 0; + int instructions_to_skip = 0; std::string next_label_name; for (const auto &i : instructions) { to_mos6502(personality, i, new_instructions); @@ -1030,23 +1029,28 @@ int main(const int argc, const char **argv) { spdlog::set_level(spdlog::level::trace); const std::map targets{ { "C64", Target::C64 } }; - CLI::App app{ "C++ Compiler for 6502 processors" }; + CLI::App app{ "C++ Compiler for 6502 processors" }; std::filesystem::path filename{}; - Target target{ Target::C64 }; + Target target{ Target::C64 }; app.add_option("-f,--file", filename, "C++ file to compile")->required(true); app.add_option("-t,--target", target, "6502 - based system to target") ->required(true) ->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) + const std::string_view include_directories = "-I ~/avr-libstdcpp/include/"; const std::string_view warning_flags = "-Wall -Wextra"; 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) { input_filename.replace_extension(new_extension); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a69c7c7..5c1fded 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,7 +6,7 @@ else() endif() 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) add_executable(tests tests.cpp) @@ -17,15 +17,19 @@ target_link_libraries(tests PRIVATE project_warnings project_options catch_main) catch_discover_tests( tests TEST_PREFIX - "unittests." + "approval_tests." + PROPERTIES + ENVIRONMENT CXX_6502=$ ENVIRONMENT X64=/usr/bin/x64 REPORTER xml OUTPUT_DIR . OUTPUT_PREFIX - "unittests." + "approval_tests." OUTPUT_SUFFIX - .xml) + .xml + +) # Add a file containing a set of constexpr tests add_executable(constexpr_tests constexpr_tests.cpp) diff --git a/test/tests.cpp b/test/tests.cpp index 9c40b28..a4d7cc7 100644 --- a/test/tests.cpp +++ b/test/tests.cpp @@ -1,14 +1,115 @@ #include -unsigned int Factorial(unsigned int number) +#include +#include + +enum struct OptimizationLevel : char { O0='0', O1='1', O2='2', O3='3', Os='s' }; + + +std::vector 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 data; + data.resize(static_cast(end_address_dump - start_address_dump + 1)); + memory_dump.read(data.data(), std::ssize(data)); + + std::vector 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); - REQUIRE(Factorial(2) == 2); - REQUIRE(Factorial(3) == 6); - REQUIRE(Factorial(10) == 3628800); + constexpr static std::string_view program = + R"( +int main() +{ + *reinterpret_cast(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(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); + }