From 263cc7986eec09fb111c3e8a4acfa7d88ebe3228 Mon Sep 17 00:00:00 2001 From: Anthony Rabine Date: Tue, 25 Apr 2023 16:10:35 +0200 Subject: [PATCH] fix skip and call in VM, add temporary registers --- software/chip32/chip32_assembler.cpp | 27 +++--- software/chip32/chip32_vm.c | 111 +++++++++--------------- software/chip32/chip32_vm.h | 68 ++++++++------- story-editor/src/script_editor_dock.cpp | 33 ++++--- 4 files changed, 107 insertions(+), 132 deletions(-) diff --git a/software/chip32/chip32_assembler.cpp b/software/chip32/chip32_assembler.cpp index 52c9e21..fa421bd 100644 --- a/software/chip32/chip32_assembler.cpp +++ b/software/chip32/chip32_assembler.cpp @@ -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; } - } } diff --git a/software/chip32/chip32_vm.c b/software/chip32/chip32_vm.c index 0b781f7..29d4ddd 100644 --- a/software/chip32/chip32_vm.c +++ b/software/chip32/chip32_vm.c @@ -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; } diff --git a/software/chip32/chip32_vm.h b/software/chip32/chip32_vm.h index 0517b62..9fce87d 100644 --- a/software/chip32/chip32_vm.h +++ b/software/chip32/chip32_vm.h @@ -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; diff --git a/story-editor/src/script_editor_dock.cpp b/story-editor/src/script_editor_dock.cpp index 88d8b0d..3386402 100644 --- a/story-editor/src/script_editor_dock.cpp +++ b/story-editor/src/script_editor_dock.cpp @@ -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)