diff --git a/cpu-emulator/build/main.o b/cpu-emulator/build/main.o index d906591..de14f15 100644 Binary files a/cpu-emulator/build/main.o and b/cpu-emulator/build/main.o differ diff --git a/cpu-emulator/convert.py b/cpu-emulator/convert.py new file mode 100644 index 0000000..24815d4 --- /dev/null +++ b/cpu-emulator/convert.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import os +import struct + +def run_command(cmd_list, error_msg): + try: + subprocess.run(cmd_list, check=True) + except subprocess.CalledProcessError as e: + print(f"{error_msg}: {e}", file=sys.stderr) + sys.exit(1) + +def compile_c_source(source_file, elf_file): + # Compile the source file with no standard library. + cmd = [ + "riscv64-unknown-elf-gcc", + "-nostdlib", + "-static", + "-O2", + "-o", elf_file, + source_file + ] + print("Compiling source file...") + run_command(cmd, "Compilation failed") + +def convert_elf_to_bin(elf_file, bin_file): + # Convert the compiled ELF to a raw binary. + cmd = [ + "riscv64-unknown-elf-objcopy", + "-O", "binary", + elf_file, + bin_file + ] + print("Converting ELF to raw binary...") + run_command(cmd, "Objcopy conversion failed") + +def generate_cpp_vector(bin_file, cpp_file, vector_name="program_data"): + + # Read the binary file. + print("Reading raw binary...") + with open(bin_file, "rb") as bf: + data = bf.read() + + # Group the binary into 32-bit words (assuming little-endian) + words = [] + for i in range(0, len(data), 4): + chunk = data[i:i+4] + # If the last chunk is not 4 bytes, pad with zeros. + if len(chunk) < 4: + chunk = chunk.ljust(4, b'\x00') + # Unpack little-endian unsigned int. + word = struct.unpack("\n") + cf.write("#include \n\n") + cf.write(f"std::vector {vector_name} = {{\n") + + # Format: 8 words per line. + for i, word in enumerate(words): + # Print in hex with 0x... formatting. + cf.write(f" 0x{word:08X}, ") + if (i + 1) % 8 == 0: + cf.write("\n") + cf.write("\n};\n") + print(f"C++ file '{cpp_file}' generated successfully.") + +def main(): + if len(sys.argv) < 2: + print("Usage: ./build_and_generate.py []", file=sys.stderr) + sys.exit(1) + + source_file = sys.argv[1] + # Default names for intermediate and output files. + elf_file = "program.elf" + bin_file = "program.bin" + cpp_file = sys.argv[2] if len(sys.argv) >= 3 else "program_data.cpp" + + # Check that the source file exists. + if not os.path.exists(source_file): + print(f"Source file '{source_file}' does not exist.", file=sys.stderr) + sys.exit(1) + + compile_c_source(source_file, elf_file) + convert_elf_to_bin(elf_file, bin_file) + generate_cpp_vector(bin_file, cpp_file) + + # Optionally, clean up the intermediate files. + os.remove(elf_file) + os.remove(bin_file) + +if __name__ == "__main__": + main() diff --git a/cpu-emulator/emulator.exe b/cpu-emulator/emulator.exe index 138b33e..86fcc27 100644 Binary files a/cpu-emulator/emulator.exe and b/cpu-emulator/emulator.exe differ diff --git a/cpu-emulator/imgui.ini b/cpu-emulator/imgui.ini index 156927c..9fb2471 100644 --- a/cpu-emulator/imgui.ini +++ b/cpu-emulator/imgui.ini @@ -18,3 +18,28 @@ Pos=764,282 Size=1010,331 Collapsed=0 +[Window][Debugger] +Pos=60,60 +Size=158,598 +Collapsed=0 + +[Window][Memory Viewer] +Pos=60,60 +Size=1828,340 +Collapsed=0 + +[Window][Disassembly] +Pos=395,41 +Size=205,201 +Collapsed=0 + +[Window][CPU Control] +Pos=18,404 +Size=1384,715 +Collapsed=0 + +[Window][Terminal] +Pos=1400,430 +Size=487,502 +Collapsed=0 + diff --git a/cpu-emulator/main.cpp b/cpu-emulator/main.cpp index b0da34f..b654e1f 100644 --- a/cpu-emulator/main.cpp +++ b/cpu-emulator/main.cpp @@ -1,282 +1,599 @@ -#include -#include +// main.cpp +#include #include #include -#include -#include #include -#include +#include +#include +#include +#include + +// OpenGL/GLFW and GLEW headers. +#include +#include + +// ImGui headers. #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" -#include -#include "imgui_memory_editor.h" // Add this header for the Memory Editor -#define SCREEN_WIDTH 128 -#define SCREEN_HEIGHT 64 -#define SCREEN_PIXELS (SCREEN_WIDTH * SCREEN_HEIGHT) -#define SCREEN_MEM_START 0x1000 -#define SCREEN_MEM_SIZE (SCREEN_PIXELS * 3) // RGB888 +#include "out.h" -// ========== CPU ========== -class CPU { + + + + +// -------------------------------------------------------------------------------- +// CPU Flags structure (for debugging and visualization) +// -------------------------------------------------------------------------------- +struct CPUFlags { + bool zero; + bool negative; + bool carry; + bool overflow; +}; + +// -------------------------------------------------------------------------------- +// RISC-V Emulator Class. +// This example implements a subset of the RV32I instructions plus ECALL for +// basic system calls that print data to a virtual terminal. +class RiscVEmulator { public: - uint8_t A = 0; - uint8_t CMPF = 0; - uint16_t PC = 0; - bool running = true; - std::vector memory; + // 32 general-purpose registers and the program counter. + uint32_t registers[32]; + uint32_t pc; + // Memory implemented as an array of 32-bit words. + std::vector memory; + // CPU flags for arithmetic operations. + CPUFlags flags; + // A flag to halt execution in case of error. + bool halted; + // A buffer to collect terminal output (our “screen”). + std::string terminalOutput; - CPU() : memory(65536, 0) {} + // Constructor: allocate memory (memSize in words) and reset the CPU. + RiscVEmulator(size_t memSize) + : pc(0), memory(memSize, 0), halted(false) + { + reset(); + } - void loadProgram(const std::vector& program) { - std::fill(memory.begin(), memory.end(), 0); - for (size_t i = 0; i < program.size(); i++) + // Reset CPU state and (optionally) memory. + void reset() { + pc = 0; + std::memset(registers, 0, sizeof(registers)); + flags = { false, false, false, false }; + halted = false; + terminalOutput.clear(); + } + + // Load a program (vector of 32-bit instructions) into memory. + void loadProgram(const std::vector& program) { + for (size_t i = 0; i < program.size() && i < memory.size(); i++) { memory[i] = program[i]; - PC = 0; - A = 0; - CMPF = 0; - running = true; + } } + // Helper to update flags on addition. + void updateFlagsForAddition(uint32_t op1, uint32_t op2, uint32_t result) { + flags.zero = (result == 0); + flags.negative = (static_cast(result) < 0); + flags.carry = (result < op1); + bool sign1 = (static_cast(op1) < 0); + bool sign2 = (static_cast(op2) < 0); + bool signr = (static_cast(result) < 0); + flags.overflow = ((sign1 == sign2) && (signr != sign1)); + } + + // Helper to update flags on subtraction. + void updateFlagsForSubtraction(uint32_t op1, uint32_t op2, uint32_t result) { + flags.zero = (result == 0); + flags.negative = (static_cast(result) < 0); + flags.carry = (op1 < op2); + bool sign1 = (static_cast(op1) < 0); + bool sign2 = (static_cast(op2) < 0); + bool signr = (static_cast(result) < 0); + flags.overflow = ((sign1 != sign2) && (signr != sign1)); + } + + // Helper: Read a null-terminated string from memory starting at the given address. + // This function assumes that the string is stored in little-endian order. + std::string readString(uint32_t address) { + std::string result; + while (true) { + if (address / 4 >= memory.size()) + break; + uint32_t word = memory[address / 4]; + // Extract 4 bytes from the word. + for (int i = 0; i < 4; i++) { + char ch = (char)((word >> (i * 8)) & 0xFF); + if (ch == '\0') return result; + result.push_back(ch); + } + address += 4; + } + return result; + } + + // Execute one instruction (fetch-decode-execute). void step() { - uint8_t opcode = memory[PC++]; - switch (opcode) { - case 0x01: A = memory[PC++]; break; // LDA #val - case 0x02: A += memory[PC++]; break; // ADD #val - case 0x03: memory[memory[PC++]] = A; break; // STA addr - case 0x04: A = memory[memory[PC++]]; break; // LDM addr - case 0x05: { - uint8_t addr = memory[PC++]; - PC = addr; - break; - } - case 0x06: { - uint8_t addr = memory[PC++]; - if (A == 0) PC = addr; - break; - } - case 0x07: running = false; break; // HLT - case 0x08: A -= memory[PC++]; break; // SUB #val - case 0x09: { - uint8_t addr = memory[PC++]; - if (A != 0) PC = addr; - break; - } - case 0x0A: CMPF = A == memory[PC++]; break; // CMP #val - case 0x0B: break; // NOP - - // Flow control - case 0x0C: { // BEQ - uint8_t addr = memory[PC++]; - if (A == 0) PC = addr; - break; - } - case 0x0D: { // BNE - uint8_t addr = memory[PC++]; - if (A != 0) PC = addr; - break; - } - case 0x0E: { // BMI (A & 0x80 != 0) - uint8_t addr = memory[PC++]; - if (A & 0x80) PC = addr; - break; - } - case 0x0F: { // BPL (A & 0x80 == 0) - uint8_t addr = memory[PC++]; - if (!(A & 0x80)) PC = addr; - break; - } - case 0x10: { // BRA (Unconditional branch) - uint8_t addr = memory[PC++]; - PC = addr; - break; - } - case 0x11: { // CMP #val - uint8_t value = memory[PC++]; - CMPF = A == value; - break; - } - case 0x12: { // BCC (Branch if carry clear) - uint8_t addr = memory[PC++]; - if (!(A & 0x01)) PC = addr; // Using the least significant bit of A for carry flag - break; - } - case 0x13: { // BCS (Branch if carry set) - uint8_t addr = memory[PC++]; - if (A & 0x01) PC = addr; - break; - } - - default: running = false; break; + if (halted) return; + if (pc % 4 != 0 || (pc / 4) >= memory.size()) { + halted = true; + return; } - } - - void run(int steps = 1) { - while (running && steps-- > 0) step(); - } - - std::string getCurrentInstruction() { - uint8_t opcode = memory[PC]; - char buffer[64]; + uint32_t inst = memory[pc / 4]; + uint32_t opcode = inst & 0x7F; switch (opcode) { - case 0x01: sprintf(buffer, "LDA 0x%02X", memory[PC + 1]); break; - case 0x02: sprintf(buffer, "ADD 0x%02X", memory[PC + 1]); break; - case 0x03: sprintf(buffer, "STA 0x%02X", memory[PC + 1]); break; - case 0x04: sprintf(buffer, "LDM 0x%02X", memory[PC + 1]); break; - case 0x05: sprintf(buffer, "JMP 0x%02X", memory[PC + 1]); break; - case 0x06: sprintf(buffer, "JEZ 0x%02X", memory[PC + 1]); break; - case 0x07: sprintf(buffer, "HLT"); break; - case 0x08: sprintf(buffer, "SUB 0x%02X", memory[PC + 1]); break; - case 0x09: sprintf(buffer, "JNZ 0x%02X", memory[PC + 1]); break; - case 0x0A: sprintf(buffer, "CMP 0x%02X", memory[PC + 1]); break; - case 0x0B: sprintf(buffer, "NOP"); break; - case 0x0C: sprintf(buffer, "BEQ 0x%02X", memory[PC + 1]); break; - case 0x0D: sprintf(buffer, "BNE 0x%02X", memory[PC + 1]); break; - case 0x0E: sprintf(buffer, "BMI 0x%02X", memory[PC + 1]); break; - case 0x0F: sprintf(buffer, "BPL 0x%02X", memory[PC + 1]); break; - case 0x10: sprintf(buffer, "BRA 0x%02X", memory[PC + 1]); break; - case 0x11: sprintf(buffer, "CMP 0x%02X", memory[PC + 1]); break; - case 0x12: sprintf(buffer, "BCC 0x%02X", memory[PC + 1]); break; - case 0x13: sprintf(buffer, "BCS 0x%02X", memory[PC + 1]); break; - default: sprintf(buffer, "??? 0x%02X", opcode); break; + + // R-type instructions. + case 0x33: { + uint32_t rd = (inst >> 7) & 0x1F; + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t rs2 = (inst >> 20) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + uint32_t funct7 = (inst >> 25) & 0x7F; + switch (funct3) { + case 0x0: + if (funct7 == 0x00) { // ADD + uint32_t res = registers[rs1] + registers[rs2]; + registers[rd] = res; + updateFlagsForAddition(registers[rs1], registers[rs2], res); + } else if (funct7 == 0x20) { // SUB + uint32_t res = registers[rs1] - registers[rs2]; + registers[rd] = res; + updateFlagsForSubtraction(registers[rs1], registers[rs2], res); + } + break; + case 0x1: // SLL + registers[rd] = registers[rs1] << (registers[rs2] & 0x1F); + break; + case 0x2: // SLT + registers[rd] = ((int32_t)registers[rs1] < (int32_t)registers[rs2]) ? 1 : 0; + break; + case 0x3: // SLTU + registers[rd] = (registers[rs1] < registers[rs2]) ? 1 : 0; + break; + case 0x4: // XOR + registers[rd] = registers[rs1] ^ registers[rs2]; + break; + case 0x5: + if (funct7 == 0x00) { // SRL + registers[rd] = registers[rs1] >> (registers[rs2] & 0x1F); + } else if (funct7 == 0x20) { // SRA + registers[rd] = ((int32_t)registers[rs1]) >> (registers[rs2] & 0x1F); + } + break; + case 0x6: // OR + registers[rd] = registers[rs1] | registers[rs2]; + break; + case 0x7: // AND + registers[rd] = registers[rs1] & registers[rs2]; + break; + } + pc += 4; + break; } - return std::string(buffer); + // I-type arithmetic instructions. + case 0x13: { + uint32_t rd = (inst >> 7) & 0x1F; + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + int32_t imm = ((int32_t)inst) >> 20; + switch (funct3) { + case 0x0: { // ADDI + uint32_t res = registers[rs1] + imm; + registers[rd] = res; + updateFlagsForAddition(registers[rs1], imm, res); + break; + } + case 0x2: // SLTI + registers[rd] = (((int32_t)registers[rs1]) < imm) ? 1 : 0; + break; + case 0x3: // SLTIU + registers[rd] = (registers[rs1] < (uint32_t)imm) ? 1 : 0; + break; + case 0x4: // XORI + registers[rd] = registers[rs1] ^ imm; + break; + case 0x6: // ORI + registers[rd] = registers[rs1] | imm; + break; + case 0x7: // ANDI + registers[rd] = registers[rs1] & imm; + break; + case 0x1: { // SLLI + uint32_t shamt = imm & 0x1F; + registers[rd] = registers[rs1] << shamt; + break; + } + case 0x5: { + uint32_t shamt = imm & 0x1F; + uint32_t imm_high = (inst >> 25) & 0x7F; + if (imm_high == 0x00) { // SRLI + registers[rd] = registers[rs1] >> shamt; + } else if (imm_high == 0x20) { // SRAI + registers[rd] = ((int32_t)registers[rs1]) >> shamt; + } + break; + } + } + pc += 4; + break; + } + + // Branch instructions (B-type). + case 0x63: { + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t rs2 = (inst >> 20) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + int32_t imm = (((inst >> 31) & 0x1) << 12) | + (((inst >> 25) & 0x3F) << 5) | + (((inst >> 8) & 0xF) << 1) | + (((inst >> 7) & 0x1) << 11); + if (imm & (1 << 12)) + imm |= 0xFFFFE000; + switch (funct3) { + case 0x0: // BEQ + pc = (registers[rs1] == registers[rs2]) ? (pc + imm) : (pc + 4); + break; + case 0x1: // BNE + pc = (registers[rs1] != registers[rs2]) ? (pc + imm) : (pc + 4); + break; + case 0x4: // BLT + pc = (((int32_t)registers[rs1]) < ((int32_t)registers[rs2])) ? (pc + imm) : (pc + 4); + break; + case 0x5: // BGE + pc = (((int32_t)registers[rs1]) >= ((int32_t)registers[rs2])) ? (pc + imm) : (pc + 4); + break; + case 0x6: // BLTU + pc = (registers[rs1] < registers[rs2]) ? (pc + imm) : (pc + 4); + break; + case 0x7: // BGEU + pc = (registers[rs1] >= registers[rs2]) ? (pc + imm) : (pc + 4); + break; + } + break; + } + + // JAL: J-type jump. + case 0x6F: { + uint32_t rd = (inst >> 7) & 0x1F; + int32_t imm = (((inst >> 31) & 0x1) << 20) | + (((inst >> 21) & 0x3FF) << 1) | + (((inst >> 20) & 0x1) << 11) | + (((inst >> 12) & 0xFF) << 12); + if (imm & (1 << 20)) + imm |= 0xFFF00000; + registers[rd] = pc + 4; + pc += imm; + break; + } + + // JALR: I-type jump. + case 0x67: { + uint32_t rd = (inst >> 7) & 0x1F; + uint32_t rs1 = (inst >> 15) & 0x1F; + int32_t imm = ((int32_t)inst) >> 20; + uint32_t temp = pc + 4; + pc = (registers[rs1] + imm) & ~1; + registers[rd] = temp; + break; + } + + // LUI: U-type. + case 0x37: { + uint32_t rd = (inst >> 7) & 0x1F; + registers[rd] = inst & 0xFFFFF000; + pc += 4; + break; + } + + // AUIPC: U-type. + case 0x17: { + uint32_t rd = (inst >> 7) & 0x1F; + registers[rd] = pc + (inst & 0xFFFFF000); + pc += 4; + break; + } + + // Load instructions (LW only). + case 0x03: { + uint32_t rd = (inst >> 7) & 0x1F; + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + int32_t imm = ((int32_t)inst) >> 20; + if (funct3 == 0x2) { // LW + uint32_t addr = registers[rs1] + imm; + if (addr % 4 == 0 && (addr / 4) < memory.size()) + registers[rd] = memory[addr / 4]; + } + pc += 4; + break; + } + + // Store instructions (SW only). + case 0x23: { + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t rs2 = (inst >> 20) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + int32_t imm = (((inst >> 25) & 0x7F) << 5) | ((inst >> 7) & 0x1F); + if (imm & 0x800) imm |= 0xFFFFF000; + if (funct3 == 0x2) { // SW + uint32_t addr = registers[rs1] + imm; + if (addr % 4 == 0 && (addr / 4) < memory.size()) + memory[addr / 4] = registers[rs2]; + } + pc += 4; + break; + } + + // ECALL: Opcode 0x73 (environment call). + // This branch simulates interrupts/system calls to “print” data to our terminal. + case 0x73: { + // For ECALL, we follow a simple convention: + // - Register x17 (a7) holds the system call number. + // - Register x10 (a0) holds the argument. + // Conventions used below: + // 1: Print integer (from x10). + // 2: Print character (the low-order 8 bits of x10). + // 3: Print string (address pointer stored in x10). + uint32_t funct3 = (inst >> 12) & 0x7; + if (funct3 == 0) { + uint32_t syscall = registers[17]; // a7 + switch (syscall) { + case 1: { // Print integer. + int value = (int) registers[10]; // a0 + terminalOutput += std::to_string(value) + "\n"; + break; + } + case 2: { // Print character. + char ch = (char)(registers[10] & 0xFF); + terminalOutput.push_back(ch); + break; + } + case 3: { // Print string (pointer in a0). + std::string str = readString(registers[10]); + terminalOutput += str; + break; + } + default: + terminalOutput += "Unknown ECALL: " + std::to_string(syscall) + "\n"; + break; + } + } + pc += 4; + break; + } + + default: + // Unknown opcode – halt the CPU. + halted = true; + break; + } + + // Enforce that register x0 is always 0. + registers[0] = 0; + } + + // Disassemble an instruction at the given address. + std::string disassembleInstruction(uint32_t address) { + if (address % 4 != 0 || (address / 4) >= memory.size()) + return "Invalid address"; + uint32_t inst = memory[address / 4]; + uint32_t opcode = inst & 0x7F; + std::stringstream ss; + ss << "0x" << std::hex << inst << std::dec << " "; + switch (opcode) { + case 0x33: { // R-type + uint32_t rd = (inst >> 7) & 0x1F; + uint32_t rs1 = (inst >> 15) & 0x1F; + uint32_t rs2 = (inst >> 20) & 0x1F; + uint32_t funct3 = (inst >> 12) & 0x7; + uint32_t funct7 = (inst >> 25) & 0x7F; + if (funct3 == 0x0 && funct7 == 0x00) + ss << "ADD x" << rd << ", x" << rs1 << ", x" << rs2; + else if (funct3 == 0x0 && funct7 == 0x20) + ss << "SUB x" << rd << ", x" << rs1 << ", x" << rs2; + else + ss << "R-type (0x" << std::hex << inst << ")"; + break; + } + case 0x13: + ss << "ADDI (or other I-type)"; + break; + case 0x63: + ss << "Branch/Compare"; + break; + case 0x6F: + ss << "JAL"; + break; + case 0x67: + ss << "JALR"; + break; + case 0x37: + ss << "LUI"; + break; + case 0x17: + ss << "AUIPC"; + break; + case 0x03: + ss << "LW"; + break; + case 0x23: + ss << "SW"; + break; + case 0x73: + ss << "ECALL"; + break; + default: + ss << "Unknown opcode"; + break; + } + return ss.str(); } }; -// ========== OpenGL Pixel Renderer ========== -void renderScreenToBackground(uint8_t* memory) { - ImGuiIO& io = ImGui::GetIO(); - ImDrawList* bg = ImGui::GetBackgroundDrawList(); - float pixelSize = io.DisplaySize.x / SCREEN_WIDTH; - - for (int y = 0; y < SCREEN_HEIGHT; ++y) { - for (int x = 0; x < SCREEN_WIDTH; ++x) { - int index = SCREEN_MEM_START + (y * SCREEN_WIDTH + x) * 3; - uint8_t r = memory[index + 0]; - uint8_t g = memory[index + 1]; - uint8_t b = memory[index + 2]; - ImVec2 tl(x * pixelSize, y * pixelSize); - ImVec2 br(tl.x + pixelSize, tl.y + pixelSize); - bg->AddRectFilled(tl, br, IM_COL32(r, g, b, 255)); - } +// -------------------------------------------------------------------------------- +// Main: OpenGL/GLFW + ImGui Debugger and Terminal UI. +// -------------------------------------------------------------------------------- +int main(int, char**) { + // Initialize GLFW. + if (!glfwInit()) { + fprintf(stderr, "Failed to initialize GLFW\n"); + return 1; } -} - -// ========== Main ========== -int main() { - if (!glfwInit()) return -1; - GLFWwindow* window = glfwCreateWindow(1024, 640, "Rainbow CPU Emulator", NULL, NULL); + const char* glsl_version = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + GLFWwindow* window = glfwCreateWindow(1280, 720, "RISC-V Emulator with Terminal", NULL, NULL); + if (window == nullptr) + return 1; glfwMakeContextCurrent(window); glfwSwapInterval(1); + // Initialize ImGui. IMGUI_CHECKVERSION(); ImGui::CreateContext(); - ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init("#version 130"); + ImGuiIO& io = ImGui::GetIO(); (void)io; ImGui::StyleColorsDark(); + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init(glsl_version); - CPU cpu; - std::vector rainbow = { - 0x01, 0xFF, // LDA #0xFF - 0x03, 0x00, // STA $00 (R) - 0x01, 0x00, - 0x03, 0x01, // G - 0x01, 0x00, - 0x03, 0x02, // B - 0x01, 0x00, - 0x03, 0x10, // pixelIndex = 0 + // Create the emulator instance with 1024 words of memory. + RiscVEmulator emulator(1024); - // loop: - 0x04, 0x00, 0x03, 0x20, // load R -> $20 - 0x04, 0x01, 0x03, 0x21, // G -> $21 - 0x04, 0x02, 0x03, 0x22, // B -> $22 - // Write to screen: mem[0x1000 + pixelIndex * 3] - 0x04, 0x10, // LDM pixelIndex - 0x02, 0x00, // ADD #0 (A = index) - 0x03, 0x11, // Store to $11 (low index) + emulator.loadProgram(program_data); - // simulate writing to screen: here you can inject into actual screen mem if you simulate 16-bit + // Debugger control flags. + bool running = false; + double lastStepTime = glfwGetTime(); + const double stepInterval = 0.1; // Auto-step every 0.1 sec in "running" mode. - 0x04, 0x00, 0x03, 0x00, // Rotate R -> temp - 0x04, 0x01, 0x03, 0x00, - 0x04, 0x02, 0x03, 0x01, - 0x04, 0x00, 0x03, 0x02, - - 0x02, 0x01, // ADD #1 (pixelIndex++) - 0x03, 0x10, // STA pixelIndex - - 0x05, 0x10 // JMP to loop start - }; - - cpu.loadProgram(rainbow); - - static MemoryEditor mem_edit_1; -// static char data[0x10000]; -// size_t data_size = 0x10000; - - // --- Main loop --- + // Main loop. while (!glfwWindowShouldClose(window)) { glfwPollEvents(); + double currentTime = glfwGetTime(); + if (running && (currentTime - lastStepTime) >= stepInterval && !emulator.halted) { + emulator.step(); + lastStepTime = currentTime; + } + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - renderScreenToBackground(cpu.memory.data()); - - ImGui::Begin("CPU Emulator"); - - if (ImGui::Button("Step")) cpu.step(); + // CPU Control Panel. + ImGui::Begin("CPU Control"); + if (ImGui::Button("Reset")) { + emulator.reset(); + emulator.loadProgram(program); + running = false; + } ImGui::SameLine(); - if (ImGui::Button("Run")) cpu.run(10000); + if (ImGui::Button("Start")) + running = true; ImGui::SameLine(); - if (ImGui::Button("Reset")) cpu.loadProgram(rainbow); - - ImGui::Text("A: 0x%02X | PC: 0x%04X", cpu.A, cpu.PC); - ImGui::Text("Instruction: %s", cpu.getCurrentInstruction().c_str()); - - ImGui::End(); - - - mem_edit_1.DrawWindow("Memory Editor", &cpu.memory, 65536); - - - ImGui::Begin("Memory Inspector"); - - static int selected = 0; - ImGui::SliderInt("Address", &selected, 0, (int)cpu.memory.size() - 1); - ImGui::Text("0x%04X: 0x%02X", selected, cpu.memory[selected]); - if (ImGui::Button("Zero")) cpu.memory[selected] = 0; - + if (ImGui::Button("Stop")) + running = false; + ImGui::SameLine(); + if (ImGui::Button("Step") && !emulator.halted) + emulator.step(); + ImGui::Text("PC: 0x%08X", emulator.pc); + if (emulator.halted) + ImGui::TextColored(ImVec4(1, 0, 0, 1), "CPU Halted"); ImGui::Separator(); - ImGui::Text("Full Memory Dump:"); - ImGui::BeginChild("memory_dump", ImVec2(500, 300), true); - for (int row = 0; row < 16; ++row) { - for (int col = 0; col < 16; ++col) { - int addr = row * 16 + col; - ImGui::SameLine(); - ImGui::Text("0x%02X", cpu.memory[addr]); + // Registers display. + if (ImGui::BeginTable("Registers", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) { + ImGui::TableSetupColumn("Reg"); + ImGui::TableSetupColumn("Value"); + ImGui::TableSetupColumn("Reg"); + ImGui::TableSetupColumn("Value"); + ImGui::TableHeadersRow(); + for (int i = 0; i < 32; i += 2) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("x%d", i); + ImGui::TableSetColumnIndex(1); + ImGui::Text("0x%08X", emulator.registers[i]); + ImGui::TableSetColumnIndex(2); + ImGui::Text("x%d", i+1); + ImGui::TableSetColumnIndex(3); + ImGui::Text("0x%08X", emulator.registers[i+1]); } + ImGui::EndTable(); + } + ImGui::Text("Flags: Z[%d] N[%d] C[%d] O[%d]", + emulator.flags.zero, emulator.flags.negative, + emulator.flags.carry, emulator.flags.overflow); + ImGui::Separator(); + + // Disassembly view. + ImGui::BeginChild("Disassembly", ImVec2(0, 150), true); + ImGui::Text("Disassembly:"); + for (int i = 0; i < 10; i++) { + uint32_t addr = emulator.pc + i * 4; + std::string line = emulator.disassembleInstruction(addr); + if (i == 0) + ImGui::TextColored(ImVec4(1, 1, 0, 1), "0x%08X: %s", addr, line.c_str()); + else + ImGui::Text("0x%08X: %s", addr, line.c_str()); } ImGui::EndChild(); - ImGui::End(); + // Memory Viewer in a grid. + ImGui::Begin("Memory Viewer"); + const int columns = 16, rows = 16; + if (ImGui::BeginTable("MemoryGrid", columns, ImGuiTableFlags_Borders)) { + for (int col = 0; col < columns; col++) { + ImGui::TableSetupColumn(""); + } + ImGui::TableHeadersRow(); + int wordIndex = 0; + for (int r = 0; r < rows; r++) { + ImGui::TableNextRow(); + for (int c = 0; c < columns; c++) { + ImGui::TableSetColumnIndex(c); + if (wordIndex < emulator.memory.size()) + ImGui::Text("0x%08X", emulator.memory[wordIndex]); + else + ImGui::Text("----"); + wordIndex++; + } + } + ImGui::EndTable(); + } + ImGui::End(); + + // Terminal screen to display ECALL outputs. + ImGui::Begin("Terminal"); + // Optionally, you can add a "Clear" button. + if (ImGui::Button("Clear")) + emulator.terminalOutput.clear(); + ImGui::Separator(); + // Display the terminal output in a scrolling region. + ImGui::BeginChild("ScrollingRegion", ImVec2(0, 150), false, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::TextUnformatted(emulator.terminalOutput.c_str()); + ImGui::EndChild(); + ImGui::End(); + + // Rendering. ImGui::Render(); int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); - glClearColor(0, 0, 0, 1); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); glfwSwapBuffers(window); } - + + // Cleanup. ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); diff --git a/cpu-emulator/out.h b/cpu-emulator/out.h new file mode 100644 index 0000000..b5591d7 --- /dev/null +++ b/cpu-emulator/out.h @@ -0,0 +1,7 @@ +// Generated C++ vector containing program data. +#include +#include + +std::vector program_data = { + 0x45051141, 0x4639858A, 0x04000893, 0x00000073, 0x08934501, 0x007305D0, 0xA0010000, +}; diff --git a/cpu-emulator/program.c b/cpu-emulator/program.c new file mode 100644 index 0000000..f6d48ac --- /dev/null +++ b/cpu-emulator/program.c @@ -0,0 +1,39 @@ +// hello.c +// Minimal RISC-V Linux program without the C library. +// It uses direct system calls via the ECALL instruction. +// +// System call numbers for RISC-V Linux: +// __NR_write = 64 +// __NR_exit = 93 +// +// Compile this program with: +// riscv64-unknown-elf-gcc -nostdlib -static -O2 -o hello hello.c + +// _start is the entry point when not using the standard C runtime. +void _start() { + // Define the message to print. + const char msg[] = "Hello, world!\n"; + + // --- Write "Hello, world!\n" to stdout --- + // For the write(2) system call: + // a0: file descriptor (1 for stdout) + // a1: pointer to the message + // a2: length of the message (without the null terminator) + // a7: system call number (__NR_write, which is 64) + register long a0 asm("a0") = 1; // file descriptor (stdout) + register const char *a1 asm("a1") = msg; // pointer to message + register long a2 asm("a2") = sizeof(msg) - 1; // message length + register long a7 asm("a7") = 64; // syscall number: write + asm volatile ("ecall" : : "r"(a0), "r"(a1), "r"(a2), "r"(a7)); + + // --- Exit the program --- + // For the exit(2) system call: + // a0: exit code (0 for success) + // a7: system call number (__NR_exit, which is 93) + register long exit_code asm("a0") = 0; + register long a7_exit asm("a7") = 93; // syscall number: exit + asm volatile ("ecall" : : "r"(exit_code), "r"(a7_exit)); + + // We should never reach this point. + while (1) {} +} diff --git a/screen-share/client.py b/screen-share/client.py new file mode 100644 index 0000000..2889ea0 --- /dev/null +++ b/screen-share/client.py @@ -0,0 +1,28 @@ +import socket +import threading +import time +from PIL import ImageGrab +import io + +def send_screen(): + while True: + img = ImageGrab.grab() + img = img.resize((800, 600)) + buf = io.BytesIO() + img.save(buf, format='JPEG', quality=50) + data = buf.getvalue() + try: + s.send(len(data).to_bytes(4, byteorder='big') + data) + except: + break + time.sleep(0.5) + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(("127.0.0.1", 9999)) # Replace with actual IP +threading.Thread(target=send_screen, daemon=True).start() + +try: + while True: + time.sleep(1) +except KeyboardInterrupt: + s.close() diff --git a/screen-share/server.py b/screen-share/server.py new file mode 100644 index 0000000..cf389ba --- /dev/null +++ b/screen-share/server.py @@ -0,0 +1,78 @@ +import socket +import threading +import tkinter as tk +from tkinter import ttk +from PIL import Image, ImageTk +import io + +clients = {} +client_frames = {} + +def handle_client(conn, addr, client_id): + print(f"[+] New client: {addr} as {client_id}") + while True: + try: + length_data = conn.recv(4) + if not length_data: + break + img_len = int.from_bytes(length_data, byteorder='big') + img_data = b'' + while len(img_data) < img_len: + packet = conn.recv(img_len - len(img_data)) + if not packet: + break + img_data += packet + if client_id in client_frames: + image = Image.open(io.BytesIO(img_data)) + tk_image = ImageTk.PhotoImage(image) + canvas = client_frames[client_id]["canvas"] + canvas.img = tk_image + canvas.create_image(0, 0, anchor="nw", image=tk_image) + except: + break + print(f"[-] Disconnected: {addr}") + del clients[client_id] + del client_frames[client_id] + conn.close() + refresh_client_list() + +def refresh_client_list(): + for widget in client_list_frame.winfo_children(): + widget.destroy() + for client_id in clients: + btn = ttk.Button(client_list_frame, text=client_id, command=lambda c=client_id: open_viewer(c)) + btn.pack(fill="x", padx=5, pady=2) + +def open_viewer(client_id): + if client_id in client_frames: + return + top = tk.Toplevel(root) + top.title(f"Screen: {client_id}") + canvas = tk.Canvas(top, width=800, height=600) + canvas.pack() + client_frames[client_id] = {"window": top, "canvas": canvas} + +def accept_clients(): + while True: + conn, addr = server_socket.accept() + client_id = f"{addr[0]}:{addr[1]}" + clients[client_id] = conn + threading.Thread(target=handle_client, args=(conn, addr, client_id), daemon=True).start() + refresh_client_list() + +# GUI setup +root = tk.Tk() +root.title("Screen Viewer Server") +root.geometry("300x500") + +client_list_frame = tk.Frame(root) +client_list_frame.pack(fill="both", expand=True) + +# Networking +server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +server_socket.bind(("0.0.0.0", 9999)) +server_socket.listen(5) +print("[*] Server listening on port 9999") + +threading.Thread(target=accept_clients, daemon=True).start() +root.mainloop()