diff --git a/README.md b/README.md index 891905e..222e91a 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,24 @@ To run an LC-3 object file: ./lc3vm/build/lc3vm path/to/your_program.obj [path/to/another_program.obj ...] ``` +## Disassembling Object Files + +The LC-3 VM can also disassemble `.obj` files, showing you the LC-3 assembly instructions corresponding to the machine code in the file. This is useful for inspecting programs or debugging. + +**How to use:** + +To disassemble one or more object files, use the `-d` or `--disassemble` flag followed by the path(s) to your `.obj` file(s): + +```bash +./lc3vm/build/lc3vm -d path/to/your_program.obj +``` + +Or for multiple files: + +```bash +./lc3vm/build/lc3vm --disassemble program1.obj program2.obj +``` + ## Running Tests To compile and run the unit tests (from within the `lc3vm` directory): diff --git a/lc3vm/Makefile b/lc3vm/Makefile index 166af19..261084f 100644 --- a/lc3vm/Makefile +++ b/lc3vm/Makefile @@ -6,28 +6,40 @@ BUILD_DIR = build TEST_DIR = tests GTEST_FLAGS = -lgtest -lgtest_main -pthread -VM_TEST_SRCS = src/ls3.cpp src/memory.cpp +VM_SRCS = src/ls3.cpp src/memory.cpp src/terminal_input.cpp +VM_TEST_SRCS = $(VM_SRCS) -TEST_MAIN_SRC = src/main.cpp +TEST_MAIN_OBJ = $(BUILD_DIR)/test_main.o +TEST_FILES = tests/test_initialization.cpp tests/test_opcode_execution.cpp tests/test_disassembly.cpp +TEST_OBJS = $(TEST_FILES:tests/%.cpp=$(BUILD_DIR)/tests/%.o) .PHONY: all clean test docs all: $(BUILD_DIR)/lc3vm -$(BUILD_DIR)/lc3vm: $(VM_TEST_SRCS) src/main.cpp +$(BUILD_DIR)/lc3vm: $(VM_SRCS) src/main.cpp mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) $(VM_TEST_SRCS) src/main.cpp -o $(BUILD_DIR)/lc3vm + $(CC) $(CFLAGS) $(VM_SRCS) src/main.cpp -o $(BUILD_DIR)/lc3vm test: $(BUILD_DIR)/test_runner $(BUILD_DIR)/test_runner -$(BUILD_DIR)/test_runner: $(TEST_DIR)/test_basic.cpp $(VM_TEST_SRCS) $(TEST_MAIN_SRC) +$(TEST_MAIN_OBJ): tests/test_main.cpp mkdir -p $(BUILD_DIR) - $(CC) $(CFLAGS) -Dmain=disabled_main $(TEST_DIR)/test_basic.cpp $(VM_TEST_SRCS) $(TEST_MAIN_SRC) -o $(BUILD_DIR)/test_runner $(GTEST_FLAGS) $(LDFLAGS_COVERAGE) + $(CC) $(CFLAGS) -c tests/test_main.cpp -o $(TEST_MAIN_OBJ) + +$(BUILD_DIR)/tests/%.o: tests/%.cpp + mkdir -p $(BUILD_DIR)/tests + $(CC) $(CFLAGS) -c $< -o $@ + +$(BUILD_DIR)/test_runner: $(TEST_MAIN_OBJ) $(TEST_OBJS) $(VM_SRCS) + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) $(TEST_MAIN_OBJ) $(TEST_OBJS) $(VM_SRCS) -o $(BUILD_DIR)/test_runner $(GTEST_FLAGS) $(LDFLAGS_COVERAGE) docs: @echo "Generating documentation with Doxygen..." @doxygen Doxyfile clean: + @echo "Cleaning build directory..." rm -rf $(BUILD_DIR) \ No newline at end of file diff --git a/lc3vm/include/lc3.hpp b/lc3vm/include/lc3.hpp index 2efe353..d97ff95 100644 --- a/lc3vm/include/lc3.hpp +++ b/lc3vm/include/lc3.hpp @@ -14,6 +14,15 @@ #include "opcodes.hpp" #include #include +#include + +/** + * @brief Represents a loaded code/data segment in memory. + */ +struct CodeSegment { + std::uint16_t start_address; ///< The starting memory address of the segment. + std::uint16_t size; ///< The size of the segment in words. +}; /** * @brief Represents the state of an LC-3 virtual machine. @@ -40,6 +49,7 @@ class LC3State { void update_flags(std::uint16_t r); bool running; ///< Flag indicating whether the LC-3 VM is currently running. + std::vector loaded_code_segments; ///< Stores info about loaded program segments. /** * @brief Table of function pointers for dispatching LC-3 opcodes. @@ -88,10 +98,11 @@ class LC3State { * @brief Disassembles the instruction at a given memory address. * (Currently not implemented) * @param address The memory address of the instruction to disassemble. + * @return A string representation of the disassembled instruction. */ - void disassemble(std::uint16_t address); + std::string disassemble(std::uint16_t address); /** - * @brief Disassembles all instructions in memory. + * @brief Disassembles all instructions in loaded memory segments. * (Currently not implemented) */ void disassemble_all(); diff --git a/lc3vm/include/terminal_input.hpp b/lc3vm/include/terminal_input.hpp new file mode 100644 index 0000000..945f784 --- /dev/null +++ b/lc3vm/include/terminal_input.hpp @@ -0,0 +1,28 @@ +#ifndef LC3_TERMINAL_INPUT_H +#define LC3_TERMINAL_INPUT_H + +/** + * @file terminal_input.hpp + * @brief Provides functions to control terminal input mode (raw/canonical). + */ + +/** + * @brief Enables raw input mode for the terminal. + * + * In raw mode: + * - Input is unbuffered (character by character). + * - Input is not echoed to the screen. + * - Special character processing (like Ctrl+C for SIGINT) might be altered, + * so signal handling needs to be robust. + * @throw std::runtime_error if enabling raw mode fails. + */ +void enable_raw_mode(); + +/** + * @brief Disables raw input mode, restoring original terminal settings. + * This function should be called before the program exits to ensure the + * terminal is left in a usable state. + */ +void disable_raw_mode(); + +#endif // LC3_TERMINAL_INPUT_H \ No newline at end of file diff --git a/lc3vm/src/ls3.cpp b/lc3vm/src/ls3.cpp index 0f7a820..d82f974 100644 --- a/lc3vm/src/ls3.cpp +++ b/lc3vm/src/ls3.cpp @@ -18,6 +18,9 @@ #include #include "traps.hpp" #include "flags.hpp" +#include +#include +#include /** * @brief Swaps the endianness of a 16-bit unsigned integer. @@ -132,9 +135,10 @@ void LC3State::ins(LC3State& state, std::uint16_t instr) { switch (instr & 0xFF) { case TRAP_GETC: { - char c_in; - std::cin.get(c_in); - state.reg[R_R0] = static_cast(c_in); + char c_in = 0; + if (read(STDIN_FILENO, &c_in, 1) == 1) { + state.reg[R_R0] = static_cast(c_in); + } } state.update_flags(R_R0); break; @@ -155,11 +159,13 @@ void LC3State::ins(LC3State& state, std::uint16_t instr) { } case TRAP_IN: { std::cout << "Enter a character: "; - char c_in_trap; - std::cin.get(c_in_trap); - std::cout.put(c_in_trap); std::cout.flush(); - state.reg[R_R0] = static_cast(c_in_trap); + char c_in_trap = 0; + if (read(STDIN_FILENO, &c_in_trap, 1) == 1) { + std::cout.put(c_in_trap); + std::cout.flush(); + state.reg[R_R0] = static_cast(c_in_trap); + } state.update_flags(R_R0); break; } @@ -235,6 +241,9 @@ void LC3State::load_image(const std::string &filename) { throw std::runtime_error("Image too large for memory at specified origin."); } } + if (words_actually_read > 0) { + loaded_code_segments.push_back({origin, static_cast(words_actually_read)}); + } } LC3State::LC3State() : memory(), reg{}, running(true) { @@ -279,11 +288,141 @@ void LC3State::update_flags(std::uint16_t r_idx) { } } -void LC3State::disassemble(std::uint16_t address) { - (void)address; - std::cout << "Disassembly not yet implemented." << std::endl; +std::string LC3State::disassemble(std::uint16_t address) { + std::uint16_t instr = memory.read(address); + std::uint16_t opcode = instr >> 12; + std::ostringstream oss; + + oss << "0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << address << ": "; + + switch (opcode) { + case OP_ADD: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::uint16_t sr1 = (instr >> 6) & 0x7; + oss << "ADD R" << dr << ", R" << sr1 << ", "; + if ((instr >> 5) & 0x1) { + std::int16_t imm5 = static_cast(sign_extend(instr & 0x1F, 5)); + oss << "#" << std::dec << imm5; + } else { + std::uint16_t sr2 = instr & 0x7; + oss << "R" << sr2; + } + break; + } + case OP_AND: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::uint16_t sr1 = (instr >> 6) & 0x7; + oss << "AND R" << dr << ", R" << sr1 << ", "; + if ((instr >> 5) & 0x1) { + std::int16_t imm5 = static_cast(sign_extend(instr & 0x1F, 5)); + oss << "#" << std::dec << imm5; + } else { + std::uint16_t sr2 = instr & 0x7; + oss << "R" << sr2; + } + break; + } + case OP_NOT: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::uint16_t sr = (instr >> 6) & 0x7; + oss << "NOT R" << dr << ", R" << sr; + break; + } + case OP_BR: { + std::uint16_t n = (instr >> 11) & 0x1; + std::uint16_t z = (instr >> 10) & 0x1; + std::uint16_t p = (instr >> 9) & 0x1; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "BR" << (n ? "n" : "") << (z ? "z" : "") << (p ? "p" : "") << " 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_JMP: { + std::uint16_t base_r = (instr >> 6) & 0x7; + if (base_r == 7) { + oss << "RET"; + } else { + oss << "JMP R" << base_r; + } + break; + } + case OP_JSR: { + oss << "JSR "; + if ((instr >> 11) & 1) { + std::int16_t pc_offset11 = static_cast(sign_extend(instr & 0x7FF, 11)); + oss << "0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset11); + } else { + std::uint16_t base_r = (instr >> 6) & 0x7; + oss << "R" << base_r; + } + break; + } + case OP_LD: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "LD R" << dr << ", 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_LDI: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "LDI R" << dr << ", 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_LDR: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::uint16_t base_r = (instr >> 6) & 0x7; + std::int16_t offset6 = static_cast(sign_extend(instr & 0x3F, 6)); + oss << "LDR R" << dr << ", R" << base_r << ", #" << std::dec << offset6; + break; + } + case OP_LEA: { + std::uint16_t dr = (instr >> 9) & 0x7; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "LEA R" << dr << ", 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_ST: { + std::uint16_t sr = (instr >> 9) & 0x7; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "ST R" << sr << ", 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_STI: { + std::uint16_t sr = (instr >> 9) & 0x7; + std::int16_t pc_offset9 = static_cast(sign_extend(instr & 0x1FF, 9)); + oss << "STI R" << sr << ", 0x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(4) << (address + 1 + pc_offset9); + break; + } + case OP_STR: { + std::uint16_t sr = (instr >> 9) & 0x7; + std::uint16_t base_r = (instr >> 6) & 0x7; + std::int16_t offset6 = static_cast(sign_extend(instr & 0x3F, 6)); + oss << "STR R" << sr << ", R" << base_r << ", #" << std::dec << offset6; + break; + } + case OP_TRAP: { + oss << "TRAP x" << std::nouppercase << std::hex << std::setfill('0') << std::setw(2) << (instr & 0xFF); + break; + } + case OP_RES: + case OP_RTI: + default: + oss << "BAD OPCODE"; + break; + } + return oss.str(); } void LC3State::disassemble_all() { - std::cout << "Disassembly not yet implemented." << std::endl; + if (loaded_code_segments.empty()) { + std::cout << "No program images loaded. Nothing to disassemble." << std::endl; + return; + } + + for (const auto& segment : loaded_code_segments) { + for (std::uint16_t i = 0; i < segment.size; ++i) { + std::uint16_t current_address = segment.start_address + i; + std::cout << disassemble(current_address) << std::endl; + } + } } \ No newline at end of file diff --git a/lc3vm/src/main.cpp b/lc3vm/src/main.cpp index 109be78..84f9606 100644 --- a/lc3vm/src/main.cpp +++ b/lc3vm/src/main.cpp @@ -10,6 +10,8 @@ #include #include #include "lc3.hpp" +#include "terminal_input.hpp" +#include /** * @brief Global pointer to the LC3State instance. @@ -29,6 +31,9 @@ void handle_sigint(int sig) { if (g_vm_ptr) { g_vm_ptr->request_halt(); } + disable_raw_mode(); + std::signal(sig, SIG_DFL); + std::raise(sig); } } @@ -45,13 +50,21 @@ void handle_sigint(int sig) { */ int main(int argc, const char* argv[]) { if (argc < 2) { - std::cerr << "Usage: " << argv[0] << " [image_file2] ..." << std::endl; + std::cerr << "Usage: " << argv[0] << " [-d|--disassemble] [image_file2] ..." << std::endl; return 1; } LC3State vm; g_vm_ptr = &vm; + try { + enable_raw_mode(); + std::atexit(disable_raw_mode); + } catch (const std::exception& e) { + std::cerr << "Terminal Setup Error: " << e.what() << std::endl; + return 1; + } + struct sigaction sa; sa.sa_handler = handle_sigint; sigemptyset(&sa.sa_mask); @@ -62,16 +75,36 @@ int main(int argc, const char* argv[]) { return 1; } + bool disassemble_mode = false; + int first_image_arg_index = 1; + + if (argc > 1) { + std::string first_arg = argv[1]; + if (first_arg == "-d" || first_arg == "--disassemble") { + disassemble_mode = true; + first_image_arg_index = 2; + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " [-d|--disassemble] [image_file2] ..." << std::endl; + std::cerr << "Error: At least one image file is required for disassembly." << std::endl; + g_vm_ptr = nullptr; + return 1; + } + } + } + try { - for (int i = 1; i < argc; ++i) { + for (int i = first_image_arg_index; i < argc; ++i) { std::string filename = argv[i]; - std::cout << "Loading image: " << filename << std::endl; vm.load_image(filename); } - std::cout << "Starting LC-3 VM..." << std::endl; - vm.run(); - std::cout << "LC-3 VM halted." << std::endl; + if (disassemble_mode) { + vm.disassemble_all(); + } else { + std::cout << "Starting LC-3 VM..." << std::endl; + vm.run(); + std::cout << "LC-3 VM halted." << std::endl; + } } catch (const std::exception& e) { std::cerr << "VM Runtime Error: " << e.what() << std::endl; diff --git a/lc3vm/src/terminal_input.cpp b/lc3vm/src/terminal_input.cpp new file mode 100644 index 0000000..728b9c1 --- /dev/null +++ b/lc3vm/src/terminal_input.cpp @@ -0,0 +1,41 @@ +#include "terminal_input.hpp" +#include +#include +#include +#include + +static struct termios original_termios; +static bool raw_mode_enabled = false; + +void enable_raw_mode() { + if (tcgetattr(STDIN_FILENO, &original_termios) == -1) { + perror("tcgetattr"); + throw std::runtime_error("Failed to get terminal attributes."); + } + + struct termios raw = original_termios; + + raw.c_iflag &= ~(BRKINT | INPCK | ISTRIP | IXON); + + raw.c_cflag |= (CS8); + + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + perror("tcsetattr"); + throw std::runtime_error("Failed to set terminal to raw mode."); + } + raw_mode_enabled = true; +} + +void disable_raw_mode() { + if (raw_mode_enabled) { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios) == -1) { + perror("disable_raw_mode: tcsetattr"); + } + raw_mode_enabled = false; + } +} \ No newline at end of file diff --git a/lc3vm/tests/test_basic.cpp b/lc3vm/tests/test_basic.cpp deleted file mode 100644 index c0447be..0000000 --- a/lc3vm/tests/test_basic.cpp +++ /dev/null @@ -1,308 +0,0 @@ -#include -#include "lc3.hpp" // Include the main VM header -#include "registers.hpp" // For R_PC, R_COND, R_R0 etc. -#include "flags.hpp" // For FL_ZRO - -// Helper to get register value if LC3State does not expose 'reg' directly publicly -// For now, assuming 'reg' is accessible for testing or we add a getter. -// Our LC3State.reg is private. We need a way to inspect registers for tests. -// Let's add a simple public getter in LC3State for tests for now. - -TEST(LC3VMTest, BasicInitialization) { - LC3State vm; - // PC should be initialized to 0x3000 - EXPECT_EQ(vm.get_register_value(R_PC), 0x3000); - // Condition code should be FL_ZRO - EXPECT_EQ(vm.get_register_value(R_COND), FL_ZRO); - - // General purpose registers R0-R7 should be 0 - for (int i = R_R0; i <= R_R7; ++i) { - EXPECT_EQ(vm.get_register_value(static_cast(i)), 0); - } -} - -// Placeholder for OP_ADD test - we'll need to write to memory -TEST(LC3VMTest, OP_ADD_Immediate) { - LC3State vm; - // Setup: R1 = 5, R2 will be destination - vm.set_register_value(R_R1, 5); - vm.set_register_value(R_R2, 0); // Clear destination - vm.set_register_value(R_PC, 0x3000); // Start PC - - // Instruction: ADD R2, R1, #10 (0001 010 001 10010 -> 0x14A2) - // R2 is DR (010), R1 is SR1 (001), imm_flag = 1, imm5 = 10 (01010) - // Opcode: 0001 (ADD) - // DR: 010 (R2) - // SR1: 001 (R1) - // Imm Flag: 1 - // Imm5: 01010 (10 decimal) - // instr = (OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | 10; - std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | 10; - vm.write_memory(0x3000, instr); - - vm.step(); // Execute the ADD instruction - - EXPECT_EQ(vm.get_register_value(R_R2), 15); // 5 + 10 = 15 - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); // Result is positive - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); // PC should increment -} - -TEST(LC3VMTest, OP_ADD_Register) { - LC3State vm; - // Setup: R1 = 5, R3 = 7, R2 will be destination - vm.set_register_value(R_R1, 5); - vm.set_register_value(R_R3, 7); - vm.set_register_value(R_R2, 0); // Clear destination - vm.set_register_value(R_PC, 0x3000); // Start PC - - // Instruction: ADD R2, R1, R3 (0001 010 001 000 011 -> 0x14C3) - // R2 is DR (010), R1 is SR1 (001), imm_flag = 0, SR2 = R3 (011) - // Opcode: 0001 (ADD) - // DR: 010 (R2) - // SR1: 001 (R1) - // Imm Flag: 0 - // Reserved: 00 - // SR2: 011 (R3) - // instr = (OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (0 << 5) | R_R3; - std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | R_R3; - vm.write_memory(0x3000, instr); - - vm.step(); // Execute the ADD instruction - - EXPECT_EQ(vm.get_register_value(R_R2), 12); // 5 + 7 = 12 - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); // Result is positive - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); // PC should increment -} - -TEST(LC3VMTest, OP_LD_LoadDirect) { - LC3State vm; - // Setup: PC = 0x3000. Memory[PC_after_fetch + offset(10)] = 123. R2 is destination. - // LD R2, LABEL_VAL (PCoffset9 = 10) - // PC_after_fetch = 0x3001. Effective Address = 0x3001 + 10 = 0x300B - vm.set_register_value(R_PC, 0x3000); - vm.write_memory(0x300B, 123); // Value to be loaded (Corrected address) - - // Instruction: LD R2, #10 (0010 010 000001010 -> 0x240A) - // R2 is DR (010), PCoffset9 = 10 - std::uint16_t instr = (Opcodes::OP_LD << 12) | (R_R2 << 9) | 10; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), 123); - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); // 123 is positive - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_ST_StoreDirect) { - LC3State vm; - // Setup: PC = 0x3000. R2 = 456. Store R2 to Memory[PC_after_fetch + offset(5)] - // ST R2, LABEL_STORE (PCoffset9 = 5) - // PC_after_fetch = 0x3001. Effective Address = 0x3001 + 5 = 0x3006 - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_R2, 456); - - // Instruction: ST R2, #5 (0011 010 000000101 -> 0x3405) - // R2 is SR (010), PCoffset9 = 5 - std::uint16_t instr = (Opcodes::OP_ST << 12) | (R_R2 << 9) | 5; - vm.write_memory(0x3000, instr); - - vm.step(); - - // Effective address calculated in OP_ST is state.reg[R_PC] (which is 0x3001) + offset (5) = 0x3006 - EXPECT_EQ(vm.read_memory(0x3001 + 5), 456); // Corrected expectation - - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); // PC just increments - // ST does not change flags -} - -TEST(LC3VMTest, OP_AND_Register) { - LC3State vm; - // Setup: R1 = 0b1100, R3 = 0b1010. R2 is DR. - vm.set_register_value(R_R1, 0xC); // 12 - vm.set_register_value(R_R3, 0xA); // 10 - vm.set_register_value(R_PC, 0x3000); - - // Instruction: AND R2, R1, R3 (0101 010 001 000 011 -> 0x54C3) - std::uint16_t instr = (Opcodes::OP_AND << 12) | (R_R2 << 9) | (R_R1 << 6) | R_R3; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), 0x8); // 0b1000 (12 & 10 = 8) - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_NOT) { - LC3State vm; - // Setup: R1 = 0xFF00. R2 is DR. - vm.set_register_value(R_R1, 0xFF00); - vm.set_register_value(R_PC, 0x3000); - - // Instruction: NOT R2, R1 (1001 010 001 111111 -> 0x94BF) - std::uint16_t instr = (Opcodes::OP_NOT << 12) | (R_R2 << 9) | (R_R1 << 6) | 0x3F; // last 6 bits are 1s - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), (std::uint16_t)~0xFF00); // 0x00FF - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_JMP) { - LC3State vm; - // Setup: R3 contains target address 0x4000 - vm.set_register_value(R_R3, 0x4000); - vm.set_register_value(R_PC, 0x3000); - - // Instruction: JMP R3 (1100 000 011 000000 -> 0xC0C0) - std::uint16_t instr = (Opcodes::OP_JMP << 12) | (R_R3 << 6); - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_PC), 0x4000); -} - -TEST(LC3VMTest, OP_LDI_LoadIndirect) { - LC3State vm; - // Setup: PC = 0x3000. R2 is DR. - // LDI R2, ADDR_PTR (PCoffset9 = 10) - // ADDR_PTR is at 0x3001 (PC after fetch) + 10 = 0x300B - // Memory[0x300B] contains 0x4000 (the effective address) - // Memory[0x4000] contains 789 (the value to be loaded) - vm.set_register_value(R_PC, 0x3000); - vm.write_memory(0x300B, 0x4000); // Pointer to effective address - vm.write_memory(0x4000, 789); // Value at effective address - - // Instruction: LDI R2, #10 (1010 010 000001010 -> 0xA40A) - std::uint16_t instr = (Opcodes::OP_LDI << 12) | (R_R2 << 9) | 10; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), 789); - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_STI_StoreIndirect) { - LC3State vm; - // Setup: PC = 0x3000. R2 = 987 (value to store). - // STI R2, ADDR_PTR (PCoffset9 = 7) - // ADDR_PTR is at 0x3001 (PC after fetch) + 7 = 0x3008 - // Memory[0x3008] contains 0x5000 (the effective address where R2 will be stored) - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_R2, 987); - vm.write_memory(0x3008, 0x5000); // Pointer to effective address - - // Instruction: STI R2, #7 (1011 010 000000111 -> 0xB407) - std::uint16_t instr = (Opcodes::OP_STI << 12) | (R_R2 << 9) | 7; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.read_memory(0x5000), 987); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); // PC just increments - // STI does not change flags -} - -TEST(LC3VMTest, OP_LDR_LoadBaseOffset) { - LC3State vm; - // Setup: PC = 0x3000. R1 (BaseR) = 0x4000. Memory[0x4000 + offset(5)] = 222. R2 is DR. - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_R1, 0x4000); // Base register - vm.write_memory(0x4000 + 5, 222); // Value at BaseR + offset - - // Instruction: LDR R2, R1, #5 (0110 010 001 000101 -> 0x6445) - // R2 is DR (010), R1 is BaseR (001), offset6 = 5 - std::uint16_t instr = (Opcodes::OP_LDR << 12) | (R_R2 << 9) | (R_R1 << 6) | 5; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), 222); - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_STR_StoreBaseOffset) { - LC3State vm; - // Setup: PC = 0x3000. R1 (BaseR) = 0x5000. R2 (SR) = 333. - // Store R2 to Memory[R1 + offset(3)] - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_R1, 0x5000); // Base register - vm.set_register_value(R_R2, 333); // Source register - - // Instruction: STR R2, R1, #3 (0111 010 001 000011 -> 0x7443) - // R2 is SR (010), R1 is BaseR (001), offset6 = 3 - std::uint16_t instr = (Opcodes::OP_STR << 12) | (R_R2 << 9) | (R_R1 << 6) | 3; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.read_memory(0x5000 + 3), 333); - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); - // STR does not change flags -} - -TEST(LC3VMTest, OP_LEA_LoadEffectiveAddress) { - LC3State vm; - // Setup: PC = 0x3000. R2 is DR. - // LEA R2, #7 (PCoffset9 = 7) - // Effective address = PC_after_fetch + 7 = 0x3001 + 7 = 0x3008 - vm.set_register_value(R_PC, 0x3000); - - // Instruction: LEA R2, #7 (1110 010 000000111 -> 0xE407) - std::uint16_t instr = (Opcodes::OP_LEA << 12) | (R_R2 << 9) | 7; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_R2), 0x3001 + 7); - EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); // Address 0x3008 is positive - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -TEST(LC3VMTest, OP_BR_BranchIfPositive) { - LC3State vm; - // Setup: PC = 0x3000. R_COND = FL_POS (1). - // BRp #10 (PCoffset9 = 10) - // Target PC = PC_after_fetch + 10 = 0x3001 + 10 = 0x300B - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_COND, FL_POS); - - // Instruction: BRp #10 (0000 001 000001010 -> 0x020A) - // nzp = 001 (Positive), PCoffset9 = 10 - std::uint16_t instr = (Opcodes::OP_BR << 12) | (FL_POS << 9) | 10; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001 + 10); - // BR does not change flags -} - -TEST(LC3VMTest, OP_BR_BranchNotTaken) { - LC3State vm; - // Setup: PC = 0x3000. R_COND = FL_ZRO (2). - // BRp #10 (PCoffset9 = 10) - // Condition (Positive) is not met, so PC should just increment. - vm.set_register_value(R_PC, 0x3000); - vm.set_register_value(R_COND, FL_ZRO); - - // Instruction: BRp #10 (0000 001 000001010 -> 0x020A) - std::uint16_t instr = (Opcodes::OP_BR << 12) | (FL_POS << 9) | 10; - vm.write_memory(0x3000, instr); - - vm.step(); - - EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); -} - -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/lc3vm/tests/test_disassembly.cpp b/lc3vm/tests/test_disassembly.cpp new file mode 100644 index 0000000..6e5780c --- /dev/null +++ b/lc3vm/tests/test_disassembly.cpp @@ -0,0 +1,133 @@ +#include +#include "lc3.hpp" +#include "opcodes.hpp" +#include "registers.hpp" +#include "flags.hpp" +#include +#include +#include + + +TEST(LC3DisassembleTest, ADD_Reg) { + LC3State vm; + std::uint16_t addr = 0x3000; + // ADD R1, R2, R3 (0001 001 010 000 011 => 0x1283) + vm.write_memory(addr, 0x1283); + EXPECT_EQ(vm.disassemble(addr), "0x3000: ADD R1, R2, R3"); +} + +TEST(LC3DisassembleTest, ADD_Imm) { + LC3State vm; + std::uint16_t addr = 0x3001; + // ADD R1, R2, #-1 (0001 001 010 1 11111 => 0x12BF) + vm.write_memory(addr, 0x12BF); + EXPECT_EQ(vm.disassemble(addr), "0x3001: ADD R1, R2, #-1"); + // ADD R3, R4, #5 (0001 011 100 1 00101 => 0x1725) + vm.write_memory(addr + 1, 0x1725); + EXPECT_EQ(vm.disassemble(addr + 1), "0x3002: ADD R3, R4, #5"); +} + +TEST(LC3DisassembleTest, AND_Reg) { + LC3State vm; + std::uint16_t addr = 0x3100; + // AND R5, R6, R7 (Op=0101 DR=101 SR1=110 Mode=0 MustBeZero=00 SR2=111 => 0101 101 110 000 111 => 0x5B87) + vm.write_memory(addr, 0x5B87); + EXPECT_EQ(vm.disassemble(addr), "0x3100: AND R5, R6, R7"); +} + +TEST(LC3DisassembleTest, AND_Imm) { + LC3State vm; + std::uint16_t addr = 0x3150; + // AND R0, R1, #0 (0101 000 001 1 00000 => 0x5060) + vm.write_memory(addr, 0x5060); + EXPECT_EQ(vm.disassemble(addr), "0x3150: AND R0, R1, #0"); + // AND R2, R3, #-16 (0101 010 011 1 10000 => 0x54F0) + vm.write_memory(addr + 1, 0x54F0); + EXPECT_EQ(vm.disassemble(addr + 1), "0x3151: AND R2, R3, #-16"); +} + +TEST(LC3DisassembleTest, NOT) { + LC3State vm; + std::uint16_t addr = 0x3200; + // NOT R3, R4 (1001 011 100 111111 => 0x973F) + vm.write_memory(addr, 0x973F); + EXPECT_EQ(vm.disassemble(addr), "0x3200: NOT R3, R4"); +} + +TEST(LC3DisassembleTest, BR) { + LC3State vm; + std::uint16_t addr = 0x3300; + // BRnzp 0x330A (PC = 0x3300, PCOffset9 = 9. Target = 0x3300+1+9 = 0x330A) + vm.write_memory(addr, 0x0E09); + EXPECT_EQ(vm.disassemble(addr), "0x3300: BRnzp 0x330a"); + // BRn (PC=0x3301, PCOffset9 = -10 Target = 0x3301+1-10 = 0x32F8) + vm.write_memory(addr + 1, 0x09F6); + EXPECT_EQ(vm.disassemble(addr+1), "0x3301: BRn 0x32f8"); +} + +TEST(LC3DisassembleTest, JMP_RET) { + LC3State vm; + std::uint16_t addr = 0x3400; + vm.write_memory(addr, 0xC0C0); // JMP R3 + EXPECT_EQ(vm.disassemble(addr), "0x3400: JMP R3"); + vm.write_memory(addr + 1, 0xC1C0); // RET (JMP R7) + EXPECT_EQ(vm.disassemble(addr + 1), "0x3401: RET"); +} + +TEST(LC3DisassembleTest, JSR_JSRR) { + LC3State vm; + std::uint16_t addr = 0x3500; + // JSR 0x358a. For PC=0x3500, Target=0x358a => PCOffset11 = 0x358a - 0x3500 - 1 = 0x89. + // Instruction: 0100 1 (JSR bit) 00010001001 (0x089) => 0x4889 + vm.write_memory(addr, 0x4889); + EXPECT_EQ(vm.disassemble(addr), "0x3500: JSR 0x358a"); + + // JSRR R5 (0100 0 00 101 000000 => 0x4140) + vm.write_memory(addr + 1, 0x4140); + EXPECT_EQ(vm.disassemble(addr + 1), "0x3501: JSR R5"); +} + +TEST(LC3DisassembleTest, LD_LDI_LDR_LEA) { + LC3State vm; + std::uint16_t addr = 0x3600; + vm.write_memory(addr, 0x200F); // LD R0, 0x3610 + EXPECT_EQ(vm.disassemble(addr), "0x3600: LD R0, 0x3610"); + vm.write_memory(addr + 1, 0xA206); // LDI R1, 0x3608 + EXPECT_EQ(vm.disassemble(addr + 1), "0x3601: LDI R1, 0x3608"); + vm.write_memory(addr + 2, 0x64C5); // LDR R2, R3, #5 + EXPECT_EQ(vm.disassemble(addr + 2), "0x3602: LDR R2, R3, #5"); + + // LEA R4, 0x35f0. For PC=0x3603, Target=0x35f0 => PCOffset9 = 0x35f0 - 0x3603 - 1 = -20 (0x1EC). + // Instruction: 1110 (LEA) 100 (R4) 111101100 (0x1EC) => 0xE9EC + vm.write_memory(addr + 3, 0xE9EC); + EXPECT_EQ(vm.disassemble(addr + 3), "0x3603: LEA R4, 0x35f0"); +} + +TEST(LC3DisassembleTest, ST_STI_STR) { + LC3State vm; + std::uint16_t addr = 0x3700; + vm.write_memory(addr, 0x3A1F); // ST R5, 0x3720 + EXPECT_EQ(vm.disassemble(addr), "0x3700: ST R5, 0x3720"); + vm.write_memory(addr + 1, 0xBDF0); // STI R6, 0x36f2 + EXPECT_EQ(vm.disassemble(addr + 1), "0x3701: STI R6, 0x36f2"); + vm.write_memory(addr + 2, 0x7E3E); // STR R7, R0, #-2 + EXPECT_EQ(vm.disassemble(addr + 2), "0x3702: STR R7, R0, #-2"); +} + +TEST(LC3DisassembleTest, TRAP) { + LC3State vm; + std::uint16_t addr = 0x3800; + vm.write_memory(addr, 0xF025); // TRAP x25 + EXPECT_EQ(vm.disassemble(addr), "0x3800: TRAP x25"); + vm.write_memory(addr+1, 0xF020); // TRAP x20 + EXPECT_EQ(vm.disassemble(addr+1), "0x3801: TRAP x20"); +} + +TEST(LC3DisassembleTest, BAD_OPCODE) { + LC3State vm; + std::uint16_t addr = 0x3900; + vm.write_memory(addr, 0x8000); // RTI (Opcode 8) + EXPECT_EQ(vm.disassemble(addr), "0x3900: BAD OPCODE"); + vm.write_memory(addr + 1, 0xD000); // RES (Opcode 13) + EXPECT_EQ(vm.disassemble(addr + 1), "0x3901: BAD OPCODE"); +} diff --git a/lc3vm/tests/test_initialization.cpp b/lc3vm/tests/test_initialization.cpp new file mode 100644 index 0000000..2d6f8e3 --- /dev/null +++ b/lc3vm/tests/test_initialization.cpp @@ -0,0 +1,14 @@ +#include +#include "lc3.hpp" +#include "registers.hpp" +#include "flags.hpp" + +TEST(LC3VMTest, BasicInitialization) { + LC3State vm; + EXPECT_EQ(vm.get_register_value(R_PC), 0x3000); + EXPECT_EQ(vm.get_register_value(R_COND), FL_ZRO); + + for (int i = R_R0; i <= R_R7; ++i) { + EXPECT_EQ(vm.get_register_value(static_cast(i)), 0); + } +} \ No newline at end of file diff --git a/lc3vm/tests/test_main.cpp b/lc3vm/tests/test_main.cpp new file mode 100644 index 0000000..9a1559a --- /dev/null +++ b/lc3vm/tests/test_main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/lc3vm/tests/test_opcode_execution.cpp b/lc3vm/tests/test_opcode_execution.cpp new file mode 100644 index 0000000..4070ba8 --- /dev/null +++ b/lc3vm/tests/test_opcode_execution.cpp @@ -0,0 +1,322 @@ +#include +#include "lc3.hpp" +#include "registers.hpp" +#include "flags.hpp" +#include "opcodes.hpp" + +TEST(LC3VMTest, OP_ADD_Immediate) +{ + LC3State vm; + vm.set_register_value(R_R1, 5); + vm.set_register_value(R_R2, 0); + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | 10; + vm.write_memory(0x3000, instr); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R2), 15); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_ADD_Register) +{ + LC3State vm; + vm.set_register_value(R_R1, 5); + vm.set_register_value(R_R3, 7); + vm.set_register_value(R_R2, 0); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | R_R3; + vm.write_memory(0x3000, instr); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R2), 12); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_LD_LoadDirect) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.write_memory(0x300B, 123); + std::uint16_t instr = (Opcodes::OP_LD << 12) | (R_R2 << 9) | 10; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), 123); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_ST_StoreDirect) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_R2, 456); + std::uint16_t instr = (Opcodes::OP_ST << 12) | (R_R2 << 9) | 5; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.read_memory(0x3001 + 5), 456); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_AND_Register) +{ + LC3State vm; + vm.set_register_value(R_R1, 0xC); + vm.set_register_value(R_R3, 0xA); + vm.set_register_value(R_PC, 0x3000); + std::uint16_t instr = (Opcodes::OP_AND << 12) | (R_R2 << 9) | (R_R1 << 6) | R_R3; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), 0x8); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_NOT) +{ + LC3State vm; + vm.set_register_value(R_R1, 0xFF00); + vm.set_register_value(R_PC, 0x3000); + std::uint16_t instr = (Opcodes::OP_NOT << 12) | (R_R2 << 9) | (R_R1 << 6) | 0x3F; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), (std::uint16_t)~0xFF00); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_JMP) +{ + LC3State vm; + vm.set_register_value(R_R3, 0x4000); + vm.set_register_value(R_PC, 0x3000); + std::uint16_t instr = (Opcodes::OP_JMP << 12) | (R_R3 << 6); + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_PC), 0x4000); +} + +TEST(LC3VMTest, OP_LDI_LoadIndirect) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.write_memory(0x300B, 0x4000); + vm.write_memory(0x4000, 789); + std::uint16_t instr = (Opcodes::OP_LDI << 12) | (R_R2 << 9) | 10; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), 789); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_STI_StoreIndirect) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_R2, 987); + vm.write_memory(0x3008, 0x5000); + std::uint16_t instr = (Opcodes::OP_STI << 12) | (R_R2 << 9) | 7; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.read_memory(0x5000), 987); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_LDR_LoadBaseOffset) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_R1, 0x4000); + vm.write_memory(0x4000 + 5, 222); + std::uint16_t instr = (Opcodes::OP_LDR << 12) | (R_R2 << 9) | (R_R1 << 6) | 5; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), 222); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_STR_StoreBaseOffset) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_R1, 0x5000); + vm.set_register_value(R_R2, 333); + std::uint16_t instr = (Opcodes::OP_STR << 12) | (R_R2 << 9) | (R_R1 << 6) | 3; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.read_memory(0x5000 + 3), 333); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_LEA_LoadEffectiveAddress) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + std::uint16_t instr = (Opcodes::OP_LEA << 12) | (R_R2 << 9) | 7; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_R2), 0x3001 + 7); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_BR_BranchIfPositive) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_COND, FL_POS); + std::uint16_t instr = (Opcodes::OP_BR << 12) | (FL_POS << 9) | 10; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001 + 10); +} + +TEST(LC3VMTest, OP_BR_BranchNotTaken) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_COND, FL_ZRO); + std::uint16_t instr = (Opcodes::OP_BR << 12) | (FL_POS << 9) | 10; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_BR_BranchIfZero) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_COND, FL_ZRO); + std::uint16_t instr = (Opcodes::OP_BR << 12) | (FL_ZRO << 9) | 10; + vm.write_memory(0x3000, instr); + vm.step(); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001 + 10); +} + +TEST(LC3VMTest, OP_ADD_Immediate_NegativeResult) +{ + LC3State vm; + vm.set_register_value(R_R1, 10); + vm.set_register_value(R_R2, 0); + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | (0b11111 & 0x1F); + std::uint16_t instr_neg = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | (0b11011 & 0x1F); + vm.write_memory(0x3000, instr_neg); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R2), 5); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_ADD_Immediate_NegativeResult_Corrected) +{ + LC3State vm; + vm.set_register_value(R_R1, 10); + vm.set_register_value(R_R2, 0); + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R2 << 9) | (R_R1 << 6) | (1 << 5) | (0b10100 & 0x1F); + vm.write_memory(0x3000, instr); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R2), 0xFFFE); + EXPECT_EQ(vm.get_register_value(R_COND), FL_NEG); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_ADD_Register_PositiveOverflow) +{ + LC3State vm; + vm.set_register_value(R_R1, 0x7000); + vm.set_register_value(R_R2, 0x7000); + vm.set_register_value(R_R3, 0); + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R3 << 9) | (R_R1 << 6) | R_R2; + vm.write_memory(0x3000, instr); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R3), 0xE000); + EXPECT_EQ(vm.get_register_value(R_COND), FL_NEG); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_ADD_Register_NegativeOverflow_WrapAround) +{ + LC3State vm; + vm.set_register_value(R_R1, 0xA000); + vm.set_register_value(R_R2, 0xA000); + vm.set_register_value(R_R3, 0); + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t instr = (Opcodes::OP_ADD << 12) | (R_R3 << 9) | (R_R1 << 6) | R_R2; + vm.write_memory(0x3000, instr); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R3), 0x4000); + EXPECT_EQ(vm.get_register_value(R_COND), FL_POS); + EXPECT_EQ(vm.get_register_value(R_PC), 0x3001); +} + +TEST(LC3VMTest, OP_LD_InvalidAddress) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + vm.set_register_value(R_PC, 0xFFFE); + std::uint16_t instr_wrap = (Opcodes::OP_LD << 12) | (R_R0 << 9) | (3 & 0x1FF); + vm.write_memory(0xFFFE, instr_wrap); + vm.write_memory(0x0002, 0xABCD); + + vm.step(); + + EXPECT_EQ(vm.get_register_value(R_R0), 0xABCD); + EXPECT_EQ(vm.get_register_value(R_PC), 0xFFFF); +} + +TEST(LC3VMTest, InvalidOpcodeExecution) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t invalid_instr = (Opcodes::OP_RTI << 12); + vm.write_memory(0x3000, invalid_instr); + std::uint16_t illegal_opcode = (0x8 << 12); + vm.write_memory(0x3000, illegal_opcode); + + std::uint16_t trap_undefined = (Opcodes::OP_TRAP << 12) | 0xFF; + vm.write_memory(0x3000, trap_undefined); +} +TEST(LC3VMTest, IllegalOpcodeExecution_DoesNotIncrementPC) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t illegal_opcode = (0x8 << 12); + vm.write_memory(0x3000, illegal_opcode); + + EXPECT_THROW(vm.step(), std::runtime_error); +} + +TEST(LC3VMTest, UndefinedTrapVector_DoesNotIncrementPC) +{ + LC3State vm; + vm.set_register_value(R_PC, 0x3000); + + std::uint16_t trap_undefined = (Opcodes::OP_TRAP << 12) | 0xFF; + vm.write_memory(0x3000, trap_undefined); + + EXPECT_THROW(vm.step(), std::runtime_error); +}