fix skip and call in VM, add temporary registers

This commit is contained in:
Anthony Rabine 2023-04-25 16:10:35 +02:00
parent 2377da1c54
commit 263cc7986e
4 changed files with 107 additions and 132 deletions

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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)