Add more tests, prep for graphical output
All checks were successful
Test / build (push) Successful in 8s

This commit is contained in:
shylie 2025-07-08 11:53:56 -04:00
parent 21d143fa04
commit 060ca1d3cd
18 changed files with 339 additions and 25 deletions

View File

@ -13,4 +13,4 @@ jobs:
- name: Build - name: Build
run: cmake --build emulator/build run: cmake --build emulator/build
- name: Test - name: Test
run: cd emulator/build && ctest . run: cd emulator/build && ctest --output-on-failure

View File

@ -25,7 +25,7 @@
╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣ ╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣
║ BXOR ║ Bitwise xor ║ D = A ^ B ║ CCCR ║ 0100 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 001 ║ A < B ║ ║ BXOR ║ Bitwise xor ║ D = A ^ B ║ CCCR ║ 0100 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 001 ║ A < B ║
╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣ ╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣
URSH ║ Unsigned right shift ║ D = A >> B ║ CCCR ║ 0101 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 010 ║ A <= B ║ RESERVED ║ ║ ║ CCCR ║ 0101 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 010 ║ A <= B ║
╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣ ╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣
║ SRSH ║ Signed right shift ║ D = A >> B ║ CCCR ║ 0110 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 011 ║ A = B ║ ║ SRSH ║ Signed right shift ║ D = A >> B ║ CCCR ║ 0110 ║ DDDd ║ dddd ║ BBBb ║ bbbb ║ AAAa ║ aaaa ║ ║ 011 ║ A = B ║
╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣ ╠══════════╬═════════════════════════╬══════════════════════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╬══════╣ ╠═══════╬═════════════════════════╣

View File

@ -5,8 +5,10 @@ project(foot-emulator)
add_library(foot-emulator src/foot-emulator.cpp include/foot-emulator.h) add_library(foot-emulator src/foot-emulator.cpp include/foot-emulator.h)
target_include_directories(foot-emulator PUBLIC include) target_include_directories(foot-emulator PUBLIC include)
find_package(SDL3 REQUIRED CONFIG)
add_executable(foot-emulator-cli src/main.cpp) add_executable(foot-emulator-cli src/main.cpp)
target_link_libraries(foot-emulator-cli PUBLIC foot-emulator) target_link_libraries(foot-emulator-cli PUBLIC foot-emulator SDL3::SDL3)
if(PROJECT_IS_TOP_LEVEL) if(PROJECT_IS_TOP_LEVEL)
enable_testing() enable_testing()

View File

@ -0,0 +1,8 @@
#include "foot.asm"
CNST. r30, #0x1000
loop:
CNST. r0, #0xEFFE
CNST.r [r0++], #29
CNST. r31, #loop

View File

@ -33,7 +33,7 @@ class Device
public: public:
virtual ~Device() = default; virtual ~Device() = default;
virtual void update() = 0; virtual bool update() = 0;
virtual uint16_t mapped_memory_size() const { return assigned_memory_size; } virtual uint16_t mapped_memory_size() const { return assigned_memory_size; }
protected: protected:
@ -77,6 +77,7 @@ private:
uint16_t dummy_value; uint16_t dummy_value;
std::vector<std::unique_ptr<Device>> devices; std::vector<std::unique_ptr<Device>> devices;
uint16_t mapped_base; uint16_t mapped_base;
bool keep_running;
void read_instruction(); void read_instruction();
@ -89,7 +90,6 @@ private:
void BWOR(); void BWOR();
void BAND(); void BAND();
void BXOR(); void BXOR();
void URSH();
void SRSH(); void SRSH();
void ZLSH(); void ZLSH();
void CLSH(); void CLSH();

View File

