Add DATA and BSS RAM sections, crate binary format with header
Some checks failed
Build-StoryEditor / build_win32 (push) Has been cancelled
Build-StoryEditor / build_linux (push) Has been cancelled

This commit is contained in:
anthony@rabine.fr 2025-10-07 08:50:31 +02:00
parent 741a3c633e
commit 9ab7b9bb14
9 changed files with 1261 additions and 363 deletions

View file

@ -24,6 +24,7 @@ THE SOFTWARE.
#include "chip32_assembler.h" #include "chip32_assembler.h"
#include "chip32_binary_format.h"
#include <sstream> #include <sstream>
#include <vector> #include <vector>
@ -32,6 +33,7 @@ THE SOFTWARE.
#include <cstdint> #include <cstdint>
#include <iterator> #include <iterator>
#include <string> #include <string>
#include <set>
namespace Chip32 namespace Chip32
{ {
@ -358,29 +360,197 @@ bool Assembler::CompileConstantArgument(Instr &instr, const std::string &a)
bool Assembler::BuildBinary(std::vector<uint8_t> &program, Result &result) bool Assembler::BuildBinary(std::vector<uint8_t> &program, Result &result)
{ {
program.clear(); program.clear();
result = { 0, 0, 0}; // clear stuff! result = { 0, 0, 0};
// serialize each instruction and arguments to program memory, assign address to variables (rom or ram) // ========================================================================
for (auto &i : m_instructions) // PHASE 1: Créer les sections temporaires
// ========================================================================
std::vector<uint8_t> dataSection; // DC - ROM constants only
std::vector<uint8_t> codeSection; // Executable code
std::vector<uint8_t> 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<uint16_t, std::vector<uint8_t>> 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; uint16_t ramAddr = instr.addr;
} uint16_t romInitAddr = instr.romInitAddr;
else uint16_t dataLen = instr.dataLen;
{
if (i.isRomCode()) // Collecter les données d'init depuis les instructions précédentes
std::vector<uint8_t> 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
{ dvInitData[ramAddr] = initData;
result.constantsSize += i.compiledArgs.size();
}
std::copy (i.compiledArgs.begin(), i.compiledArgs.end(), std::back_inserter(program));
} }
} }
result.romUsageSize = program.size();
// ========================================================================
// PHASE 3: Deuxième passe - dispatcher dans les bonnes sections
// ========================================================================
// Track which ROM data belongs to DV variables
std::set<uint16_t> 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<uint32_t>(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<uint8_t> &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<uint32_t>(dataSection.size());
header.bss_size = bssSize;
header.code_size = static_cast<uint32_t>(codeSection.size());
header.entry_point = entryPoint;
header.init_data_size = static_cast<uint32_t>(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<uint32_t>(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; return true;
} }
@ -474,44 +644,97 @@ bool Assembler::Parse(const std::string &data)
std::string type = lineParts[1]; std::string type = lineParts[1];
CHIP32_CHECK(instr, (type.size() >= 3), "bad data type size"); 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); CHIP32_CHECK(instr, m_labels.count(opcode) == 0, "duplicated label : " + opcode);
instr.isRomData = type[1] == 'C' ? true : false; // Parse data type size (8, 16, or 32)
instr.isRamData = type[1] == 'V' ? true : false;
type.erase(0, 2); type.erase(0, 2);
instr.dataTypeSize = static_cast<uint32_t>(strtol(type.c_str(), NULL, 0)); instr.dataTypeSize = static_cast<uint32_t>(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) if (instr.isRomData)
{ {
instr.addr = code_addr; instr.addr = code_addr;
m_labels[opcode] = instr; // location of the start of the data 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++) 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); m_instructions.push_back(instr);
code_addr += instr.compiledArgs.size(); code_addr += instr.compiledArgs.size();
instr.addr = code_addr; 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.addr = ram_addr;
instr.dataLen = static_cast<uint16_t>(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<uint32_t>(strtol(lineParts[2].c_str(), NULL, 0));
instr.dataLen = static_cast<uint16_t>(numElements * (instr.dataTypeSize / 8));
ram_addr += instr.dataLen; ram_addr += instr.dataLen;
m_labels[opcode] = instr; m_labels[opcode] = instr;
m_instructions.push_back(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 // 2. Second pass: replace all label or RAM data by the real address in memory

View file

@ -50,12 +50,14 @@ struct Instr {
uint16_t dataTypeSize{0}; uint16_t dataTypeSize{0};
uint16_t dataLen{0}; uint16_t dataLen{0};
bool isLabel{false}; //!< If true, this is a label, otherwise it is an instruction 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 useLabel{false}; //!< If true, the instruction uses a label
bool isRomData{false}; //!< True is constant data in program bool isRomData{false}; //!< True if constant data in ROM (DC)
bool isRamData{false}; //!< True is constant data in program 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); } bool isRomCode() const { return !(isLabel || isRomData || isRamData); }
}; };

View file

@ -0,0 +1,287 @@
/*
* Chip32 Binary Format - Implementation
*/
#include "chip32_binary_format.h"
#include <stdio.h>
// 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");
}

View file

@ -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 <stdint.h>
#include <string.h>
#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

View file

@ -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 #pragma once
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <sstream>
#include <functional>
#include <cstring>
#include "chip32_assembler.h" #include "chip32_assembler.h"
#include "chip32_binary_format.h"
#include "chip32_macros.h" #include "chip32_macros.h"
// Dans chip32_machine.h
namespace Chip32 namespace Chip32
{ {
@ -22,8 +27,7 @@ public:
bool buildResult{false}; bool buildResult{false};
chip32_result_t runResult{VM_OK}; chip32_result_t runResult{VM_OK};
std::string printOutput; std::string printOutput;
chip32_ctx_t ctx; // Public pour accès aux registres dans les tests
static Machine *m_instance;
Machine() { Machine() {
// Bind syscall handler to this instance // Bind syscall handler to this instance
@ -32,7 +36,94 @@ public:
std::placeholders::_2); 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<uint8_t> 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<uint32_t>(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) static std::string GetStringFromMemory(chip32_ctx_t *ctx, uint32_t addr)
{ {
if (!ctx) { if (!ctx) {
@ -91,72 +182,41 @@ public:
// Vérifier si on a assez d'arguments // Vérifier si on a assez d'arguments
if (argIndex >= static_cast<int>(args.size())) { if (argIndex >= static_cast<int>(args.size())) {
result << "{" << argIndex << ":?}"; // Argument manquant result << "{" << argIndex << ":?}";
pos += 2; pos += 3;
continue; continue;
} }
uint32_t argValue = args[argIndex]; // 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];
// Vérifier s'il y a un type spécifié {:d}, {:s}, {:f}, {:x} if (typeChar == 's') {
if (pos + 3 < format.length() && format[pos + 2] == ':') { // String: argValue est une adresse
char typeChar = format[pos + 3]; try {
std::string str = GetStringFromMemory(ctx, argValue);
// Vérifier si le placeholder se termine bien par '}' result << str;
if (pos + 4 < format.length() && format[pos + 4] == '}') { pos += 5; // Avancer de "{0:s}"
// Parser le type et formater continue;
switch (typeChar) { } catch (const std::exception& e) {
case 'd': // Entier décimal signé result << "{str_error}";
case 'i': pos += 5;
result << static_cast<int32_t>(argValue); continue;
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 << "<error:0x" << std::hex << argValue << std::dec << ">";
}
break;
case 'f': // Float
{
float floatValue;
std::memcpy(&floatValue, &argValue, sizeof(float));
result << floatValue;
break;
} }
} else if (typeChar == 'i' || typeChar == 'd') {
case 'c': // Caractère // Integer
result << static_cast<char>(argValue); result << static_cast<int32_t>(argValue);
break; pos += 5;
continue;
default:
// Type inconnu, afficher tel quel
result << "{" << argIndex << ":" << typeChar << "}";
} }
pos += 5; // Avancer de "{0:d}"
continue;
} }
} } else if (pos + 2 < format.length() && format[pos + 2] == '}') {
// Format court {0} sans type → défaut: entier // Format simple {0} - traiter comme int par défaut
else if (pos + 2 < format.length() && format[pos + 2] == '}') { uint32_t argValue = args[argIndex];
result << static_cast<int32_t>(argValue); result << static_cast<int32_t>(argValue);
pos += 3; // Avancer de "{0}" pos += 3;
continue; continue;
} }
} }
@ -170,7 +230,10 @@ public:
return result.str(); return result.str();
} }
// Handler de syscall (méthode membre, non statique) // ========================================================================
// Syscall handler
// ========================================================================
uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code) uint8_t HandleSyscall(chip32_ctx_t *ctx, uint8_t code)
{ {
try { 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<uint8_t> &binary,
chip32_ctx_t &vm_ctx,
uint8_t *ram_buffer,
uint32_t ram_size)
{ {
std::vector<uint8_t> program; // Charger et valider le binaire
Chip32::Assembler assembler; chip32_loaded_binary_t loaded;
Chip32::Result result; chip32_binary_error_t err = chip32_binary_load(
std::vector<uint8_t> data(8*1024); binary.data(),
static_cast<uint32_t>(binary.size()),
&loaded
);
parseResult = assembler.Parse(assemblyCode); if (err != CHIP32_BIN_OK)
std::cout << assembler.GetLastError().ToString() << std::endl; {
std::cerr << "Binary load error: " << chip32_binary_error_string(err) << std::endl;
buildResult = assembler.BuildBinary(program, result); return false;
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;
} }
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<uint8_t*>(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: private:
// std::function contenant le bind std::vector<uint8_t> m_ram; // RAM storage
std::function<uint8_t(chip32_ctx_t*, uint8_t)> m_syscallHandler; std::function<uint8_t(chip32_ctx_t*, uint8_t)> m_syscallHandler;
// Wrapper statique qui récupère l'instance et appelle la méthode membre // Wrapper statique qui récupère l'instance et appelle la méthode membre

View file

@ -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::cout << "\n=== Generating all TAC programs ===\n";
std::map<std::string, TACProgram> pageTACPrograms; std::map<std::string, TACProgram> pageTACPrograms;
@ -491,7 +491,7 @@ bool StoryProject::GenerateCompleteProgram(std::string &assembly)
generator.GenerateGlobalVariables(); generator.GenerateGlobalVariables();
// Constantes de tous les nœuds de toutes les pages // 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); generator.GenerateNodesVariables(allNodes);
// === SECTION TEXT (chaque page = une fonction) === // === SECTION TEXT (chaque page = une fonction) ===

View file

@ -22,6 +22,7 @@ add_executable(${PROJECT_NAME}
../chip32/chip32_assembler.cpp ../chip32/chip32_assembler.cpp
../chip32/chip32_vm.c ../chip32/chip32_vm.c
../chip32/chip32_binary_format.c
) )
target_include_directories(${PROJECT_NAME} PRIVATE target_include_directories(${PROJECT_NAME} PRIVATE

View file

@ -23,194 +23,133 @@ THE SOFTWARE.
*/ */
#include <iostream> #include <iostream>
#include <thread>
#include "catch.hpp" #include "catch.hpp"
#include "chip32_assembler.h" #include "chip32_machine.h"
#include "chip32_macros.h" #include "chip32_binary_format.h"
/* /*
Purpose: grammar, ram usage and macros, rom code generation 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); void hexdump(void *ptr, int buflen);
// ============================================================================
// TEST 1: Basic grammar with DV/DZ
// ============================================================================
static const std::string test1 = R"( static const std::string test1 = R"(
; label definition ; ============================================================================
.main: ;; comment here should work ; Test grammar with new DV/DZ syntax
; We create a stupid loop just for RAM variable testing ; ============================================================================
lcons r0, 4 ; prepare loop: 4 iterations ; ROM constants (DC)
lcons r2, $RamData1 ; save in R2 a ram address $imageBird DC8 "example.bmp"
store @r2, r0, 4 ; save R0 in RAM $someConstant DC32 12456789
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
; 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) ; RAM zeroed areas (DZ)
mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2) $MyArray DZ8 10 ; Array of 10 bytes (zeroed)
$WorkBuffer DZ8 64 ; Buffer of 64 bytes (zeroed)
halt ; ============================================================================
; CODE
$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 <stdarg.h>
#include <string.h>
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<uint8_t> 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
.main: .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
lcons t0, 1000 sub r0, r1
lcons t1, 4 store @r2, r0, 4 ; save R0 in RAM
.while R1 > 0 skipz r0 ; skip loop if R0 == 0
.print "La valeur est: %d", $counter jump .loop
mov r0, t0 ; wait time in ms for argument
syscall 5 ; wait call
.endwhile
; Test spacing variations
mov r0, r2 ; copy R2 into R0 (blank space)
mov R0,R2 ; copy R2 into R0 (NO blank space)
halt 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"( static const std::string testMacro1 = R"(
%section_macro %section_macro
%macro incr 1 %macro incr 1
@ -220,24 +159,22 @@ static const std::string testMacro1 = R"(
pop t0 pop t0
%endmacro %endmacro
%macro print 2 %macro print 2
lcons r0, %1 ; string text lcons r0, %1 ; string text
lcons r1, 1 ; number of arguments lcons r1, 1 ; number of arguments
mov r2, %2 mov r2, %2
syscall 4 syscall 4
%endmacro %endmacro
%macro LOOP_START 3 %macro LOOP_START 3
lcons %2, %3 ; Initialise le compteur de boucle (registre spécifié) lcons %2, %3 ; Initialize loop counter
%1_loop: ; Étiquette de début de boucle %1_loop: ; Loop start label
%endmacro %endmacro
%macro LOOP_END 2 %macro LOOP_END 2
subi %2, 1 ; Décrémente le registre spécifié subi %2, 1 ; Decrement counter
skipz %2 skipz %2
jump %1_loop ; Saute si le registre n'est pas ro jump %1_loop ; Jump if not zero
%endmacro %endmacro
%section_text %section_text
@ -248,76 +185,207 @@ static const std::string testMacro1 = R"(
LOOP_START .myLoop, r6, 5 LOOP_START .myLoop, r6, 5
print $printHello, r3 print $printHello, r3
LOOP_END .myLoop, r6 LOOP_END .myLoop, r6
halt halt
%section_data %section_data
; ROM constant (DC)
$printHello DC8 "Answer is %d" $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; Chip32::ScriptProcessor processor;
processor.process(testMacro1); processor.process(testMacro1);
processor.generate_assembly(); processor.generate_assembly();
std::string resultAsm = processor.GetResult(); std::string resultAsm = processor.GetResult();
std::cout << "-----------------------------------------------------" << std::endl; std::cout << "Generated Assembly:" << std::endl;
std::cout << resultAsm << std::endl; std::cout << resultAsm << std::endl;
std::cout << "-----------------------------------------------------" << std::endl;
/* Chip32::Machine machine;
const std::string& output_filename machine.QuickExecute(resultAsm);
std::ofstream out(output_filename);
if (!out) {
std::cerr << "Error creating file: " << output_filename << "\n";
return;
}
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;
}
std::vector<uint8_t> program; // ============================================================================
// TEST 4: DV vs DZ comprehensive test
// ============================================================================
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::Assembler assembler;
Chip32::Result result; Chip32::Result result;
uint8_t data[8*1024]; std::vector<uint8_t> program;
bool parseResult = assembler.Parse(resultAsm); REQUIRE(assembler.Parse(testBinaryFormat) == true);
REQUIRE(assembler.BuildBinary(program, result) == true);
std::cout << assembler.GetLastError().ToString() << std::endl; std::cout << "\nBinary statistics:" << std::endl;
REQUIRE( parseResult == true );
REQUIRE( assembler.BuildBinary(program, result) == true);
result.Print(); result.Print();
hexdump(program.data(), program.size());
chip32_ctx_t chip32_ctx; // Validate binary format
chip32_loaded_binary_t loaded;
chip32_binary_error_t error = chip32_binary_load(
program.data(),
static_cast<uint32_t>(program.size()),
&loaded
);
chip32_ctx.stack_size = 512; REQUIRE(error == CHIP32_BIN_OK);
chip32_ctx.rom.mem = program.data(); std::cout << "\nBinary header:" << std::endl;
chip32_ctx.rom.addr = 0; chip32_binary_print_header(&loaded.header);
chip32_ctx.rom.size = program.size();
chip32_ctx.ram.mem = data; // Verify header values
chip32_ctx.ram.addr = 40 *1024; REQUIRE(loaded.header.magic == CHIP32_MAGIC);
chip32_ctx.ram.size = sizeof(data); 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
chip32_ctx.syscall = story_player_syscall; 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;
chip32_initialize(&chip32_ctx); // Initialize RAM and verify
chip32_result_t runResult = chip32_run(&chip32_ctx); std::vector<uint8_t> ram(loaded.header.bss_size);
uint32_t init_bytes = chip32_binary_init_ram(&loaded, ram.data(), ram.size());
REQUIRE( runResult == VM_FINISHED ); REQUIRE(init_bytes == loaded.header.bss_size);
REQUIRE( chip32_ctx.registers[R3] == 5);
// Verify DV ramCounter is initialized to 99
uint32_t ramCounter = *reinterpret_cast<uint32_t*>(&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;
} }

View file

@ -194,6 +194,7 @@ set(SRCS
../core/chip32/chip32_assembler.cpp ../core/chip32/chip32_assembler.cpp
../core/chip32/chip32_vm.c ../core/chip32/chip32_vm.c
../core/chip32/chip32_binary_format.c
# Add lua source code files # Add lua source code files