mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
229 lines
8 KiB
C
229 lines
8 KiB
C
/*
|
|
The MIT License
|
|
|
|
Copyright (c) 2022 Anthony Rabine
|
|
Copyright (c) 2018 Mario Falcao
|
|
|
|
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.
|
|
*/
|
|
|
|
#ifndef CHIP32_H
|
|
#define CHIP32_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
|
|
// General form: instruction destination, source
|
|
// coded as: instr dst, src
|
|
typedef enum
|
|
{
|
|
// system:
|
|
OP_NOP = 0, ///< do nothing
|
|
OP_HALT = 1, ///< halt execution
|
|
OP_SYSCALL = 2, ///< system call handled by user-registered function, 4 arguments (R0 - R3) passed by value
|
|
// constants:
|
|
OP_LCONS = 3, ///< store a value in a register, e.g.: lcons r0, 0xA2 0x00 0x00 0x00
|
|
///< can also load a variable address: lcons r2, $DataInRam
|
|
|
|
// register operations:
|
|
OP_MOV = 4, ///< copy a value between registers, e.g.: mov r0, r2
|
|
// stack:
|
|
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
|
|
|
|
// memory get/set from/to an address. The last argument is a size (1, 2 or 4 bytes)
|
|
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 = 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 = 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 = 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/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
|
|
|
|
// 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
|
|
|
|
INSTRUCTION_COUNT
|
|
} chip32_instruction_t;
|
|
|
|
|
|
/*
|
|
|
|
| name | number | type | preserved |
|
|
|-------|--------|----------------------------------|-----------|
|
|
| r0-r9 | 0-9 | general-purpose | N |
|
|
| t0-t9 | 10-19 | temporary registers | Y |
|
|
| pc | 20 | program counter | Y |
|
|
| sp | 21 | stack pointer | Y |
|
|
| ra | 22 | return address | N |
|
|
|
|
*/
|
|
typedef enum
|
|
{
|
|
// preserved across a call, use them for argument passing
|
|
R0,
|
|
R1,
|
|
R2,
|
|
R3,
|
|
R4,
|
|
R5,
|
|
R6,
|
|
R7,
|
|
R8,
|
|
R9,
|
|
// Temporaries are automatically saved on stack across a call
|
|
T0,
|
|
T1,
|
|
T2,
|
|
T3,
|
|
T4,
|
|
T5,
|
|
T6,
|
|
T7,
|
|
T8,
|
|
T9,
|
|
// special
|
|
PC,
|
|
SP,
|
|
RA,
|
|
// count
|
|
REGISTER_COUNT
|
|
} chip32_register_t;
|
|
|
|
|
|
typedef enum
|
|
{
|
|
VM_READY, // VM Ready to be started
|
|
VM_FINISHED, // execution completed (i.e. got halt instruction)
|
|
VM_SKIPED, // skipped instruction
|
|
VM_WAIT_EVENT, // execution paused since we hit the maximum instructions
|
|
VM_OK, // execution ok
|
|
VM_ERR_UNKNOWN_OPCODE, // unknown opcode
|
|
VM_ERR_UNSUPPORTED_OPCODE, // instruction not supported on this platform
|
|
VM_ERR_INVALID_REGISTER, // invalid register access
|
|
VM_ERR_UNHANDLED_INTERRUPT, // interrupt triggered without registered handler
|
|
VM_ERR_STACK_OVERFLOW, // stack overflow
|
|
VM_ERR_STACK_UNDERFLOW, // stack underflow
|
|
VM_ERR_INVALID_ADDRESS, // tried to access an invalid memory address
|
|
} chip32_result_t;
|
|
|
|
typedef struct {
|
|
uint8_t opcode;
|
|
uint8_t nbAargs; //!< Number of arguments needed in assembly
|
|
uint8_t bytes; //!< Size of bytes arguments
|
|
} 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_STORE, 3, 3 }, { OP_LOAD, 3, 3 }, { 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_CALL, 1, 1 }, { OP_RET, 0, 0 }, \
|
|
{ OP_JUMP, 1, 2 }, { OP_JUMPR, 1, 1 }, { OP_SKIPZ, 1, 1 }, { OP_SKIPNZ, 1, 1 }, \
|
|
{ OP_CMP_EQ, 3, 3 }, { OP_CMP_GT, 3, 3 }, { OP_CMP_LT, 3, 3 } }
|
|
|
|
/**
|
|
Whole memory is 64KB
|
|
|
|
The RAM and ROM segments can be placed anywhere:
|
|
- they must not overlap
|
|
- there can have gaps between segments (R/W to a dummy byte if accessed)
|
|
|
|
----------- 0xFFFF
|
|
| |
|
|
| |
|
|
|---------|<-- stack start
|
|
| |
|
|
| RAM |
|
|
|_________|
|
|
| |
|
|
|_________|
|
|
| ROM |
|
|
|_________|
|
|
| |
|
|
----------- 0x0000
|
|
*/
|
|
typedef struct
|
|
{
|
|
uint8_t *mem; //!< Pointer to a real memory location (ROM or RAM)
|
|
uint16_t size; //!< Size of the real memory
|
|
uint16_t addr; //!< Start address of the virtual memory
|
|
|
|
} virtual_mem_t;
|
|
|
|
typedef struct chip32_ctx_t chip32_ctx_t;
|
|
|
|
typedef uint8_t (*syscall_t)(chip32_ctx_t *, uint8_t);
|
|
|
|
#define SYSCALL_RET_OK 0 ///< Default state, continue execution immediately
|
|
#define SYSCALL_RET_WAIT_EV 1 ///< Sets the VM in wait for event state
|
|
|
|
|
|
struct chip32_ctx_t
|
|
{
|
|
virtual_mem_t rom;
|
|
virtual_mem_t ram;
|
|
uint16_t stack_size;
|
|
uint32_t instrCount;
|
|
uint16_t prog_size;
|
|
uint32_t max_instr;
|
|
uint32_t registers[REGISTER_COUNT];
|
|
syscall_t syscall;
|
|
|
|
};
|
|
|
|
// =======================================================================================
|
|
// VM RUN
|
|
// =======================================================================================
|
|
void chip32_initialize(chip32_ctx_t *ctx);
|
|
chip32_result_t chip32_run(chip32_ctx_t *ctx); // loop until the end or max instructions
|
|
chip32_result_t chip32_step(chip32_ctx_t *ctx); // one instruction
|
|
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif // CHIP32_H
|