Start adding tests

This commit is contained in:
Jason Turner 2021-05-19 14:28:05 -06:00
parent 681e35ba5f
commit f64fa3cce6
3 changed files with 158 additions and 49 deletions

View File

@ -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<uint8_t>(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<mos6502> &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<mos6502> &instructions,
}
void indirect_store(std::vector<mos6502> &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<mos6502> &instructio
}
void subtract_16_bit(const Personality &personality,
std::vector<mos6502> & instructions,
int reg,
const std::uint16_t value)
std::vector<mos6502> &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<mos6502> &inst
}
void translate_instruction(const Personality &personality,
std::vector<mos6502> & instructions,
const AVR::OpCode op,
const Operand & o1,
const Operand & o2)
std::vector<mos6502> &instructions,
const AVR::OpCode op,
const Operand &o1,
const Operand &o2)
{
const auto translate_register_number = [](const Operand &reg) {
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<mos6502> 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<std::string, Target> 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);

View File

@ -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=$<TARGET_FILE:6502-c++> 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)

View File

@ -1,14 +1,115 @@
#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);
REQUIRE(Factorial(2) == 2);
REQUIRE(Factorial(3) == 6);
REQUIRE(Factorial(10) == 3628800);
constexpr static std::string_view program =
R"(
int main()
{
*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);
}