@ -34,12 +34,16 @@ void Emulator::run(const std::vector<uint16_t>& program)
{ {
std::copy(program.begin(), program.end(), memory.begin()); std::copy(program.begin(), program.end(), memory.begin());
while (true) keep_running = true;
while (keep_running)
{ {
run_instruction(); run_instruction();
for (auto& device : devices) for (auto& device : devices)
{ {
device->update(); if (device->update())
{
keep_running = false;
}
} }
} }
} }
@ -69,10 +73,10 @@ uint16_t& Emulator::decode_operand(Operand operand)
return registers[operand.register_index]; return registers[operand.register_index];
case Operand::AddressingMode::IndirectAutoIncrement: case Operand::AddressingMode::IndirectAutoIncrement:
return memory[++registers[operand.register_index]]; return memory[registers[operand.register_index]++];
case Operand::AddressingMode::IndirectAutoDecrement: case Operand::AddressingMode::IndirectAutoDecrement:
return memory[--registers[operand.register_index]]; return memory[registers[operand.register_index]--];
case Operand::AddressingMode::Indirect: case Operand::AddressingMode::Indirect:
return memory[registers[operand.register_index]]; return memory[registers[operand.register_index]];
@ -151,7 +155,6 @@ void Emulator::run_instruction()
break; break;
case 5: case 5:
URSH();
break; break;
case 6: case 6:
@ -286,15 +289,6 @@ void Emulator::BXOR()
d = a ^ b; d = a ^ b;
} }
void Emulator::URSH()
{
uint16_t a = decode_operand(instruction & 0x000000FF);
uint16_t b = decode_operand((instruction & 0x0000FF00) >> 8);
uint16_t& d = decode_operand((instruction & 0x00FF0000) >> 16);
d = a >> b;
}
void Emulator::SRSH() void Emulator::SRSH()
{ {
uint16_t a = decode_operand(instruction & 0x000000FF); uint16_t a = decode_operand(instruction & 0x000000FF);

View File

@ -3,6 +3,8 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <SDL3/SDL.h>
namespace namespace
{ {
@ -34,7 +36,7 @@ public:
{ {
} }
void update() override bool update() override
{ {
uint16_t& value = memory(0); uint16_t& value = memory(0);
if (value != 0) if (value != 0)
@ -42,18 +44,88 @@ public:
std::cout << char(value); std::cout << char(value);
value = 0; value = 0;
} }
return false;
} }
}; };
class MemoryViewDevice : public foot::Device
{
public:
MemoryViewDevice(SDL_Renderer* render, uint8_t width, uint8_t height) :
Device(width * height / 2),
render(render),
texture(SDL_CreateTexture(render, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, width, height)),
width(width),
height(height)
{}
~MemoryViewDevice()
{
SDL_DestroyTexture(texture);
}
bool update() override
{
int pitch;
uint8_t* pixels;
if (!SDL_LockTexture(texture, nullptr, (void**)&pixels, &pitch))
{
std::cout << SDL_GetError() << std::endl;
}
int idx = 0;
for (int i = 0; i < width / 2; i++)
{
for (int j = 0; j < height; j++)
{
const uint16_t mem = memory(i + j * width / 2);
const uint8_t left = mem & 0xFF;
const uint8_t right = mem >> 8;
pixels[idx + 0] = left;
pixels[idx + 1] = left;
pixels[idx + 2] = left;
pixels[idx + 3] = right;
pixels[idx + 4] = right;
pixels[idx + 5] = right;
idx += 6;
}
}
SDL_UnlockTexture(texture);
SDL_RenderPresent(render);
SDL_Event event;
bool stop = false;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_EVENT_QUIT)
{
stop = true;
}
}
return stop;
}
private:
SDL_Renderer* render;
SDL_Texture* texture;
uint8_t width;
uint8_t height;
};
} }
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
foot::Emulator emu;
emu.map_device(std::make_unique<PrintDevice>());
if (argc > 1) if (argc > 1)
{ {
constexpr int WIDTH = 64;
constexpr int HEIGHT = 64;
std::ifstream file(argv[1], std::ios::binary); std::ifstream file(argv[1], std::ios::binary);
if (!file.is_open()) if (!file.is_open())
@ -62,7 +134,22 @@ int main(int argc, char** argv)
return 1; return 1;
} }
SDL_Window* window;
SDL_Renderer* render;
SDL_Init(SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer("foot emulator", WIDTH * 4, HEIGHT * 4, 0, &window, &render);
foot::Emulator emu;
emu.map_device(std::make_unique<PrintDevice>());
emu.map_device(std::make_unique<MemoryViewDevice>(render, WIDTH, HEIGHT));
emu.run(read_file(file)); emu.run(read_file(file));
SDL_DestroyRenderer(render);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
} }
else else
{ {

View File

@ -9,3 +9,11 @@ function(test name)
endfunction() endfunction()
test(cnst-instruction) test(cnst-instruction)
test(bwng-instruction)
test(arng-instruction)
test(long-instruction)
test(bwor-instruction)
test(band-instruction)
test(bxor-instruction)
test(srsh-instruction)
test(zlsh-instruction)

View File

@ -0,0 +1,18 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// ARNG
// dst - 0, Direct
// a - 0, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x01200220 });
if (!check(0xBA99, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -0,0 +1,23 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// CNST
// dst - 1, Direct
// imm - 0x2345
//
// BAND
// dst - 0, Direct
// a - 0, Direct
// b - 1, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x00212345, 0x03202021 });
if (!check(0x4567 & 0x2345, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -0,0 +1,18 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// BWNG
// dst - 0, Direct
// a - 0, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x01200120 });
if (!check(0xBA98, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -0,0 +1,23 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// CNST
// dst - 1, Direct
// imm - 0x2345
//
// BWOR
// dst - 0, Direct
// a - 0, Direct
// b - 1, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x00212345, 0x02202021 });
if (!check(0x4567 | 0x2345, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -0,0 +1,23 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// CNST
// dst - 1, Direct
// imm - 0x2345
//
// BXOR
// dst - 0, Direct
// a - 0, Direct
// b - 1, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x00212345, 0x04202021 });
if (!check(0x4567 ^ 0x2345, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -1,4 +1,3 @@
#include <foot-emulator.h>
#include "test-common.h" #include "test-common.h"
int main(int argc, char** argv) int main(int argc, char** argv)
@ -8,7 +7,7 @@ int main(int argc, char** argv)
// imm - 0xFFFF // imm - 0xFFFF
{ {
foot::Emulator emu = run_instruction(0x0020FFFF); foot::Emulator emu = run_instruction(0x0020FFFF);
if (emu.register_at(0) != 0xFFFF) { return 1; } if (!check(0xFFFF, emu.register_at(0))) { return 1; }
} }
// CNST // CNST
@ -20,7 +19,25 @@ int main(int argc, char** argv)
// imm = 0x7777 // imm = 0x7777
{ {
foot::Emulator emu = run_instructions({ 0x0020FFFF, 0x00807777 }); foot::Emulator emu = run_instructions({ 0x0020FFFF, 0x00807777 });
if (emu.memory_at(0xFFFF) != 0x7777) { return 1; } if (!check(0x7777, emu.memory_at(0xFFFF))) { return 1; }
}
// CNST
// dst - 30, Direct
// imm = 0x0010
//
// CNST
// dst 0, Indirect with Auto-Increment
// imm = 0x1234
{
bool failed = false;
foot::Emulator emu = run_instructions({ 0x003E0010, 0x10401234 });
for (int i = 0; i < 0x10; i++)
{
if (!check(0x1234, emu.memory_at(i))) { failed = true; }
}
if (failed) { return 1; }
} }
return 0; return 0;

View File

@ -0,0 +1,30 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// LONG
// dst - 0, Direct
// a - 0, Direct
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x01200320 });
if (!check(0x0000, emu.register_at(0))) { return 1; }
}
// CNST
// dst - 0, Direct
// imm - 0x0000
//
// LONG
// dst - 0, Direct
// a - 0, Direct
{
foot::Emulator emu = run_instructions({ 0x00200000, 0x01200320 });
if (!check(0x0001, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -0,0 +1,31 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// SRSH
// dst - 0, Direct
// a - 0, Direct
// b - 2, Immediate
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x06200220 });
if (!check(0x4567 >> 2, emu.register_at(0))) { return 1; }
}
// CNST
// dst - 0, Direct
// imm - 0x8000
//
// SRSH
// dst - 0, Direct
// a - 0, Direct
{
foot::Emulator emu = run_instructions({ 0x00208000, 0x06200220 });
if (!check((-0x8000) >> 2, emu.register_at(0))) { return 1; }
}
return 0;
}

View File

@ -1,5 +1,7 @@
#include <foot-emulator.h> #include <foot-emulator.h>
#include <iostream>
inline foot::Emulator run_instruction(uint32_t instruction) inline foot::Emulator run_instruction(uint32_t instruction)
{ {
foot::Emulator emu; foot::Emulator emu;
@ -27,3 +29,14 @@ inline foot::Emulator run_instructions(const std::vector<uint32_t>& instructions
return emu; return emu;
} }
inline bool check(uint16_t expected, uint16_t actual)
{
if (actual != expected)
{
std::cout << std::hex << "Expected " << expected << ", got " << actual << ".\n";
return false;
}
return true;
}

View File

@ -0,0 +1,19 @@
#include "test-common.h"
int main(int argc, char** argv)
{
// CNST
// dst - 0, Direct
// imm - 0x4567
//
// ZLSH
// dst - 0, Direct
// a - 0, Direct
// b - 2, Immediate
{
foot::Emulator emu = run_instructions({ 0x00204567, 0x07200220 });
if (!check(0x159C, emu.register_at(0))) { return 1; }
}
return 0;
}