mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
Chip32: added two instructions and a macro pre-processor
This commit is contained in:
parent
9cd7956630
commit
bbf149dedd
6 changed files with 553 additions and 74 deletions
|
|
@ -54,7 +54,7 @@ static const uint32_t NbRegs = sizeof(AllRegs) / sizeof(AllRegs[0]);
|
|||
|
||||
// Keep same order than the opcodes list!!
|
||||
static const std::string Mnemonics[] = {
|
||||
"nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "sub", "mul", "div",
|
||||
"nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "addi", "sub", "subi", "mul", "div",
|
||||
"shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "jumpr", "skipz", "skipnz",
|
||||
"eq", "gt", "lt"
|
||||
};
|
||||
|
|
@ -104,38 +104,8 @@ static inline void leu16_put(std::vector<std::uint8_t> &container, uint16_t data
|
|||
m_lastError.message = error; \
|
||||
return false; } \
|
||||
|
||||
std::vector<std::string> Split(std::string line)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::istringstream iss(line);
|
||||
std::string token;
|
||||
|
||||
while (std::getline(iss, token, ' ')) {
|
||||
// Vérifier si le jeton contient une virgule
|
||||
size_t comma_pos = token.find(",");
|
||||
if (comma_pos != std::string::npos) {
|
||||
// Diviser le jeton en deux parties séparées par la virgule
|
||||
std::string first_token = token.substr(0, comma_pos);
|
||||
std::string second_token = token.substr(comma_pos + 1);
|
||||
// Ajouter chaque partie au vecteur de résultats
|
||||
if (!first_token.empty()) {
|
||||
result.push_back(first_token);
|
||||
}
|
||||
if (!second_token.empty()) {
|
||||
result.push_back(second_token);
|
||||
}
|
||||
} else {
|
||||
// Ajouter le jeton entier au vecteur de résultats
|
||||
if (!token.empty()) {
|
||||
result.push_back(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t convertStringToLong(const std::string& str) {
|
||||
static uint32_t convertStringToLong(const std::string& str) {
|
||||
char* end;
|
||||
if (str.compare(0, 2, "0x") == 0 || str.compare(0, 2, "0X") == 0) {
|
||||
return static_cast<uint32_t>(strtol(str.c_str(), &end, 16));
|
||||
|
|
@ -163,6 +133,38 @@ bool Assembler::GetRegister(const std::string ®Name, uint8_t ®)
|
|||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> Assembler::Split(const std::string &line)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::string current;
|
||||
bool inQuotes = false;
|
||||
|
||||
for (char c : line) {
|
||||
if (c == '"') {
|
||||
// Si on rencontre un guillemet, on change l'état
|
||||
inQuotes = !inQuotes;
|
||||
current += c;
|
||||
}
|
||||
else if ((c == ' ' || c == ',') && !inQuotes) {
|
||||
// Si on rencontre un espace ou une virgule en dehors des guillemets
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
current.clear();
|
||||
}
|
||||
} else {
|
||||
// Sinon, on ajoute le caractère au "current"
|
||||
current += c;
|
||||
}
|
||||
}
|
||||
|
||||
// Ajout du dernier morceau, s'il existe
|
||||
if (!current.empty()) {
|
||||
result.push_back(current);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Assembler::GetRegisterName(uint8_t reg, std::string ®Name)
|
||||
{
|
||||
for (uint32_t i = 0; i < NbRegs; i++)
|
||||
|
|
@ -227,6 +229,19 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
|
|||
instr.compiledArgs.push_back(ra);
|
||||
instr.compiledArgs.push_back(rb);
|
||||
break;
|
||||
case OP_ADDI:
|
||||
case OP_SUBI:
|
||||
{
|
||||
GET_REG(instr.args[0], ra);
|
||||
instr.compiledArgs.push_back(ra);
|
||||
|
||||
uint32_t op = convertStringToLong(instr.args[1]);
|
||||
if (op > 255) {
|
||||
return false;
|
||||
}
|
||||
leu32_put(instr.compiledArgs, op);
|
||||
break;
|
||||
}
|
||||
case OP_JUMP:
|
||||
// Reserve 2 bytes for address, it will be filled at the end
|
||||
instr.useLabel = true;
|
||||
|
|
@ -243,14 +258,37 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
|
|||
instr.compiledArgs.push_back(static_cast<uint32_t>(strtol(instr.args[2].c_str(), NULL, 0)));
|
||||
break;
|
||||
case OP_LOAD:
|
||||
CHIP32_CHECK(instr, instr.args[1].at(0) == '@', "Missing @ sign before register")
|
||||
instr.args[1].erase(0, 1);
|
||||
GET_REG(instr.args[0], ra);
|
||||
GET_REG(instr.args[1], rb);
|
||||
instr.compiledArgs.push_back(ra);
|
||||
instr.compiledArgs.push_back(rb);
|
||||
instr.compiledArgs.push_back(static_cast<uint32_t>(strtol(instr.args[2].c_str(), NULL, 0)));
|
||||
{
|
||||
// We allow two forms of writing:
|
||||
// - load r0, @R1, 4 ; the address is located in a register
|
||||
// - load r0, $variable, 4 ; we use the variable address to get the value
|
||||
//
|
||||
char prefix = instr.args[1].at(0);
|
||||
|
||||
// Register based
|
||||
if (prefix == '@')
|
||||
{
|
||||
|
||||
instr.args[1].erase(0, 1);
|
||||
GET_REG(instr.args[0], ra);
|
||||
GET_REG(instr.args[1], rb);
|
||||
instr.compiledArgs.push_back(ra);
|
||||
instr.compiledArgs.push_back(rb);
|
||||
instr.compiledArgs.push_back(static_cast<uint32_t>(strtol(instr.args[2].c_str(), NULL, 0)));
|
||||
}
|
||||
// Variable based
|
||||
else if (prefix == '$')
|
||||
{
|
||||
instr.useLabel = true;
|
||||
leu32_put(instr.compiledArgs, 0); // reserve 4 bytes
|
||||
}
|
||||
else
|
||||
{
|
||||
CHIP32_CHECK(instr, false, "Load source address must be @reg or $variable");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case OP_CMP_EQ:
|
||||
case OP_CMP_GT:
|
||||
case OP_CMP_LT:
|
||||
|
|
@ -415,7 +453,7 @@ bool Assembler::Parse(const std::string &data)
|
|||
|
||||
if (nbArgsSuccess)
|
||||
{
|
||||
CHIP32_CHECK(instr, CompileMnemonicArguments(instr) == true, "Compile failure");
|
||||
CHIP32_CHECK(instr, CompileMnemonicArguments(instr) == true, "Compile failure, mnemonic or arguments");
|
||||
instr.addr = code_addr;
|
||||
code_addr += 1 + instr.compiledArgs.size();
|
||||
m_instructions.push_back(instr);
|
||||
|
|
@ -478,16 +516,20 @@ bool Assembler::Parse(const std::string &data)
|
|||
{
|
||||
if (instr.useLabel && (instr.args.size() > 0))
|
||||
{
|
||||
// label is the first argument for jump, second position for LCONS
|
||||
uint16_t argsIndex = instr.code.opcode == OP_LCONS ? 1 : 0;
|
||||
// label is the first argument for jump, second position for LCONS and LOAD
|
||||
uint16_t argsIndex = 1;
|
||||
if (instr.code.opcode == OP_JUMP) {
|
||||
argsIndex = 0;
|
||||
}
|
||||
std::string label = instr.args[argsIndex];
|
||||
CHIP32_CHECK(instr, m_labels.count(label) > 0, "label not found: " + label);
|
||||
uint16_t addr = m_labels[label].addr;
|
||||
std::cout << "LABEL: " << label << " , addr: " << addr << std::endl;
|
||||
instr.compiledArgs[argsIndex] = addr & 0xFF;
|
||||
instr.compiledArgs[argsIndex+1] = (addr >> 8U) & 0xFF;
|
||||
if (instr.code.opcode == OP_LCONS) {
|
||||
if ((instr.code.opcode == OP_LCONS) || (instr.code.opcode == OP_LOAD)) {
|
||||
// We precise if the address is from RAM or ROM
|
||||
// For that, the MSB is set to 1 (for RAM)
|
||||
instr.compiledArgs[argsIndex+3] = m_labels[label].isRamData ? 0x80 : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,8 +89,13 @@ public:
|
|||
|
||||
struct Error {
|
||||
std::string message;
|
||||
int line;
|
||||
std::string ToString() const { return "Error line " + std::to_string(line) + ", " + message; }
|
||||
int line{-1};
|
||||
std::string ToString() const {
|
||||
if (line < 0)
|
||||
return "No error";
|
||||
else
|
||||
return "Error line " + std::to_string(line) + ", " + message;
|
||||
}
|
||||
};
|
||||
|
||||
// Separated parser to allow only code check
|
||||
|
|
@ -103,6 +108,8 @@ public:
|
|||
m_instructions.clear();
|
||||
}
|
||||
|
||||
static std::vector<std::string> Split(const std::string &line);
|
||||
|
||||
std::vector<Instr>::const_iterator Begin() { return m_instructions.begin(); }
|
||||
std::vector<Instr>::const_iterator End() { return m_instructions.end(); }
|
||||
|
||||
|
|
|
|||
195
core/chip32/chip32_macros.h
Normal file
195
core/chip32/chip32_macros.h
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Anthony Rabine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "chip32_assembler.h"
|
||||
|
||||
namespace Chip32
|
||||
{
|
||||
|
||||
struct Macro {
|
||||
int param_count;
|
||||
std::vector<std::string> body;
|
||||
};
|
||||
|
||||
class ScriptProcessor {
|
||||
public:
|
||||
void process(const std::string &text) {
|
||||
|
||||
resultAsm.clear();
|
||||
|
||||
std::stringstream data_stream(text);
|
||||
std::string line;
|
||||
|
||||
while(std::getline(data_stream, line))
|
||||
{
|
||||
line = trim(line);
|
||||
parseLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the expanded assembly file
|
||||
// without any macro directives
|
||||
std::string GetResult() const {
|
||||
return resultAsm;
|
||||
}
|
||||
|
||||
void generate_assembly() {
|
||||
|
||||
std::stringstream out;
|
||||
|
||||
for (const std::string& line : text_section) {
|
||||
out << line << "\n";
|
||||
}
|
||||
|
||||
for (const std::string& line : data_section) {
|
||||
out << line << "\n";
|
||||
}
|
||||
resultAsm = out.str();
|
||||
}
|
||||
|
||||
private:
|
||||
std::stack<int> conditionStack;
|
||||
std::stack<int> loopStack;
|
||||
std::string resultAsm;
|
||||
Macro* current_macro = nullptr;
|
||||
|
||||
|
||||
std::unordered_map<std::string, Macro> macros;
|
||||
std::vector<std::string> text_section;
|
||||
std::vector<std::string> data_section;
|
||||
bool in_data_section = false;
|
||||
bool in_macro_section = false;
|
||||
|
||||
|
||||
int labelCounter = 0;
|
||||
int printCounter = 0;
|
||||
|
||||
std::string trim(const std::string& str) {
|
||||
size_t first = str.find_first_not_of(" \t");
|
||||
if (first == std::string::npos) return "";
|
||||
size_t last = str.find_last_not_of(" \t");
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
|
||||
std::string expand_macro(const std::string& line, const std::vector<std::string>& args) {
|
||||
std::string expanded = line;
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
std::string placeholder = "%" + std::to_string(i + 1);
|
||||
expanded = std::regex_replace(expanded, std::regex(placeholder), args[i]);
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
void createLabel()
|
||||
{
|
||||
int label = labelCounter++;
|
||||
loopStack.push(label);
|
||||
std::cout << "L" << label << "_START:" << std::endl;
|
||||
}
|
||||
|
||||
void parseLine(std::string& line) {
|
||||
line = std::regex_replace(line, std::regex("^ +| +$"), ""); // Trim spaces
|
||||
if (line.empty() || line[0] == ';') return;
|
||||
|
||||
if ((line.find("%macro") == 0)) {
|
||||
|
||||
if (!in_macro_section) {
|
||||
std::cerr << "Error: Macros must be located in macro section\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::istringstream ss(line);
|
||||
std::string _, name;
|
||||
int param_count;
|
||||
ss >> _ >> name >> param_count;
|
||||
macros[name] = { param_count, {} };
|
||||
current_macro = ¯os[name];
|
||||
} else if (line.find("%endmacro") == 0) {
|
||||
current_macro = nullptr;
|
||||
} else if (current_macro) {
|
||||
current_macro->body.push_back(line);
|
||||
} else if (line.find("%section_macro") == 0) {
|
||||
in_macro_section = true;
|
||||
|
||||
} else if (line.find("%section_text") == 0) {
|
||||
|
||||
in_macro_section = false;
|
||||
in_data_section = false;
|
||||
} else if (line.find("%section_data") == 0) {
|
||||
|
||||
in_macro_section = false;
|
||||
in_data_section = true;
|
||||
} else {
|
||||
std::vector<std::string> args = Assembler::Split(line);
|
||||
|
||||
if (args.size() > 0) {
|
||||
std::string name = args[0];
|
||||
args.erase(args.begin());
|
||||
|
||||
if ((name.find("DC") == 0 || name.find("DV") == 0) && !in_data_section) {
|
||||
std::cerr << "Error: Data declarations (DC/DV) must be inside section .data\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (macros.find(name) != macros.end()) {
|
||||
Macro& macro = macros[name];
|
||||
|
||||
if (args.size() != macro.param_count) {
|
||||
std::cerr << "Error: Macro " << name << " expects " << macro.param_count << " arguments, got " << args.size() << "\n";
|
||||
return;
|
||||
}
|
||||
for (const std::string& body_line : macro.body) {
|
||||
text_section.push_back(expand_macro(body_line, args));
|
||||
}
|
||||
} else {
|
||||
if (in_data_section) {
|
||||
data_section.push_back(line);
|
||||
} else {
|
||||
text_section.push_back(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Macro problem with this line\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace Chip32
|
||||
|
|
@ -65,14 +65,14 @@ static inline uint32_t _NEXT_INT (chip32_ctx_t *ctx)
|
|||
#define _CHECK_REGISTER_VALID(r) \
|
||||
if (r >= REGISTER_COUNT) \
|
||||
return VM_ERR_INVALID_REGISTER;
|
||||
|
||||
#define _CHECK_CAN_PUSH(n) \
|
||||
if (ctx->registers[SP] - (n * sizeof(uint32_t)) > ctx->ram.addr) \
|
||||
if (ctx->registers[SP] - (n * sizeof(uint32_t)) < 0) \
|
||||
return VM_ERR_STACK_OVERFLOW;
|
||||
|
||||
#define _CHECK_CAN_POP(n) \
|
||||
if (ctx->registers[SP] + (n * sizeof(uint32_t)) > (ctx->ram.addr + ctx->ram.size)) \
|
||||
return VM_ERR_STACK_UNDERFLOW; \
|
||||
if (ctx->registers[SP] < ctx->prog_size) \
|
||||
return VM_ERR_STACK_OVERFLOW;
|
||||
if ((ctx->registers[SP] + (n * sizeof(uint32_t))) > (ctx->ram.size)) \
|
||||
return VM_ERR_STACK_UNDERFLOW;
|
||||
#else
|
||||
#define _CHECK_ROM_ADDR_VALID(a)
|
||||
#define _CHECK_BYTES_AVAIL(n)
|
||||
|
|
@ -266,6 +266,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ctx->registers[reg1] + ctx->registers[reg2];
|
||||
break;
|
||||
}
|
||||
case OP_ADDI:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t val = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
ctx->registers[reg1] = ctx->registers[reg1] + val;
|
||||
break;
|
||||
}
|
||||
case OP_SUB:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
|
|
@ -275,6 +283,14 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ctx->registers[reg1] - ctx->registers[reg2];
|
||||
break;
|
||||
}
|
||||
case OP_SUBI:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
const uint8_t val = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
ctx->registers[reg1] = ctx->registers[reg1] - val;
|
||||
break;
|
||||
}
|
||||
case OP_MUL:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
|
|
|
|||
|
|
@ -59,31 +59,33 @@ typedef enum
|
|||
|
||||
// arithmetic:
|
||||
OP_ADD = 9, ///< sum and store in first reg, e.g.: add r0, r2
|
||||
OP_SUB = 10, ///< subtract and store in first reg, e.g.: sub r0, r2
|
||||
OP_MUL = 11, ///< multiply and store in first reg, e.g.: mul r0, r2
|
||||
OP_DIV = 12, ///< divide and store in first reg, remain in second, e.g.: div r0, r2
|
||||
OP_ADDI = 10, ///< Add immediate value (8 bits max), e.g.: addi r4, 2 ; r4 = r4 + 2
|
||||
OP_SUB = 11, ///< subtract and store in first reg, e.g.: sub r0, r2
|
||||
OP_SUBI = 12, ///< substract immediate value (8 bits max), e.g.: subi r2, 8 ; r2 = r2 - 8
|
||||
OP_MUL = 13, ///< multiply and store in first reg, e.g.: mul r0, r2
|
||||
OP_DIV = 14, ///< divide and store in first reg, remain in second, e.g.: div r0, r2
|
||||
|
||||
OP_SHL = 13, ///< logical shift left, e.g.: shl r0, r1
|
||||
OP_SHR = 14, ///< logical shift right, e.g.: shr r0, r1
|
||||
OP_ISHR = 15, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1
|
||||
OP_SHL = 15, ///< logical shift left, e.g.: shl r0, r1
|
||||
OP_SHR = 16, ///< logical shift right, e.g.: shr r0, r1
|
||||
OP_ISHR = 17, ///< arithmetic shift right (for signed values), e.g.: ishr r0, r1
|
||||
|
||||
OP_AND = 16, ///< and two registers and store result in the first one, e.g.: and r0, r1
|
||||
OP_OR = 17, ///< or two registers and store result in the first one, e.g.: or r0, r1
|
||||
OP_XOR = 18, ///< xor two registers and store result in the first one, e.g.: xor r0, r1
|
||||
OP_NOT = 19, ///< not a register and store result, e.g.: not r0
|
||||
OP_AND = 18, ///< and two registers and store result in the first one, e.g.: and r0, r1
|
||||
OP_OR = 19, ///< or two registers and store result in the first one, e.g.: or r0, r1
|
||||
OP_XOR = 20, ///< xor two registers and store result in the first one, e.g.: xor r0, r1
|
||||
OP_NOT = 21, ///< not a register and store result, e.g.: not r0
|
||||
|
||||
// branching/functions
|
||||
OP_CALL = 20, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
|
||||
OP_RET = 21, ///< return to the address of last callee (RA), e.g.: ret
|
||||
OP_JUMP = 22, ///< jump to address (can use label or address), e.g.: jump .my_label
|
||||
OP_JUMPR = 23, ///< jump to address contained in a register, e.g.: jumpr t9
|
||||
OP_SKIPZ = 24, ///< skip next instruction if zero, e.g.: skipz r0
|
||||
OP_SKIPNZ = 25, ///< skip next instruction if not zero, e.g.: skipnz r2
|
||||
OP_CALL = 22, ///< set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
|
||||
OP_RET = 23, ///< return to the address of last callee (RA), e.g.: ret
|
||||
OP_JUMP = 24, ///< jump to address (can use label or address), e.g.: jump .my_label
|
||||
OP_JUMPR = 25, ///< jump to address contained in a register, e.g.: jumpr t9
|
||||
OP_SKIPZ = 26, ///< skip next instruction if zero, e.g.: skipz r0
|
||||
OP_SKIPNZ = 27, ///< skip next instruction if not zero, e.g.: skipnz r2
|
||||
|
||||
// Comparison
|
||||
OP_CMP_EQ = 26, ///< compare two registers for equality, result in first e.g.: cmp_eq r4, r0, r1 (r4 = (r0 == r1 ? 1 : 0)
|
||||
OP_CMP_GT = 27, ///< compare if first register is greater than the second, result in first e.g.: cmp_gt r4, r0, r1
|
||||
OP_CMP_LT = 28, ///< compare if first register is less than the second, result in first e.g.: cmp_lt r4, r0, r1
|
||||
OP_CMP_EQ = 28, ///< compare two registers for equality, result in first e.g.: cmp_eq r4, r0, r1 (r4 = (r0 == r1 ? 1 : 0)
|
||||
OP_CMP_GT = 29, ///< compare if first register is greater than the second, result in first e.g.: cmp_gt r4, r0, r1
|
||||
OP_CMP_LT = 30, ///< compare if first register is less than the second, result in first e.g.: cmp_lt r4, r0, r1
|
||||
|
||||
INSTRUCTION_COUNT
|
||||
} chip32_instruction_t;
|
||||
|
|
@ -91,7 +93,7 @@ typedef enum
|
|||
|
||||
/*
|
||||
|
||||
| name | number | type | preserved |
|
||||
| name | number | type | preserved on function call |
|
||||
|-------|--------|----------------------------------|-----------|
|
||||
| r0-r9 | 0-9 | general-purpose | N |
|
||||
| t0-t9 | 10-19 | temporary registers | Y |
|
||||
|
|
@ -157,7 +159,8 @@ typedef struct {
|
|||
|
||||
#define OPCODES_LIST { { OP_NOP, 0, 0 }, { OP_HALT, 0, 0 }, { OP_SYSCALL, 1, 1 }, { OP_LCONS, 2, 5 }, \
|
||||
{ OP_MOV, 2, 2 }, { OP_PUSH, 1, 1 }, {OP_POP, 1, 1 }, \
|
||||
{ OP_STORE, 3, 3 }, { OP_LOAD, 3, 3 }, { OP_ADD, 2, 2 }, { OP_SUB, 2, 2 }, { OP_MUL, 2, 2 }, \
|
||||
{ OP_STORE, 3, 3 }, { OP_LOAD, 3, 3 }, \
|
||||
{ OP_ADD, 2, 2 }, { OP_ADDI, 2, 2 }, { OP_SUB, 2, 2 }, { OP_SUBI, 2, 2 }, { OP_MUL, 2, 2 }, \
|
||||
{ OP_DIV, 2, 2 }, { OP_SHL, 2, 2 }, { OP_SHR, 2, 2 }, { OP_ISHR, 2, 2 }, { OP_AND, 2, 2 }, \
|
||||
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 1 }, { OP_RET, 0, 0 }, \
|
||||
{ OP_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ THE SOFTWARE.
|
|||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include "catch.hpp"
|
||||
#include "chip32_assembler.h"
|
||||
#include "chip32_macros.h"
|
||||
|
||||
/*
|
||||
Purpose: grammar, ram usage and macros, rom code generation
|
||||
|
|
@ -65,14 +67,80 @@ mov R0,R2 ; copy R2 into R0 (NO blank space between , and R2)
|
|||
halt
|
||||
)";
|
||||
|
||||
#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;
|
||||
|
|
@ -82,7 +150,7 @@ TEST_CASE( "Check various indentations and typos" ) {
|
|||
|
||||
bool parseResult = assembler.Parse(test1);
|
||||
|
||||
std::cout << assembler.GetLastError().ToString();
|
||||
std::cout << assembler.GetLastError().ToString() << std::endl;
|
||||
|
||||
REQUIRE( parseResult == true );
|
||||
|
||||
|
|
@ -90,7 +158,7 @@ TEST_CASE( "Check various indentations and typos" ) {
|
|||
result.Print();
|
||||
hexdump(program.data(), program.size());
|
||||
|
||||
static chip32_ctx_t chip32_ctx;
|
||||
chip32_ctx_t chip32_ctx;
|
||||
|
||||
chip32_ctx.stack_size = 512;
|
||||
|
||||
|
|
@ -99,7 +167,7 @@ TEST_CASE( "Check various indentations and typos" ) {
|
|||
chip32_ctx.rom.size = program.size();
|
||||
|
||||
chip32_ctx.ram.mem = data;
|
||||
chip32_ctx.ram.addr = 40 *1024,
|
||||
chip32_ctx.ram.addr = 40 *1024;
|
||||
chip32_ctx.ram.size = sizeof(data);
|
||||
|
||||
chip32_ctx.syscall = story_player_syscall;
|
||||
|
|
@ -108,3 +176,151 @@ TEST_CASE( "Check various indentations and typos" ) {
|
|||
chip32_result_t runResult = chip32_run(&chip32_ctx);
|
||||
REQUIRE( runResult == VM_FINISHED );
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
static const std::string testPrintf = R"(
|
||||
|
||||
; ========================================================
|
||||
; We test the printf system call
|
||||
; ========================================================
|
||||
jump .entry
|
||||
|
||||
$printHello DC8 "La réponse est %d"
|
||||
$answer DC32 42
|
||||
|
||||
$counter DV32 10
|
||||
|
||||
.entry:
|
||||
|
||||
; prepapre loop
|
||||
|
||||
lcons t0, 1000
|
||||
lcons t1, 4
|
||||
.while R1 > 0
|
||||
.print "La valeur est: %d", $counter
|
||||
|
||||
mov r0, t0 ; wait time in ms for argument
|
||||
syscall 5 ; wait call
|
||||
|
||||
.endwhile
|
||||
|
||||
|
||||
halt
|
||||
|
||||
|
||||
|
||||
)";
|
||||
|
||||
|
||||
static const std::string testMacro1 = R"(
|
||||
|
||||
%section_macro
|
||||
|
||||
%macro incr 1
|
||||
push t0
|
||||
lcons t0, 1
|
||||
add %1, t0
|
||||
pop t0
|
||||
%endmacro
|
||||
|
||||
|
||||
%macro print 2
|
||||
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
|
||||
%endmacro
|
||||
|
||||
|
||||
%macro LOOP_END 2
|
||||
subi %2, 1 ; Décrémente le registre spécifié
|
||||
skipz %2
|
||||
jump %1_loop ; Saute si le registre n'est pas zéro
|
||||
%endmacro
|
||||
|
||||
%section_text
|
||||
|
||||
lcons R3, 4
|
||||
incr R3
|
||||
|
||||
LOOP_START .myLoop, r6, 5
|
||||
print $printHello, r3
|
||||
LOOP_END .myLoop, r6
|
||||
halt
|
||||
|
||||
%section_data
|
||||
|
||||
$printHello DC8 "Answer is %d"
|
||||
|
||||
)";
|
||||
|
||||
TEST_CASE( "Check assembly macro language part 1" )
|
||||
{
|
||||
|
||||
Chip32::ScriptProcessor processor;
|
||||
processor.process(testMacro1);
|
||||
|
||||
processor.generate_assembly();
|
||||
|
||||
std::string resultAsm = processor.GetResult();
|
||||
|
||||
std::cout << "-----------------------------------------------------" << 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;
|
||||
}
|
||||
|
||||
out.close();
|
||||
*/
|
||||
|
||||
|
||||
std::vector<uint8_t> program;
|
||||
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);
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue