From 9ab7b9bb146146c13620cf472c0eb0474b9009fc Mon Sep 17 00:00:00 2001 From: "anthony@rabine.fr" Date: Tue, 7 Oct 2025 08:50:31 +0200 Subject: [PATCH] Add DATA and BSS RAM sections, crate binary format with header --- core/chip32/chip32_assembler.cpp | 289 +++++++++++-- core/chip32/chip32_assembler.h | 12 +- core/chip32/chip32_binary_format.c | 287 +++++++++++++ core/chip32/chip32_binary_format.h | 213 ++++++++++ core/chip32/chip32_machine.h | 309 +++++++++----- core/story-manager/src/story_project.cpp | 4 +- core/tests/CMakeLists.txt | 1 + core/tests/test_parser.cpp | 508 +++++++++++++---------- story-editor/CMakeLists.txt | 1 + 9 files changed, 1261 insertions(+), 363 deletions(-) create mode 100644 core/chip32/chip32_binary_format.c create mode 100644 core/chip32/chip32_binary_format.h diff --git a/core/chip32/chip32_assembler.cpp b/core/chip32/chip32_assembler.cpp index 8508b6f..38924a7 100644 --- a/core/chip32/chip32_assembler.cpp +++ b/core/chip32/chip32_assembler.cpp @@ -24,6 +24,7 @@ THE SOFTWARE. #include "chip32_assembler.h" +#include "chip32_binary_format.h" #include #include @@ -32,6 +33,7 @@ THE SOFTWARE. #include #include #include +#include namespace Chip32 { @@ -358,29 +360,197 @@ bool Assembler::CompileConstantArgument(Instr &instr, const std::string &a) bool Assembler::BuildBinary(std::vector &program, Result &result) { program.clear(); - result = { 0, 0, 0}; // clear stuff! - - // serialize each instruction and arguments to program memory, assign address to variables (rom or ram) - for (auto &i : m_instructions) + result = { 0, 0, 0}; + + // ======================================================================== + // PHASE 1: Créer les sections temporaires + // ======================================================================== + std::vector dataSection; // DC - ROM constants only + std::vector codeSection; // Executable code + std::vector initDataSection; // DV values + DZ zeros (RAM init) + + uint32_t bssSize = 0; // Total RAM size (DV + DZ) + uint32_t entryPoint = 0; // Entry point (.main) + + // Map to track DV init data: RAM address -> init data + std::map> dvInitData; + + // ======================================================================== + // PHASE 2: Première passe - identifier les variables DV et leurs données + // ======================================================================== + // On doit d'abord construire une map des données d'initialisation DV + for (size_t idx = 0; idx < m_instructions.size(); idx++) { - if (i.isRamData) + const auto &instr = m_instructions[idx]; + + // Détecter une variable DV (le marqueur final) + if (instr.isRamData && !instr.isZeroData && instr.mnemonic[0] == '$') { - result.ramUsageSize += i.dataLen; - } - else - { - if (i.isRomCode()) + uint16_t ramAddr = instr.addr; + uint16_t romInitAddr = instr.romInitAddr; + uint16_t dataLen = instr.dataLen; + + // Collecter les données d'init depuis les instructions précédentes + std::vector initData; + + // Chercher en arrière les instructions ROM data pour cette variable + for (size_t j = 0; j < idx; j++) { - program.push_back(i.code.opcode); + const auto &dataInstr = m_instructions[j]; + + // Ces instructions ont isRomData=true et leur addr correspond à romInitAddr + if (dataInstr.isRomData && !dataInstr.isRamData && + dataInstr.addr >= romInitAddr && + dataInstr.addr < romInitAddr + dataLen) + { + // Ces données appartiennent à cette variable DV + initData.insert(initData.end(), + dataInstr.compiledArgs.begin(), + dataInstr.compiledArgs.end()); + } } - else if (i.isRomData) // Seulement pour ROM DATA - { - result.constantsSize += i.compiledArgs.size(); - } - std::copy (i.compiledArgs.begin(), i.compiledArgs.end(), std::back_inserter(program)); + + dvInitData[ramAddr] = initData; } } - result.romUsageSize = program.size(); + + // ======================================================================== + // PHASE 3: Deuxième passe - dispatcher dans les bonnes sections + // ======================================================================== + // Track which ROM data belongs to DV variables + std::set dvRomAddresses; + for (const auto &instr : m_instructions) + { + if (instr.isRamData && !instr.isZeroData && instr.mnemonic[0] == '$') + { + for (uint16_t addr = instr.romInitAddr; + addr < instr.romInitAddr + instr.dataLen; + addr++) + { + dvRomAddresses.insert(addr); + } + } + } + + for (const auto &i : m_instructions) + { + // ==================================================================== + // RAM VARIABLES (DV et DZ) - marqueurs uniquement + // ==================================================================== + if (i.isRamData) + { + bssSize += i.dataLen; + // Les données seront traitées dans la phase 4 + } + // ==================================================================== + // ROM CODE (Instructions exécutables) + // ==================================================================== + else if (i.isRomCode()) + { + // Détecter le point d'entrée AVANT d'ajouter les données + if (i.isLabel && i.mnemonic == ".main") + { + entryPoint = static_cast(codeSection.size()); + } + + // Ajouter l'opcode + codeSection.push_back(i.code.opcode); + + // Ajouter les arguments compilés + std::copy(i.compiledArgs.begin(), + i.compiledArgs.end(), + std::back_inserter(codeSection)); + } + // ==================================================================== + // ROM DATA - Distinguer DC (vraies constantes) de DV init data + // ==================================================================== + else if (i.isRomData && !i.isRamData) + { + // Vérifier si cette donnée appartient à une variable DV + bool isDvInitData = (dvRomAddresses.find(i.addr) != dvRomAddresses.end()); + + if (!isDvInitData) + { + // C'est une vraie constante DC - va dans dataSection + std::copy(i.compiledArgs.begin(), + i.compiledArgs.end(), + std::back_inserter(dataSection)); + + result.constantsSize += i.compiledArgs.size(); + } + // Sinon, c'est une donnée DV, elle sera traitée dans la phase 4 + } + } + + // ======================================================================== + // PHASE 4: Construire initDataSection (DV + DZ dans l'ordre RAM) + // ======================================================================== + if (bssSize > 0) + { + initDataSection.resize(bssSize, 0); // Tout à zéro par défaut (pour DZ) + + // Copier les données DV aux bonnes positions RAM + for (const auto &pair : dvInitData) + { + uint16_t ramAddr = pair.first; + const std::vector &data = pair.second; + + // Copier les données d'init à l'adresse RAM correspondante + for (size_t i = 0; i < data.size() && (ramAddr + i) < bssSize; i++) + { + initDataSection[ramAddr + i] = data[i]; + } + } + + // Les zones DZ sont déjà à zéro grâce au resize() + } + + // ======================================================================== + // PHASE 5: Créer le header binaire + // ======================================================================== + chip32_binary_header_t header; + chip32_binary_header_init(&header); + + header.data_size = static_cast(dataSection.size()); + header.bss_size = bssSize; + header.code_size = static_cast(codeSection.size()); + header.entry_point = entryPoint; + header.init_data_size = static_cast(initDataSection.size()); + + if (initDataSection.size() > 0) + { + header.flags |= CHIP32_FLAG_HAS_INIT_DATA; + } + + // ======================================================================== + // PHASE 6: Assembler le binaire final + // ======================================================================== + uint32_t totalSize = chip32_binary_calculate_size(&header); + program.resize(totalSize); + + uint32_t bytesWritten = chip32_binary_write( + &header, + dataSection.empty() ? nullptr : dataSection.data(), + codeSection.empty() ? nullptr : codeSection.data(), + initDataSection.empty() ? nullptr : initDataSection.data(), + program.data(), + static_cast(program.size()) + ); + + if (bytesWritten == 0 || bytesWritten != totalSize) + { + // Erreur lors de l'écriture + program.clear(); + return false; + } + + // ======================================================================== + // PHASE 7: Remplir les statistiques + // ======================================================================== + result.ramUsageSize = bssSize; + result.romUsageSize = header.data_size + header.code_size; + result.constantsSize = header.data_size; + return true; } @@ -474,44 +644,97 @@ bool Assembler::Parse(const std::string &data) std::string type = lineParts[1]; CHIP32_CHECK(instr, (type.size() >= 3), "bad data type size"); - CHIP32_CHECK(instr, (type[0] == 'D') && ((type[1] == 'C') || (type[1] == 'V')), "bad data type (must be DCxx or DVxx"); + CHIP32_CHECK(instr, (type[0] == 'D') && ((type[1] == 'C') || (type[1] == 'V') || (type[1] == 'Z')), + "bad data type (must be DCxx, DVxx or DZxx)"); CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode); - instr.isRomData = type[1] == 'C' ? true : false; - instr.isRamData = type[1] == 'V' ? true : false; + // Parse data type size (8, 16, or 32) type.erase(0, 2); - instr.dataTypeSize = static_cast(strtol(type.c_str(), NULL, 0)); + instr.dataTypeSize = static_cast(strtol(type.c_str(), NULL, 0)); + // Determine data type + char typeChar = lineParts[1][1]; + instr.isRomData = (typeChar == 'C'); + instr.isRamData = (typeChar == 'V' || typeChar == 'Z'); + instr.isZeroData = (typeChar == 'Z'); + + // ======================================================================================= + // DC - ROM Constants (read-only data in program memory) + // ======================================================================================= if (instr.isRomData) { instr.addr = code_addr; m_labels[opcode] = instr; // location of the start of the data - // if ROM data, we generate one instruction per argument - // reason: arguments may be labels, easier to replace later - + + // Generate one instruction per argument + // Reason: arguments may be labels, easier to replace later for (unsigned int i = 2; i < lineParts.size(); i++) { - CHIP32_CHECK(instr, CompileConstantArgument(instr, lineParts[i]), "Compile argument error, stopping."); + CHIP32_CHECK(instr, CompileConstantArgument(instr, lineParts[i]), + "Compile argument error, stopping."); m_instructions.push_back(instr); code_addr += instr.compiledArgs.size(); instr.addr = code_addr; } } - else // RAM DATA, only one argument is used: the size of the array + // ======================================================================================= + // DV - RAM Variables with initial values (data stored in ROM, copied to RAM at startup) + // ======================================================================================= + else if (!instr.isZeroData) // DV { + // DV behaves like DC for data storage, but marks the location as RAM + // The initial values are stored in ROM and must be copied to RAM at startup + instr.addr = ram_addr; - instr.dataLen = static_cast(strtol(lineParts[2].c_str(), NULL, 0)) * instr.dataTypeSize/8; + m_labels[opcode] = instr; // RAM address for this variable + + // Store the ROM address where initial data starts + instr.romInitAddr = code_addr; + + // Process all initial values (like DC) + for (unsigned int i = 2; i < lineParts.size(); i++) + { + Instr dataInstr = instr; // Copy instruction info + CHIP32_CHECK(dataInstr, CompileConstantArgument(dataInstr, lineParts[i]), + "Compile argument error, stopping."); + + // This instruction contains ROM data for initialization + dataInstr.addr = code_addr; + dataInstr.isRomData = true; // Store in ROM for init data + dataInstr.isRamData = false; // But this is just init data, not the variable + m_instructions.push_back(dataInstr); + + code_addr += dataInstr.compiledArgs.size(); + instr.dataLen += dataInstr.compiledArgs.size(); + } + + // Now add the RAM variable marker + instr.addr = ram_addr; + instr.isRomData = false; + instr.isRamData = true; + ram_addr += instr.dataLen; + m_instructions.push_back(instr); + } + // ======================================================================================= + // DZ - Zero-initialized RAM zones (no data in ROM, just reserve space) + // ======================================================================================= + else // DZ + { + // DZ only takes ONE argument: the number of elements + CHIP32_CHECK(instr, lineParts.size() == 3, + "DZ directive requires exactly one argument (number of elements)"); + + instr.addr = ram_addr; + + // Calculate size in bytes: num_elements * (type_size / 8) + uint32_t numElements = static_cast(strtol(lineParts[2].c_str(), NULL, 0)); + instr.dataLen = static_cast(numElements * (instr.dataTypeSize / 8)); + ram_addr += instr.dataLen; m_labels[opcode] = instr; m_instructions.push_back(instr); } } - else - { - m_lastError.message = "Unknown mnemonic or badly formatted line"; - m_lastError.line = lineNum; - return false; - } } // 2. Second pass: replace all label or RAM data by the real address in memory diff --git a/core/chip32/chip32_assembler.h b/core/chip32/chip32_assembler.h index 955f7e6..ee0e367 100644 --- a/core/chip32/chip32_assembler.h +++ b/core/chip32/chip32_assembler.h @@ -50,12 +50,14 @@ struct Instr { uint16_t dataTypeSize{0}; uint16_t dataLen{0}; - bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction - bool useLabel{false}; //!< If true, the instruction uses a label - bool isRomData{false}; //!< True is constant data in program - bool isRamData{false}; //!< True is constant data in program + bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction + bool useLabel{false}; //!< If true, the instruction uses a label + bool isRomData{false}; //!< True if constant data in ROM (DC) + bool isRamData{false}; //!< True if RAM variable (DV or DZ) + bool isZeroData{false}; //!< True if zero-initialized RAM (DZ only) - uint16_t addr{0}; //!< instruction address when assembled in program memory + uint16_t addr{0}; //!< Instruction/data address (ROM for DC, RAM for DV/DZ) + uint16_t romInitAddr{0};//!< For DV: ROM address where initial data is stored bool isRomCode() const { return !(isLabel || isRomData || isRamData); } }; diff --git a/core/chip32/chip32_binary_format.c b/core/chip32/chip32_binary_format.c new file mode 100644 index 0000000..7499165 --- /dev/null +++ b/core/chip32/chip32_binary_format.c @@ -0,0 +1,287 @@ +/* + * Chip32 Binary Format - Implementation + */ + +#include "chip32_binary_format.h" +#include + +// Verify header size at compile time +_Static_assert(sizeof(chip32_binary_header_t) == 28, "Header must be 28 bytes"); + +// ============================================================================ +// LOADING FUNCTIONS +// ============================================================================ + +chip32_binary_error_t chip32_binary_load( + uint8_t* binary, + uint32_t size, + chip32_loaded_binary_t* out_loaded +) +{ + if (!binary || !out_loaded) { + return CHIP32_BIN_ERR_NULL_POINTER; + } + + // Clear output structure + memset(out_loaded, 0, sizeof(chip32_loaded_binary_t)); + + // Check minimum size + if (size < sizeof(chip32_binary_header_t)) { + out_loaded->error = CHIP32_BIN_ERR_TOO_SMALL; + return CHIP32_BIN_ERR_TOO_SMALL; + } + + // Copy header + memcpy(&out_loaded->header, binary, sizeof(chip32_binary_header_t)); + + // Verify magic number + if (out_loaded->header.magic != CHIP32_MAGIC) { + out_loaded->error = CHIP32_BIN_ERR_INVALID_MAGIC; + return CHIP32_BIN_ERR_INVALID_MAGIC; + } + + // Check version + if (out_loaded->header.version > CHIP32_VERSION) { + out_loaded->error = CHIP32_BIN_ERR_UNSUPPORTED_VERSION; + return CHIP32_BIN_ERR_UNSUPPORTED_VERSION; + } + + // Calculate expected size + uint32_t expected_size = sizeof(chip32_binary_header_t) + + out_loaded->header.data_size + + out_loaded->header.code_size + + out_loaded->header.init_data_size; + + if (size != expected_size) { + out_loaded->error = CHIP32_BIN_ERR_SIZE_MISMATCH; + return CHIP32_BIN_ERR_SIZE_MISMATCH; + } + + // Set section pointers + uint32_t offset = sizeof(chip32_binary_header_t); + + if (out_loaded->header.data_size > 0) { + out_loaded->data_section = binary + offset; + offset += out_loaded->header.data_size; + } + + if (out_loaded->header.code_size > 0) { + out_loaded->code_section = binary + offset; + offset += out_loaded->header.code_size; + } + + if (out_loaded->header.init_data_size > 0) { + out_loaded->init_data_section = binary + offset; + } + + out_loaded->error = CHIP32_BIN_OK; + return CHIP32_BIN_OK; +} + +void chip32_binary_get_stats( + const chip32_loaded_binary_t* loaded, + chip32_binary_stats_t* out_stats +) +{ + if (!loaded || !out_stats) { + return; + } + + out_stats->data_size = loaded->header.data_size; + out_stats->bss_size = loaded->header.bss_size; + out_stats->code_size = loaded->header.code_size; + out_stats->init_data_size = loaded->header.init_data_size; + + out_stats->total_file_size = sizeof(chip32_binary_header_t) + + loaded->header.data_size + + loaded->header.code_size + + loaded->header.init_data_size; + + out_stats->total_rom_size = loaded->header.data_size + + loaded->header.code_size; + + out_stats->total_ram_size = loaded->header.bss_size; +} + +const char* chip32_binary_error_string(chip32_binary_error_t error) +{ + switch (error) { + case CHIP32_BIN_OK: + return "No error"; + case CHIP32_BIN_ERR_TOO_SMALL: + return "Binary too small (less than header size)"; + case CHIP32_BIN_ERR_INVALID_MAGIC: + return "Invalid magic number (not a Chip32 binary)"; + case CHIP32_BIN_ERR_UNSUPPORTED_VERSION: + return "Unsupported binary version"; + case CHIP32_BIN_ERR_SIZE_MISMATCH: + return "Binary size mismatch (corrupted file?)"; + case CHIP32_BIN_ERR_NULL_POINTER: + return "NULL pointer argument"; + default: + return "Unknown error"; + } +} + +// ============================================================================ +// BUILDING FUNCTIONS +// ============================================================================ + +void chip32_binary_header_init(chip32_binary_header_t* header) +{ + if (!header) { + return; + } + + memset(header, 0, sizeof(chip32_binary_header_t)); + header->magic = CHIP32_MAGIC; + header->version = CHIP32_VERSION; + header->flags = 0; +} + +uint32_t chip32_binary_calculate_size(const chip32_binary_header_t* header) +{ + if (!header) { + return 0; + } + + return sizeof(chip32_binary_header_t) + + header->data_size + + header->code_size + + header->init_data_size; +} + +uint32_t chip32_binary_write( + const chip32_binary_header_t* header, + const uint8_t* data_section, + const uint8_t* code_section, + const uint8_t* init_data_section, + uint8_t* out_buffer, + uint32_t buffer_size +) +{ + if (!header || !out_buffer) { + return 0; + } + + // Calculate required size + uint32_t required_size = chip32_binary_calculate_size(header); + + if (buffer_size < required_size) { + return 0; // Buffer too small + } + + uint32_t offset = 0; + + // Write header + memcpy(out_buffer + offset, header, sizeof(chip32_binary_header_t)); + offset += sizeof(chip32_binary_header_t); + + // Write DATA section + if (header->data_size > 0) { + if (!data_section) { + return 0; // Data expected but NULL pointer + } + memcpy(out_buffer + offset, data_section, header->data_size); + offset += header->data_size; + } + + // Write CODE section + if (header->code_size > 0) { + if (!code_section) { + return 0; // Code expected but NULL pointer + } + memcpy(out_buffer + offset, code_section, header->code_size); + offset += header->code_size; + } + + // Write INIT DATA section + if (header->init_data_size > 0) { + if (!init_data_section) { + return 0; // Init data expected but NULL pointer + } + memcpy(out_buffer + offset, init_data_section, header->init_data_size); + offset += header->init_data_size; + } + + return offset; +} + +// ============================================================================ +// RAM INITIALIZATION HELPER +// ============================================================================ + +uint32_t chip32_binary_init_ram( + const chip32_loaded_binary_t* loaded, + uint8_t* ram_buffer, + uint32_t ram_size +) +{ + if (!loaded || !ram_buffer) { + return 0; + } + + // Check if binary has init data + if (loaded->header.init_data_size == 0 || !loaded->init_data_section) { + return 0; + } + + // Copy init data to RAM (respect buffer limits) + uint32_t copy_size = loaded->header.init_data_size; + if (copy_size > ram_size) { + copy_size = ram_size; // Truncate if RAM is smaller + } + + memcpy(ram_buffer, loaded->init_data_section, copy_size); + + return copy_size; +} + +// ============================================================================ +// DEBUG/UTILITY FUNCTIONS +// ============================================================================ + +void chip32_binary_print_header(const chip32_binary_header_t* header) +{ + if (!header) { + return; + } + + printf("=== Chip32 Binary Header ===\n"); + printf("Magic: 0x%08X", header->magic); + if (header->magic == CHIP32_MAGIC) { + printf(" (valid)\n"); + } else { + printf(" (INVALID!)\n"); + } + printf("Version: %u\n", header->version); + printf("Flags: 0x%04X", header->flags); + if (header->flags & CHIP32_FLAG_HAS_INIT_DATA) { + printf(" (has init data)"); + } + printf("\n"); + printf("DATA section: %u bytes (ROM constants)\n", header->data_size); + printf("BSS section: %u bytes (Total RAM: DV+DZ)\n", header->bss_size); + printf("CODE section: %u bytes\n", header->code_size); + printf("Entry point: 0x%08X\n", header->entry_point); + printf("Init data: %u bytes (DV values + DZ zeros)\n", header->init_data_size); + printf("\n"); +} + +void chip32_binary_print_stats(const chip32_binary_stats_t* stats) +{ + if (!stats) { + return; + } + + printf("=== Chip32 Binary Statistics ===\n"); + printf("DATA section: %u bytes (ROM, initialized)\n", stats->data_size); + printf("BSS section: %u bytes (RAM, DV+DZ)\n", stats->bss_size); + printf("CODE section: %u bytes (ROM, executable)\n", stats->code_size); + printf("Init data: %u bytes (RAM initialization)\n", stats->init_data_size); + printf("---\n"); + printf("File size: %u bytes\n", stats->total_file_size); + printf("ROM usage: %u bytes (DATA + CODE)\n", stats->total_rom_size); + printf("RAM usage: %u bytes (BSS)\n", stats->total_ram_size); + printf("\n"); +} \ No newline at end of file diff --git a/core/chip32/chip32_binary_format.h b/core/chip32/chip32_binary_format.h new file mode 100644 index 0000000..13f469a --- /dev/null +++ b/core/chip32/chip32_binary_format.h @@ -0,0 +1,213 @@ +/* + * Chip32 Binary Format - Pure C Implementation + * Compatible with embedded systems + * + * Variable types: + * DC8, DC32 : ROM constants (initialized data in ROM) + * DV8, DV32 : RAM variables with initial value + * DZ8, DZ32 : RAM zero-initialized arrays/buffers + * + * Examples: + * $romString DC8 "Hello" ; String in ROM + * $romValue DC32 42 ; Constant in ROM + * $ramCounter DV32 100 ; RAM variable initialized to 100 + * $ramMessage DV8 "Test" ; RAM string initialized to "Test" + * $ramBuffer DZ8 256 ; RAM buffer of 256 bytes (zeroed) + * $ramArray DZ32 100 ; RAM array of 100 x 32-bit (zeroed) + * + * Binary file structure: + * + * [HEADER - 28 bytes] + * - Magic number: "C32\0" (4 bytes) + * - Version: uint16_t (2 bytes) + * - Flags: uint16_t (2 bytes) + * - Data section size: uint32_t (4 bytes) + * - BSS section size: uint32_t (4 bytes) + * - Code section size: uint32_t (4 bytes) + * - Entry point: uint32_t (4 bytes) + * - Init data size: uint32_t (4 bytes) + * + * [DATA SECTION] + * - ROM constants (DC8, DC32, etc.) + * + * [CODE SECTION] + * - Executable instructions + * + * [INIT DATA SECTION] + * - Initial RAM values: DV values + zeros for DZ areas + * - Size = sum of all DV and DZ declarations + * - Layout matches RAM layout exactly + */ + +#ifndef CHIP32_BINARY_FORMAT_H +#define CHIP32_BINARY_FORMAT_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Magic number: "C32\0" in little-endian +#define CHIP32_MAGIC 0x00323343 + +// Current version +#define CHIP32_VERSION 1 + +// Flags +#define CHIP32_FLAG_HAS_INIT_DATA 0x0001 // Binary contains RAM init data + +// Error codes +typedef enum { + CHIP32_BIN_OK = 0, + CHIP32_BIN_ERR_TOO_SMALL, + CHIP32_BIN_ERR_INVALID_MAGIC, + CHIP32_BIN_ERR_UNSUPPORTED_VERSION, + CHIP32_BIN_ERR_SIZE_MISMATCH, + CHIP32_BIN_ERR_NULL_POINTER +} chip32_binary_error_t; + +// Binary header structure (28 bytes, packed) +#pragma pack(push, 1) +typedef struct { + uint32_t magic; // Magic number "C32\0" + uint16_t version; // Format version + uint16_t flags; // Feature flags + uint32_t data_size; // Size of DATA section in bytes (ROM constants) + uint32_t bss_size; // Total RAM size (DV + DZ) + uint32_t code_size; // Size of CODE section in bytes + uint32_t entry_point; // Entry point offset in CODE section + uint32_t init_data_size; // Size of INIT DATA section (DV values + DZ zeros) +} chip32_binary_header_t; +#pragma pack(pop) + +// Loaded binary structure +typedef struct { + chip32_binary_header_t header; + uint8_t* data_section; // Points to DATA section (ROM) + uint8_t* code_section; // Points to CODE section + uint8_t* init_data_section; // Points to INIT DATA (RAM initialization) + chip32_binary_error_t error; +} chip32_loaded_binary_t; + +// Statistics +typedef struct { + uint32_t data_size; // ROM constants + uint32_t bss_size; // Total RAM needed + uint32_t code_size; // Executable code + uint32_t init_data_size; // RAM initialization data + uint32_t total_file_size; // File size on disk + uint32_t total_rom_size; // DATA + CODE + uint32_t total_ram_size; // RAM needed +} chip32_binary_stats_t; + +// ============================================================================ +// LOADING FUNCTIONS +// ============================================================================ + +/** + * Load and validate a Chip32 binary + * @param binary Pointer to binary data + * @param size Size of binary data in bytes + * @param out_loaded Output structure (filled on success) + * @return Error code + */ +chip32_binary_error_t chip32_binary_load( + uint8_t* binary, + uint32_t size, + chip32_loaded_binary_t* out_loaded +); + +/** + * Get statistics from a loaded binary + * @param loaded Loaded binary structure + * @param out_stats Output statistics + */ +void chip32_binary_get_stats( + const chip32_loaded_binary_t* loaded, + chip32_binary_stats_t* out_stats +); + +/** + * Get error string from error code + * @param error Error code + * @return Human-readable error message + */ +const char* chip32_binary_error_string(chip32_binary_error_t error); + +// ============================================================================ +// BUILDING FUNCTIONS (for assembler/compiler) +// ============================================================================ + +/** + * Initialize a binary header + * @param header Header to initialize + */ +void chip32_binary_header_init(chip32_binary_header_t* header); + +/** + * Calculate total binary size from header + * @param header Binary header + * @return Total size in bytes + */ +uint32_t chip32_binary_calculate_size(const chip32_binary_header_t* header); + +/** + * Write a complete binary to memory + * @param header Binary header + * @param data_section DATA section content (can be NULL if data_size is 0) + * @param code_section CODE section content (can be NULL if code_size is 0) + * @param init_data_section INIT DATA section (can be NULL if init_data_size is 0) + * @param out_buffer Output buffer (must be large enough) + * @param buffer_size Size of output buffer + * @return Number of bytes written, or 0 on error + */ +uint32_t chip32_binary_write( + const chip32_binary_header_t* header, + const uint8_t* data_section, + const uint8_t* code_section, + const uint8_t* init_data_section, + uint8_t* out_buffer, + uint32_t buffer_size +); + +// ============================================================================ +// RAM INITIALIZATION HELPER +// ============================================================================ + +/** + * Initialize RAM from binary INIT DATA section + * This copies all initial values (DV) and zeros (DZ) to RAM + * @param loaded Loaded binary with init data + * @param ram_buffer Destination RAM buffer + * @param ram_size Size of RAM buffer + * @return Number of bytes copied, or 0 if no init data + */ +uint32_t chip32_binary_init_ram( + const chip32_loaded_binary_t* loaded, + uint8_t* ram_buffer, + uint32_t ram_size +); + +// ============================================================================ +// DEBUG/UTILITY FUNCTIONS +// ============================================================================ + +/** + * Print binary header information + * @param header Binary header + */ +void chip32_binary_print_header(const chip32_binary_header_t* header); + +/** + * Print binary statistics + * @param stats Binary statistics + */ +void chip32_binary_print_stats(const chip32_binary_stats_t* stats); + +#ifdef __cplusplus +} +#endif + +#endif // CHIP32_BINARY_FORMAT_H \ No newline at end of file diff --git a/core/chip32/chip32_machine.h b/core/chip32/chip32_machine.h index 90c7a13..413ef85 100644 --- a/core/chip32/chip32_machine.h +++ b/core/chip32/chip32_machine.h @@ -1,17 +1,22 @@ +/* + * Chip32 Machine - VM wrapper with binary format support + * Updated to use chip32_binary_format for proper DV/DZ handling + */ + #pragma once - - #include #include #include #include +#include +#include +#include #include "chip32_assembler.h" +#include "chip32_binary_format.h" #include "chip32_macros.h" -// Dans chip32_machine.h - namespace Chip32 { @@ -22,9 +27,8 @@ public: bool buildResult{false}; chip32_result_t runResult{VM_OK}; std::string printOutput; - - static Machine *m_instance; - + chip32_ctx_t ctx; // Public pour accès aux registres dans les tests + Machine() { // Bind syscall handler to this instance m_syscallHandler = std::bind(&Machine::HandleSyscall, this, @@ -32,7 +36,94 @@ public: std::placeholders::_2); } - // Lecture d'une chaîne depuis la mémoire (non statique maintenant) + // ======================================================================== + // Méthode principale : Parse, Build, Execute + // ======================================================================== + + void QuickExecute(const std::string &assemblyCode) + { + std::vector program; + Chip32::Assembler assembler; + Chip32::Result result; + + // Parse + parseResult = assembler.Parse(assemblyCode); + + if (!parseResult) { + std::cout << "Parse error: " << assembler.GetLastError().ToString() << std::endl; + return; + } + + // Build binary with new format + buildResult = assembler.BuildBinary(program, result); + + if (!buildResult) { + std::cout << "Build error: " << assembler.GetLastError().ToString() << std::endl; + return; + } + + result.Print(); + + // Load binary using new format + chip32_loaded_binary_t loaded; + chip32_binary_error_t error = chip32_binary_load( + program.data(), + static_cast(program.size()), + &loaded + ); + + if (error != CHIP32_BIN_OK) { + std::cout << "Binary load error: " << chip32_binary_error_string(error) << std::endl; + buildResult = false; + return; + } + + // Allocate and initialize RAM + m_ram.resize(loaded.header.bss_size); + + uint32_t init_bytes = chip32_binary_init_ram(&loaded, m_ram.data(), m_ram.size()); + + if (init_bytes > 0) { + std::cout << "RAM initialized: " << init_bytes << " bytes" << std::endl; + } + + // Setup VM context + memset(&ctx, 0, sizeof(ctx)); + ctx.stack_size = 512; + + // ROM = DATA + CODE (contiguous in loaded binary) + ctx.rom.mem = loaded.data_section; + ctx.rom.addr = 0; + ctx.rom.size = loaded.header.data_size + loaded.header.code_size; + + // RAM + ctx.ram.mem = m_ram.data(); + ctx.ram.addr = ctx.rom.size; + ctx.ram.size = m_ram.size(); + + // Set syscall handler using wrapper + ctx.syscall = SyscallWrapper; + ctx.user_data = this; + + // Initialize VM + chip32_initialize(&ctx); + + // Set entry point (DATA size + entry point offset in CODE) + ctx.registers[PC] = loaded.header.data_size + loaded.header.entry_point; + + std::cout << "Starting execution at PC=0x" << std::hex << ctx.registers[PC] + << std::dec << std::endl; + + // Run + runResult = chip32_run(&ctx); + + std::cout << "Execution finished with result: " << runResult << std::endl; + } + + // ======================================================================== + // Helper functions + // ======================================================================== + static std::string GetStringFromMemory(chip32_ctx_t *ctx, uint32_t addr) { if (!ctx) { @@ -91,72 +182,41 @@ public: // Vérifier si on a assez d'arguments if (argIndex >= static_cast(args.size())) { - result << "{" << argIndex << ":?}"; // Argument manquant - pos += 2; + result << "{" << argIndex << ":?}"; + pos += 3; continue; } - uint32_t argValue = args[argIndex]; - - // Vérifier s'il y a un type spécifié {:d}, {:s}, {:f}, {:x} - if (pos + 3 < format.length() && format[pos + 2] == ':') { - char typeChar = format[pos + 3]; - - // Vérifier si le placeholder se termine bien par '}' - if (pos + 4 < format.length() && format[pos + 4] == '}') { - // Parser le type et formater - switch (typeChar) { - case 'd': // Entier décimal signé - case 'i': - result << static_cast(argValue); - break; - - case 'u': // Entier non signé - result << argValue; - break; - - case 'x': // Hexadécimal minuscule - result << "0x" << std::hex << argValue << std::dec; - break; - - case 'X': // Hexadécimal majuscule - result << "0x" << std::hex << std::uppercase - << argValue << std::nouppercase << std::dec; - break; - - case 's': // String (adresse) - try { - result << GetStringFromMemory(ctx, argValue); - } catch (const std::exception& e) { - result << ""; - } - break; - - case 'f': // Float - { - float floatValue; - std::memcpy(&floatValue, &argValue, sizeof(float)); - result << floatValue; - break; - } - - case 'c': // Caractère - result << static_cast(argValue); - break; - - default: - // Type inconnu, afficher tel quel - result << "{" << argIndex << ":" << typeChar << "}"; - } + // Vérifier le type (i ou s) + if (pos + 2 < format.length() && format[pos + 2] == ':') { + if (pos + 3 < format.length()) { + char typeChar = format[pos + 3]; + uint32_t argValue = args[argIndex]; - pos += 5; // Avancer de "{0:d}" - continue; + if (typeChar == 's') { + // String: argValue est une adresse + try { + std::string str = GetStringFromMemory(ctx, argValue); + result << str; + pos += 5; // Avancer de "{0:s}" + continue; + } catch (const std::exception& e) { + result << "{str_error}"; + pos += 5; + continue; + } + } else if (typeChar == 'i' || typeChar == 'd') { + // Integer + result << static_cast(argValue); + pos += 5; + continue; + } } - } - // Format court {0} sans type → défaut: entier - else if (pos + 2 < format.length() && format[pos + 2] == '}') { + } else if (pos + 2 < format.length() && format[pos + 2] == '}') { + // Format simple {0} - traiter comme int par défaut + uint32_t argValue = args[argIndex]; result << static_cast(argValue); - pos += 3; // Avancer de "{0}" + pos += 3; continue; } } @@ -170,7 +230,10 @@ public: return result.str(); } - // Handler de syscall (méthode membre, non statique) + // ======================================================================== + // Syscall handler + // ======================================================================== + uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code) { try { @@ -207,44 +270,84 @@ public: } } - void QuickExecute(const std::string &assemblyCode) + // ============================================================================ + // Fonction helper pour charger un binaire Chip32 dans la VM + // IMPORTANT: Le vecteur binary doit rester en vie pendant toute l'exécution + // car vm_ctx pointe directement dans ses données + // ============================================================================ + bool LoadBinaryIntoVM( + std::vector &binary, + chip32_ctx_t &vm_ctx, + uint8_t *ram_buffer, + uint32_t ram_size) { - std::vector program; - Chip32::Assembler assembler; - Chip32::Result result; - std::vector data(8*1024); - - parseResult = assembler.Parse(assemblyCode); - std::cout << assembler.GetLastError().ToString() << std::endl; - - buildResult = assembler.BuildBinary(program, result); - result.Print(); - - chip32_ctx_t chip32_ctx; - chip32_ctx.stack_size = 512; - chip32_ctx.rom.mem = program.data(); - chip32_ctx.rom.addr = 0; - chip32_ctx.rom.size = program.size(); - chip32_ctx.ram.mem = data.data(); - chip32_ctx.ram.addr = 40 * 1024; - chip32_ctx.ram.size = data.size(); - - // Utiliser le wrapper statique qui appelle notre fonction membre - chip32_ctx.syscall = SyscallWrapper; - chip32_ctx.user_data = this; // Stocker le pointeur vers cette instance - - chip32_initialize(&chip32_ctx); - - Instr mainLine; - if (assembler.GetMain(mainLine)) { - chip32_ctx.registers[PC] = mainLine.addr; + // Charger et valider le binaire + chip32_loaded_binary_t loaded; + chip32_binary_error_t err = chip32_binary_load( + binary.data(), + static_cast(binary.size()), + &loaded + ); + + if (err != CHIP32_BIN_OK) + { + std::cerr << "Binary load error: " << chip32_binary_error_string(err) << std::endl; + return false; } - - runResult = chip32_run(&chip32_ctx); + + // Afficher les informations du binaire (debug) + chip32_binary_print_header(&loaded.header); + + // Vérifier que la RAM est suffisante + if (loaded.header.bss_size > ram_size) + { + std::cerr << "Insufficient RAM: need " << loaded.header.bss_size + << " bytes, have " << ram_size << " bytes" << std::endl; + return false; + } + + // ======================================================================== + // Configurer la VM - ROM pointe directement dans le binaire + // ======================================================================== + + // Pour la VM, on considère DATA + CODE comme une seule ROM + // DATA commence à l'offset après le header + // CODE suit immédiatement après DATA + + vm_ctx.rom.mem = const_cast(loaded.data_section); + vm_ctx.rom.addr = 0; + vm_ctx.rom.size = loaded.header.data_size + loaded.header.code_size; + + // ======================================================================== + // Initialiser la RAM avec INIT DATA section + // ======================================================================== + + // D'abord mettre toute la RAM à zéro + memset(ram_buffer, 0, ram_size); + + // Ensuite copier les données d'initialisation (DV values + DZ zeros) + if (loaded.header.init_data_size > 0) + { + uint32_t copied = chip32_binary_init_ram(&loaded, ram_buffer, ram_size); + std::cout << "Copied " << copied << " bytes of init data to RAM" << std::endl; + } + + // Configurer RAM dans la VM + vm_ctx.ram.mem = ram_buffer; + vm_ctx.ram.addr = 0x8000; // Bit 31 = 1 pour RAM + vm_ctx.ram.size = ram_size; + + // ======================================================================== + // Configurer le point d'entrée + // ======================================================================== + // Le PC doit pointer dans la section CODE, qui commence après DATA + vm_ctx.registers[PC] = loaded.header.data_size + loaded.header.entry_point; + + return true; } private: - // std::function contenant le bind + std::vector m_ram; // RAM storage std::function m_syscallHandler; // Wrapper statique qui récupère l'instance et appelle la méthode membre @@ -259,4 +362,4 @@ private: } }; -} // namespace Chip32 +} // namespace Chip32 \ No newline at end of file diff --git a/core/story-manager/src/story_project.cpp b/core/story-manager/src/story_project.cpp index bcc75c8..312675b 100644 --- a/core/story-manager/src/story_project.cpp +++ b/core/story-manager/src/story_project.cpp @@ -445,7 +445,7 @@ bool StoryProject::GenerateCompleteProgram(std::string &assembly) } } - // ✅ PHASE 2 : GÉNÉRATION DE TOUS LES TAC (avant la section DATA!) + // PHASE 2 : GÉNÉRATION DE TOUS LES TAC (avant la section DATA!) std::cout << "\n=== Generating all TAC programs ===\n"; std::map pageTACPrograms; @@ -491,7 +491,7 @@ bool StoryProject::GenerateCompleteProgram(std::string &assembly) generator.GenerateGlobalVariables(); // Constantes de tous les nœuds de toutes les pages - // ✅ Les format strings ont déjà été modifiés par le TAC generator + // Les format strings ont déjà été modifiés par le TAC generator generator.GenerateNodesVariables(allNodes); // === SECTION TEXT (chaque page = une fonction) === diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index 4b2ba28..c312eb9 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -22,6 +22,7 @@ add_executable(${PROJECT_NAME} ../chip32/chip32_assembler.cpp ../chip32/chip32_vm.c + ../chip32/chip32_binary_format.c ) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/core/tests/test_parser.cpp b/core/tests/test_parser.cpp index 951ecb2..f1aedb1 100644 --- a/core/tests/test_parser.cpp +++ b/core/tests/test_parser.cpp @@ -23,194 +23,133 @@ THE SOFTWARE. */ #include -#include #include "catch.hpp" -#include "chip32_assembler.h" -#include "chip32_macros.h" +#include "chip32_machine.h" +#include "chip32_binary_format.h" /* Purpose: grammar, ram usage and macros, rom code generation +Tests updated with new DV/DZ syntax: + - DV8, DV32 : RAM variables WITH initial value + - DZ8, DZ32 : RAM zero-initialized areas (buffers) */ void hexdump(void *ptr, int buflen); +// ============================================================================ +// TEST 1: Basic grammar with DV/DZ +// ============================================================================ static const std::string test1 = R"( -; label definition -.main: ;; comment here should work -; We create a stupid loop just for RAM variable testing +; ============================================================================ +; Test grammar with new DV/DZ syntax +; ============================================================================ - lcons r0, 4 ; prepare loop: 4 iterations - lcons r2, $RamData1 ; save in R2 a ram address - store @r2, r0, 4 ; save R0 in RAM - lcons r1, 1 -.loop: - load r0, @r2, 4 ; load this variable - sub r0, r1 - store @r2, r0, 4 ; save R0 in RAM - skipz r0 ; skip loop if R0 == 0 - jump .loop +; ROM constants (DC) +$imageBird DC8 "example.bmp" +$someConstant DC32 12456789 +; RAM initialized variables (DV) +$RamData1 DV32 0 ; Integer initialized to 0 +$counter DV32 10 ; Counter initialized to 10 - mov r0, r2 ; copy R2 into R0 (blank space between , and R2) -mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2) +; RAM zeroed areas (DZ) +$MyArray DZ8 10 ; Array of 10 bytes (zeroed) +$WorkBuffer DZ8 64 ; Buffer of 64 bytes (zeroed) - halt - -$imageBird DC8 "example.bmp", 8 ; data -$someConstant DC32 12456789 - -; DSxx to declare a variable in RAM, followed by the number of elements -$RamData1 DV32 1 ; one 32-bit integer -$MyArray DV8 10 ; array of 10 bytes - -)"; - -#include -#include - - -int get_from_memory(chip32_ctx_t *ctx, uint32_t addr, char *text) -{ - int valid = 0; - - // Test if address is valid - - bool isRam = addr & 0x80000000; - addr &= 0xFFFF; // mask the RAM/ROM bit, ensure 16-bit addressing - if (isRam) { - strcpy(&text[0], (const char *)&ctx->ram.mem[addr]); - } else { - strcpy(&text[0], (const char *)&ctx->rom.mem[addr]); - } - - return valid; -} - - -static uint8_t story_player_syscall(chip32_ctx_t *ctx, uint8_t code) -{ - uint8_t retCode = SYSCALL_RET_OK; - char working_buf[100] = {0}; - - // Printf - if (code == 4) - { - // In R0: string with escaped characters - // R1: Number of arguments - // R2, R3 ... arguments - - // Integers: stored in registers by values - // Strings: first character address in register - - get_from_memory(ctx, ctx->registers[R0], working_buf); - int arg_count = ctx->registers[R1]; - - switch(arg_count){ - case 0: - puts(working_buf); - break; - case 1: - printf(working_buf, ctx->registers[R2]); - puts(""); - break; - case 2: - printf(working_buf, ctx->registers[R2], ctx->registers[R3]); - puts(""); - break; - case 3: - printf(working_buf, ctx->registers[R2], ctx->registers[R3], ctx->registers[R4]); - puts(""); - break; - default: - break; - } - - } - // WAIT (sleep) - else if (code == 5) - { - std::this_thread::sleep_for(std::chrono::milliseconds(ctx->registers[R0])); - } - - return retCode; -} - - - - - -TEST_CASE( "Check various indentations and typos" ) { - - std::vector program; - Chip32::Assembler assembler; - Chip32::Result result; - uint8_t data[8*1024]; - - bool parseResult = assembler.Parse(test1); - - std::cout << assembler.GetLastError().ToString() << std::endl; - - REQUIRE( parseResult == true ); - - REQUIRE( assembler.BuildBinary(program, result) == true); - result.Print(); - hexdump(program.data(), program.size()); - - chip32_ctx_t chip32_ctx; - - chip32_ctx.stack_size = 512; - - chip32_ctx.rom.mem = program.data(); - chip32_ctx.rom.addr = 0; - chip32_ctx.rom.size = program.size(); - - chip32_ctx.ram.mem = data; - chip32_ctx.ram.addr = 40 *1024; - chip32_ctx.ram.size = sizeof(data); - - chip32_ctx.syscall = story_player_syscall; - - chip32_initialize(&chip32_ctx); - chip32_result_t runResult = chip32_run(&chip32_ctx); - REQUIRE( runResult == VM_FINISHED ); -} - -// ==================================================================================================== -static const std::string testPrintf = R"( - -; ======================================================== -; We test the printf system call -; ======================================================== -$printHello DC8 "La réponse est %d" -$answer DC32 42 - -$counter DV32 10 +; ============================================================================ +; CODE +; ============================================================================ .main: + ; We create a loop for RAM variable testing + lcons r0, 4 ; prepare loop: 4 iterations + lcons r2, $RamData1 ; save in R2 a ram address + store @r2, r0, 4 ; save R0 in RAM + lcons r1, 1 - ; prepapre loop +.loop: + load r0, @r2, 4 ; load this variable + sub r0, r1 + store @r2, r0, 4 ; save R0 in RAM + skipz r0 ; skip loop if R0 == 0 + jump .loop - lcons t0, 1000 - lcons t1, 4 - .while R1 > 0 - .print "La valeur est: %d", $counter + ; Test spacing variations + mov r0, r2 ; copy R2 into R0 (blank space) + mov R0,R2 ; copy R2 into R0 (NO blank space) - mov r0, t0 ; wait time in ms for argument - syscall 5 ; wait call - - .endwhile - - halt - - - )"; +// ============================================================================ +// TEST CASE 1: Grammar and indentation with DV/DZ +// ============================================================================ + +TEST_CASE("Check various indentations and typos with DV/DZ") +{ + std::cout << "\n=== Test 1: Grammar and indentation ===" << std::endl; + + Chip32::Machine machine; + machine.QuickExecute(test1); + + // Verify results + REQUIRE(machine.parseResult == true); + REQUIRE(machine.buildResult == true); + REQUIRE(machine.runResult == VM_FINISHED); + + std::cout << "✓ Test 1 passed: Grammar and DV/DZ syntax" << std::endl; +} + +// ============================================================================ +// TEST 2: Printf with DV variable +// ============================================================================ + +static const std::string testPrintf = R"( +; ============================================================================ +; Test printf system call with DV variable +; ============================================================================ + +; ROM constants (DC) +$printHello DC8 "La réponse est %d" +$answer DC32 42 + +; RAM initialized variable (DV) +$counter DV32 10 ; Counter initialized to 10 + +; ============================================================================ +; CODE +; ============================================================================ + +.main: + ; Simple test - print the counter value + lcons r0, $printHello + lcons r1, 1 ; 1 argument + load r2, $counter, 4 ; Load counter value + syscall 4 ; Printf + + halt +)"; + +TEST_CASE("Test printf with DV variable") +{ + std::cout << "\n=== Test 2: Printf with DV ===" << std::endl; + + Chip32::Machine machine; + machine.QuickExecute(testPrintf); + + REQUIRE(machine.parseResult == true); + REQUIRE(machine.buildResult == true); + REQUIRE(machine.runResult == VM_FINISHED); + + std::cout << "✓ Test 2 passed: Printf with DV" << std::endl; +} + +// ============================================================================ +// TEST 3: Macro language with DV/DZ +// ============================================================================ static const std::string testMacro1 = R"( - %section_macro %macro incr 1 @@ -220,24 +159,22 @@ static const std::string testMacro1 = R"( pop t0 %endmacro - %macro print 2 - lcons r0, %1 ; string text - lcons r1, 1 ; number of arguments + lcons r0, %1 ; string text + lcons r1, 1 ; number of arguments mov r2, %2 syscall 4 %endmacro %macro LOOP_START 3 - lcons %2, %3 ; Initialise le compteur de boucle (registre spécifié) - %1_loop: ; Étiquette de début de boucle + lcons %2, %3 ; Initialize loop counter + %1_loop: ; Loop start label %endmacro - %macro LOOP_END 2 - subi %2, 1 ; Décrémente le registre spécifié + subi %2, 1 ; Decrement counter skipz %2 - jump %1_loop ; Saute si le registre n'est pas zéro + jump %1_loop ; Jump if not zero %endmacro %section_text @@ -248,76 +185,207 @@ static const std::string testMacro1 = R"( LOOP_START .myLoop, r6, 5 print $printHello, r3 LOOP_END .myLoop, r6 + halt %section_data +; ROM constant (DC) $printHello DC8 "Answer is %d" - + +; RAM zeroed buffer (DZ) +$tempBuffer DZ8 32 ; 32 bytes buffer for temporary data )"; -TEST_CASE( "Check assembly macro language part 1" ) +TEST_CASE("Check assembly macro language with DV/DZ") { - + std::cout << "\n=== Test 3: Macros with DV/DZ ===" << std::endl; + Chip32::ScriptProcessor processor; processor.process(testMacro1); - processor.generate_assembly(); std::string resultAsm = processor.GetResult(); - std::cout << "-----------------------------------------------------" << std::endl; + std::cout << "Generated Assembly:" << std::endl; std::cout << resultAsm << std::endl; - std::cout << "-----------------------------------------------------" << std::endl; -/* - const std::string& output_filename - std::ofstream out(output_filename); - if (!out) { - std::cerr << "Error creating file: " << output_filename << "\n"; - return; - } + Chip32::Machine machine; + machine.QuickExecute(resultAsm); - out.close(); -*/ + REQUIRE(machine.parseResult == true); + REQUIRE(machine.buildResult == true); + REQUIRE(machine.runResult == VM_FINISHED); + REQUIRE(machine.ctx.registers[R3] == 5); + + std::cout << "✓ Test 3 passed: Macros with DV/DZ" << std::endl; +} +// ============================================================================ +// TEST 4: DV vs DZ comprehensive test +// ============================================================================ - std::vector program; +static const std::string testDvVsDz = R"( +; ============================================================================ +; Comprehensive test: DV (initialized) vs DZ (zeroed) +; ============================================================================ + +; ROM constants (DC) +$appName DC8 "TestApp" +$version DC32 100 + +; RAM initialized variables (DV) - WITH VALUES +$counter DV32 42 ; int counter = 42 +$temperature DV32 20 ; int temp = 20 +$userName DV8 "Guest" ; char name[] = "Guest" +$flags DV8 1, 0, 1 ; uint8_t flags[] = {1, 0, 1} + +; RAM zeroed areas (DZ) - BUFFERS AND ARRAYS +$rxBuffer DZ8 128 ; uint8_t buffer[128] = {0} +$dataArray DZ32 50 ; int32_t array[50] = {0} +$workArea DZ8 256 ; uint8_t work[256] = {0} + +; ============================================================================ +; CODE +; ============================================================================ + +.main: + ; Test 1: DV counter should be 42 + load r0, $counter, 4 + ; r0 should be 42 + + ; Test 2: DV temperature should be 20 + load r1, $temperature, 4 + ; r1 should be 20 + + ; Test 3: DZ rxBuffer[0] should be 0 + lcons r2, $rxBuffer + load r3, @r2, 1 + ; r3 should be 0 + + ; Test 4: Write to DZ buffer + lcons r4, 0x42 + store @r2, r4, 1 + ; rxBuffer[0] = 0x42 + + ; Test 5: Read back modified value + load r5, @r2, 1 + ; r5 should be 0x42 + + halt +)"; + +TEST_CASE("DV vs DZ comprehensive test") +{ + std::cout << "\n=== Test 4: DV vs DZ comprehensive ===" << std::endl; + + Chip32::Machine machine; + machine.QuickExecute(testDvVsDz); + + REQUIRE(machine.parseResult == true); + REQUIRE(machine.buildResult == true); + REQUIRE(machine.runResult == VM_FINISHED); + + // Verify register values + std::cout << "\nRegister values after execution:" << std::endl; + std::cout << " R0 (counter) = " << machine.ctx.registers[R0] + << " (expected: 42)" << std::endl; + std::cout << " R1 (temperature) = " << machine.ctx.registers[R1] + << " (expected: 20)" << std::endl; + std::cout << " R3 (rxBuffer[0] initial) = " << machine.ctx.registers[R3] + << " (expected: 0)" << std::endl; + std::cout << " R5 (rxBuffer[0] after write) = " << machine.ctx.registers[R5] + << " (expected: 66)" << std::endl; + + REQUIRE(machine.ctx.registers[R0] == 42); // counter DV value + REQUIRE(machine.ctx.registers[R1] == 20); // temperature DV value + REQUIRE(machine.ctx.registers[R3] == 0); // rxBuffer DZ initial (zero) + REQUIRE(machine.ctx.registers[R5] == 0x42); // rxBuffer after write + + std::cout << "✓ Test 4 passed: DV vs DZ comprehensive" << std::endl; +} + +// ============================================================================ +// TEST 5: Binary format validation +// ============================================================================ + +static const std::string testBinaryFormat = R"( +; Test binary format with all section types + +; DC: ROM constants +$romString DC8 "Hello" +$romValue DC32 123 + +; DV: Initialized RAM variables +$ramCounter DV32 99 +$ramMessage DV8 "OK" + +; DZ: Zeroed RAM areas +$ramBuffer DZ8 64 +$ramArray DZ32 20 + +.main: + load r0, $ramCounter, 4 + halt +)"; + +TEST_CASE("Binary format validation") +{ + std::cout << "\n=== Test 5: Binary format validation ===" << std::endl; + + Chip32::Machine machine; + + // Parse and build Chip32::Assembler assembler; Chip32::Result result; - uint8_t data[8*1024]; - - bool parseResult = assembler.Parse(resultAsm); - - std::cout << assembler.GetLastError().ToString() << std::endl; - - REQUIRE( parseResult == true ); - - - REQUIRE( assembler.BuildBinary(program, result) == true); + std::vector program; + + REQUIRE(assembler.Parse(testBinaryFormat) == true); + REQUIRE(assembler.BuildBinary(program, result) == true); + + std::cout << "\nBinary statistics:" << std::endl; result.Print(); - hexdump(program.data(), program.size()); - - chip32_ctx_t chip32_ctx; - - chip32_ctx.stack_size = 512; - - chip32_ctx.rom.mem = program.data(); - chip32_ctx.rom.addr = 0; - chip32_ctx.rom.size = program.size(); - - chip32_ctx.ram.mem = data; - chip32_ctx.ram.addr = 40 *1024; - chip32_ctx.ram.size = sizeof(data); - - chip32_ctx.syscall = story_player_syscall; - - chip32_initialize(&chip32_ctx); - chip32_result_t runResult = chip32_run(&chip32_ctx); - REQUIRE( runResult == VM_FINISHED ); - - REQUIRE( chip32_ctx.registers[R3] == 5); + // Validate binary format + chip32_loaded_binary_t loaded; + chip32_binary_error_t error = chip32_binary_load( + program.data(), + static_cast(program.size()), + &loaded + ); - + REQUIRE(error == CHIP32_BIN_OK); + + std::cout << "\nBinary header:" << std::endl; + chip32_binary_print_header(&loaded.header); + + // Verify header values + REQUIRE(loaded.header.magic == CHIP32_MAGIC); + REQUIRE(loaded.header.version == CHIP32_VERSION); + REQUIRE((loaded.header.flags & CHIP32_FLAG_HAS_INIT_DATA) != 0); + REQUIRE(loaded.header.data_size > 0); // Has ROM constants + REQUIRE(loaded.header.bss_size > 0); // Has RAM + REQUIRE(loaded.header.code_size > 0); // Has code + REQUIRE(loaded.header.init_data_size == loaded.header.bss_size); // Must match + + std::cout << "\nBinary format validations:" << std::endl; + std::cout << " ✓ Magic number correct" << std::endl; + std::cout << " ✓ Version correct" << std::endl; + std::cout << " ✓ Has init data flag set" << std::endl; + std::cout << " ✓ All sections present" << std::endl; + std::cout << " ✓ INIT DATA size matches BSS size" << std::endl; + + // Initialize RAM and verify + std::vector ram(loaded.header.bss_size); + uint32_t init_bytes = chip32_binary_init_ram(&loaded, ram.data(), ram.size()); + + REQUIRE(init_bytes == loaded.header.bss_size); + + // Verify DV ramCounter is initialized to 99 + uint32_t ramCounter = *reinterpret_cast(&ram[0]); + std::cout << " ✓ DV ramCounter = " << ramCounter << " (expected: 99)" << std::endl; + REQUIRE(ramCounter == 99); + + std::cout << "\n✓ Test 5 passed: Binary format validation" << std::endl; } + diff --git a/story-editor/CMakeLists.txt b/story-editor/CMakeLists.txt index 8f5908f..aa0bb24 100644 --- a/story-editor/CMakeLists.txt +++ b/story-editor/CMakeLists.txt @@ -194,6 +194,7 @@ set(SRCS ../core/chip32/chip32_assembler.cpp ../core/chip32/chip32_vm.c + ../core/chip32/chip32_binary_format.c # Add lua source code files