mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
fix skip and call in VM, add temporary registers
This commit is contained in:
parent
2377da1c54
commit
263cc7986e
4 changed files with 107 additions and 132 deletions
|
|
@ -75,15 +75,16 @@ static std::string ToLower(const std::string &text)
|
|||
}
|
||||
|
||||
static const RegNames AllRegs[] = { { R0, "r0" }, { R1, "r1" }, { R2, "r2" }, { R3, "r3" }, { R4, "r4" }, { R5, "r5" },
|
||||
{ R6, "r6" }, { R7, "r7" }, { R8, "r8" }, { R9, "r9" }, { PC, "pc" }, { SP, "sp" }, { RA, "ra" }
|
||||
{ R6, "r6" }, { R7, "r7" }, { R8, "r8" }, { R9, "r9" }, { T0, "t0" }, { T1, "t1" }, { T2, "t2" }, { T3, "t3" }, { T4, "t4" },
|
||||
{ T5, "t5" }, { T6, "t6" }, { T7, "t7" }, { T8, "t8" }, { T9, "t9" },{ PC, "pc" }, { SP, "sp" }, { RA, "ra" }
|
||||
};
|
||||
|
||||
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", "call", "ret", "store", "load", "add", "sub", "mul", "div",
|
||||
"shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "jump", "jumpr", "skipz", "skipnz"
|
||||
"nop", "halt", "syscall", "lcons", "mov", "push", "pop", "store", "load", "add", "sub", "mul", "div",
|
||||
"shiftl", "shiftr", "ishiftr", "and", "or", "xor", "not", "call", "ret", "jump", "skipz", "skipnz"
|
||||
};
|
||||
|
||||
static OpCode OpCodes[] = OPCODES_LIST;
|
||||
|
|
@ -191,7 +192,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
|
|||
GET_REG(instr.args[0], ra);
|
||||
instr.compiledArgs.push_back(ra);
|
||||
// Detect address or immedate value
|
||||
if (instr.args[1].at(0) == '$') {
|
||||
if ((instr.args[1].at(0) == '$') || (instr.args[1].at(0) == '.')) {
|
||||
instr.useLabel = true;
|
||||
leu32_put(instr.compiledArgs, 0); // reserve 4 bytes
|
||||
} else { // immediate value
|
||||
|
|
@ -202,7 +203,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
|
|||
case OP_PUSH:
|
||||
case OP_SKIPZ:
|
||||
case OP_SKIPNZ:
|
||||
case OP_JR:
|
||||
case OP_CALL:
|
||||
GET_REG(instr.args[0], ra);
|
||||
instr.compiledArgs.push_back(ra);
|
||||
break;
|
||||
|
|
@ -223,8 +224,7 @@ bool Assembler::CompileMnemonicArguments(Instr &instr)
|
|||
instr.compiledArgs.push_back(ra);
|
||||
instr.compiledArgs.push_back(rb);
|
||||
break;
|
||||
case OP_JMP:
|
||||
case OP_CALL:
|
||||
case OP_JUMP:
|
||||
// Reserve 2 bytes for address, it will be filled at the end
|
||||
instr.useLabel = true;
|
||||
instr.compiledArgs.push_back(0);
|
||||
|
|
@ -448,6 +448,11 @@ bool Assembler::Parse(const std::string &data)
|
|||
m_labels[opcode] = instr;
|
||||
m_instructions.push_back(instr);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_lastError = "Unknown mnemonic or bad formatted line: " + std::to_string(lineNum);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Second pass: replace all label or RAM data by the real address in memory
|
||||
|
|
@ -455,20 +460,18 @@ bool Assembler::Parse(const std::string &data)
|
|||
{
|
||||
if (instr.useLabel && (instr.args.size() > 0))
|
||||
{
|
||||
// label is the first argument for jump, call, store
|
||||
// in second position for load!
|
||||
// label is the first argument for jump, second position for LCONS
|
||||
uint16_t argsIndex = instr.code.opcode == OP_LCONS ? 1 : 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;
|
||||
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) {
|
||||
// We precise if we load from RAM or ROM
|
||||
// We precise if the address is from RAM or ROM
|
||||
instr.compiledArgs[argsIndex+3] = m_labels[label].isRamData ? 0x80 : 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,25 @@ THE SOFTWARE.
|
|||
ctx->rom.mem[ctx->registers[PC] - 1] << 16 | ctx->rom.mem[ctx->registers[PC]] << 24; \
|
||||
})
|
||||
|
||||
static inline uint32_t leu32_get(const uint8_t *a)
|
||||
{
|
||||
uint32_t val = 0;
|
||||
val |= (((uint32_t) a[3]) << 24);
|
||||
val |= (((uint32_t) a[2]) << 16);
|
||||
val |= (((uint32_t) a[1]) << 8);
|
||||
val |= ((uint32_t) a[0]);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline void leu32_put(uint8_t *buff, uint32_t data)
|
||||
{
|
||||
buff[3] = (data >> 24U) & 0xFFU;
|
||||
buff[2] = (data >> 16U) & 0xFFU;
|
||||
buff[1] = (data >> 8U) & 0xFFU;
|
||||
buff[0] = data & 0xFFU;
|
||||
}
|
||||
|
||||
#define _CHECK_SKIP if (skip) continue;
|
||||
|
||||
#ifndef VM_DISABLE_CHECKS
|
||||
|
|
@ -85,52 +104,10 @@ void chip32_initialize(chip32_ctx_t *ctx)
|
|||
{
|
||||
memset(ctx->ram.mem, 0, ctx->ram.size);
|
||||
memset(ctx->registers, 0, REGISTER_COUNT * sizeof(uint32_t));
|
||||
|
||||
ctx->skip_next = false;
|
||||
ctx->instrCount = 0;
|
||||
|
||||
ctx->registers[SP] = ctx->ram.size;
|
||||
}
|
||||
|
||||
#define MEM_ACCESS(addr, vmem) if ((addr >= vmem->addr) && ((addr + vmem->size) < vmem->size))\
|
||||
{\
|
||||
addr -= vmem->addr;\
|
||||
return &vmem->mem[addr];\
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
uint8_t *chip32_memory(uint16_t addr)
|
||||
{
|
||||
static uint8_t dummy = 0;
|
||||
// Beware, can provoke memory overflow
|
||||
MEM_ACCESS(addr, g_rom);
|
||||
MEM_ACCESS(addr, g_ram);
|
||||
|
||||
return g_ram->mem; //!< Defaut memory to RAM location if address out of segment.
|
||||
}
|
||||
|
||||
uint32_t chip32_stack_count()
|
||||
{
|
||||
return g_ram->size - ctx->registers[SP];
|
||||
}
|
||||
|
||||
void chip32_stack_push(uint32_t value)
|
||||
{
|
||||
ctx->registers[SP] -= 4;
|
||||
memcpy(chip32_memory(ctx->registers[SP]), &value, sizeof(uint32_t));
|
||||
}
|
||||
|
||||
uint32_t chip32_stack_pop()
|
||||
{
|
||||
uint32_t val = 0;
|
||||
memcpy(&val, chip32_memory(ctx->registers[SP]), sizeof(uint32_t));
|
||||
ctx->registers[SP] += 4;
|
||||
return val;
|
||||
}*/
|
||||
|
||||
|
||||
chip32_result_t chip32_run(chip32_ctx_t *ctx)
|
||||
{
|
||||
chip32_result_t result = VM_OK;
|
||||
|
|
@ -146,21 +123,13 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
chip32_result_t result = VM_OK;
|
||||
|
||||
_CHECK_ROM_ADDR_VALID(ctx->registers[PC])
|
||||
const uint8_t instr = ctx->rom.mem[ctx->registers[PC]];
|
||||
uint8_t instr = ctx->rom.mem[ctx->registers[PC]];
|
||||
if (instr >= INSTRUCTION_COUNT)
|
||||
return VM_ERR_UNKNOWN_OPCODE;
|
||||
|
||||
uint8_t bytes = OpCodes[instr].bytes;
|
||||
_CHECK_BYTES_AVAIL(bytes);
|
||||
|
||||
if (ctx->skip_next)
|
||||
{
|
||||
ctx->skip_next = false;
|
||||
ctx->registers[PC] += bytes + 1; // jump over arguments and point to the next instruction
|
||||
ctx->instrCount++;
|
||||
return VM_SKIPED;
|
||||
}
|
||||
|
||||
switch (instr)
|
||||
{
|
||||
case OP_NOP:
|
||||
|
|
@ -220,13 +189,24 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
}
|
||||
case OP_CALL:
|
||||
{
|
||||
ctx->registers[RA] = ctx->registers[PC] + 3;
|
||||
ctx->registers[RA] = ctx->registers[PC] + 3; // set return address to next instruction after CALL
|
||||
ctx->registers[PC] = _NEXT_SHORT - 1;
|
||||
// save all temporary registers on stack
|
||||
ctx->registers[SP] -= 4*10; // reserve memory
|
||||
// fill memory
|
||||
for (int i = 0; i < 10; i++) {
|
||||
leu32_put(&ctx->ram.mem[ctx->registers[SP] + i*4], ctx->registers[T0 + i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_RET:
|
||||
{
|
||||
ctx->registers[PC] = ctx->registers[RA] - 1;
|
||||
// restore all temporary registers on stack
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ctx->registers[T0 + i] = leu32_get(&ctx->ram.mem[ctx->registers[SP] + i*4]);
|
||||
}
|
||||
ctx->registers[SP] += 4*10; // free memory
|
||||
break;
|
||||
}
|
||||
case OP_STORE:
|
||||
|
|
@ -367,36 +347,23 @@ chip32_result_t chip32_step(chip32_ctx_t *ctx)
|
|||
ctx->registers[reg1] = ~ctx->registers[reg1];
|
||||
break;
|
||||
}
|
||||
case OP_JMP:
|
||||
case OP_JUMP:
|
||||
{
|
||||
ctx->registers[PC] = _NEXT_SHORT - 1;
|
||||
break;
|
||||
}
|
||||
case OP_JR:
|
||||
{
|
||||
const uint8_t reg1 = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg1)
|
||||
uint16_t addr = ctx->registers[reg1];
|
||||
ctx->registers[PC] = addr;
|
||||
break;
|
||||
}
|
||||
case OP_SKIPZ:
|
||||
{
|
||||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
if (reg == 0)
|
||||
{
|
||||
ctx->skip_next = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_SKIPNZ:
|
||||
{
|
||||
const uint8_t reg = _NEXT_BYTE;
|
||||
_CHECK_REGISTER_VALID(reg)
|
||||
if (reg != 0)
|
||||
bool skip = instr == OP_SKIPZ ? ctx->registers[reg] == 0 : ctx->registers[reg] != 0;
|
||||
if (skip)
|
||||
{
|
||||
ctx->skip_next = true;
|
||||
ctx->registers[PC]++; // 1. go to next instruction
|
||||
instr = ctx->rom.mem[ctx->registers[PC]];
|
||||
bytes = OpCodes[instr].bytes;
|
||||
ctx->registers[PC] += bytes; // jump over argument bytes
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,34 +53,31 @@ typedef enum
|
|||
OP_PUSH = 5, // push a register onto the stack, e.g.: push r0
|
||||
OP_POP = 6, // pop the first element of the stack to a register, e.g.: pop r0
|
||||
|
||||
// functions
|
||||
OP_CALL = 7, // set register RA to the next instruction and jump to subroutine, e.g.: call 0x10 0x00
|
||||
OP_RET = 8, // return to the address of last callee (RA), e.g.: ret
|
||||
|
||||
// memory get/set from/to an address. The last argument is a size (1, 2 or 4 bytes)
|
||||
OP_STORE = 9, // copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes)
|
||||
OP_LOAD = 10, // copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte)
|
||||
OP_STORE = 7, // copy a value from a register to a ram address located in a register, specific size e.g. : store @r4, r1, 2 (2 bytes)
|
||||
OP_LOAD = 8, // copy a value from a ram address located in a register to a register, specific size e.g.: load r0, @r3, 1 (1 byte)
|
||||
|
||||
// arithmetic:
|
||||
OP_ADD = 11, // sum and store in first reg, e.g.: add r0, r2
|
||||
OP_SUB = 12, // subtract and store in first reg, e.g.: sub r0, r2
|
||||
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_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_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_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_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
|
||||
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
|
||||
|
||||
// branching:
|
||||
OP_JMP = 22, // jump to address, e.g.: jmp 0x0A 0x00
|
||||
OP_JR = 23, // jump to address in register, e.g.: jr r1
|
||||
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
|
||||
// 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_SKIPZ = 23, // skip next instruction if zero, e.g.: skipz r0
|
||||
OP_SKIPNZ = 24, // skip next instruction if not zero, e.g.: skipnz r2
|
||||
|
||||
INSTRUCTION_COUNT
|
||||
} chip32_instruction_t;
|
||||
|
|
@ -91,14 +88,15 @@ typedef enum
|
|||
| name | number | type | preserved |
|
||||
|-------|--------|----------------------------------|-----------|
|
||||
| r0-r9 | 0-9 | general-purpose | Y |
|
||||
| pc | 16 | program counter | Y |
|
||||
| sp | 18 | stack pointer | Y |
|
||||
| ra | 19 | return address | N |
|
||||
| t0-t9 | 10-19 | temporary registers | N |
|
||||
| pc | 20 | program counter | Y |
|
||||
| sp | 21 | stack pointer | Y |
|
||||
| ra | 22 | return address | N |
|
||||
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
// preserved across a call
|
||||
// preserved across a call, use them for argument passing
|
||||
R0,
|
||||
R1,
|
||||
R2,
|
||||
|
|
@ -109,6 +107,17 @@ typedef enum
|
|||
R7,
|
||||
R8,
|
||||
R9,
|
||||
// Temporaties are automatically saved on stack across a call
|
||||
T0,
|
||||
T1,
|
||||
T2,
|
||||
T3,
|
||||
T4,
|
||||
T5,
|
||||
T6,
|
||||
T7,
|
||||
T8,
|
||||
T9,
|
||||
// special
|
||||
PC,
|
||||
SP,
|
||||
|
|
@ -140,11 +149,11 @@ typedef struct {
|
|||
} OpCode;
|
||||
|
||||
#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_CALL, 1, 2 }, { OP_RET, 0, 0 }, \
|
||||
{ OP_MOV, 2, 2 }, { OP_PUSH, 1, 1 }, {OP_POP, 1, 1 }, \
|
||||
{ OP_STORE, 3, 4 }, { OP_LOAD, 3, 4 }, { OP_ADD, 2, 2 }, { OP_SUB, 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_JMP, 1, 2 }, { OP_JR, 1, 1 }, \
|
||||
{ OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 } }
|
||||
{ OP_OR, 2, 2 }, { OP_XOR, 2, 2 }, { OP_NOT, 1, 1 }, { OP_CALL, 1, 2 }, { OP_RET, 0, 0 }, \
|
||||
{ OP_JUMP, 1, 2 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 } }
|
||||
|
||||
/**
|
||||
Whole memory is 64KB
|
||||
|
|
@ -185,7 +194,6 @@ typedef struct
|
|||
uint32_t instrCount;
|
||||
uint16_t prog_size;
|
||||
uint32_t max_instr;
|
||||
bool skip_next;
|
||||
uint32_t registers[REGISTER_COUNT];
|
||||
syscall_t syscall;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,13 +27,8 @@ $RamData1 DV32 1 ; one 32-bit integer
|
|||
push r0
|
||||
lcons r0, .MEDIA_03
|
||||
push r0
|
||||
|
||||
|
||||
lcons r2, 3 ; 3 iterations
|
||||
|
||||
|
||||
jmp .media
|
||||
|
||||
jump .media
|
||||
|
||||
; Generic media choice manager
|
||||
.media:
|
||||
|
|
@ -43,31 +38,33 @@ $RamData1 DV32 1 ; one 32-bit integer
|
|||
; r2: nombre d'itérations
|
||||
|
||||
; Local:
|
||||
; r0: loop counter
|
||||
; r3: increment
|
||||
; r4: current media address
|
||||
; t0: loop counter
|
||||
; t1: increment 1
|
||||
; t2: increment 4
|
||||
; t4: current media address
|
||||
|
||||
.media_loop_start:
|
||||
mov r0, r2 ; i = 3
|
||||
mov r4, r1 ; current_media = @media
|
||||
mov t0, r2 ; i = 3
|
||||
lcons t1, 1
|
||||
lcons t2, 4
|
||||
mov t4, r1 ; copy address, r4 will be modified
|
||||
.media_loop:
|
||||
lcons r3, 1
|
||||
sub r0, r3 ; i--
|
||||
lcons r3, 4
|
||||
add r4, r3 ; @++
|
||||
sub t0, t1 ; i--
|
||||
add t4, t3 ; @++
|
||||
skipnz r0 ; if (r0) goto start_loop;
|
||||
jmp .media_loop_start
|
||||
jump .media_loop_start
|
||||
push sp
|
||||
push r0
|
||||
push r1
|
||||
call r4
|
||||
load r0, @r4, 4
|
||||
call r0
|
||||
pop r1
|
||||
pop r0
|
||||
pop sp
|
||||
|
||||
; TODO: wait for event
|
||||
|
||||
jmp .media_loop
|
||||
jump .media_loop
|
||||
|
||||
.MEDIA_02:
|
||||
lcons r0, $imageBird ; image name address in ROM located in R0 (null terminated)
|
||||
|
|
|
|||
Loading…
Reference in a new issue