haker
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage

WOPR C API

This api is a work in progress. It is not complete and is being changed regularly. This draft notice will be removed and document given version once it is finalized.

CPU API

typedef struct cpu_t cpu_t;

Opaque type representing cpu instance.

cpu_t* cpu_new();
void cpu_free(cpu_t* cpu);

Create new CPU instance with cpu_new and free it using cpu_free().

bool cpu_load(cpu_t* cpu, FILE* in);

Load program from file into CPU memory.

File format is binary and consists of memory blocks. Each block consists of a word for the target location and word for block size, followed by said number of bytes. File is continualy read, one block at a time until end of file.

  • file
    • block
      • address (cpu_word_t)
      • size (cpu_word_t)
      • data (size number of bytes)
    • block?
cpu_word_t cpu_get_pc(cpu_t* cpu);
cpu_word_t cpu_get_es(cpu_t* cpu);
cpu_word_t cpu_get_rs(cpu_t* cpu);

cpu_word_t cpu_get_es_max(cpu_t* cpu);
cpu_word_t cpu_get_rs_max(cpu_t* cpu);

Get value of the PC, ES and RS register.

WOPR keeps some metrics and can return the maximum values for ES and RS encountered thus far. This can help in profiling your programs and chosing best stack size. Please note, ‘max’ for ES really means lowest value, as the evaluation stack grown downward.

size_t cpu_get_ticks(cpu_t* cpu);

Get number of ticks/cpu cycles that the CPU has executed.

void cpu_step(cpu_t* cpu);

Execute single instruction.

void cpu_int(cpu_t* cpu, cpu_word_t int_number);

Raise specified interrupt.

Interrupt is not raised immediately, but rather it is only queued. Each interrupt can be queued only once. Interrupt is raised when the next instruction executes, unless another interrupt handler is processing. In this case, CPU will continue executing until the current interrupt handler completes using IRET, and the next queued interrupt is executed at the subsequent cycle. Interrupts are raised according to preference, with lowest numbers being more important then the high numbers. Order of interrupt requests is not important. When CPU is ready to raise interupt it will simply pick lowest queued one. This means that it is possible that high interrupts and user code might me starved of CPU cycles if a higher priority interrupt takes too long to execute.

int cpu_get_state(cpu_t* cpu);

#define CPU_HALTED 0x01
#define CPU_IN_INTERRUPT 0x02

Get CPU state flags.

  • CPU_HALTED flag indicates that the CPU is halted and it will not execute any more/instructions.
  • CPU_IN_INTERRUPT flag indicates that the CPU is currently executing an interrupt handler.
   if (cpu_state(cpu) & CPU_HALTED) {
      // CPU is halted...
   }

Memory Access

cpu_word_t cpu_get_byte(cpu_t* cpu, cpu_word_t addr);
void cpu_set_byte(cpu_t* cpu, cpu_word_t addr, uint8_t val);
cpu_word_t cpu_get_word(cpu_t* cpu, cpu_word_t addr);
void cpu_set_word(cpu_t* cpu, cpu_word_t addr, cpu_word_t val);
void cpu_get_memory(cpu_t* cpu, void* dest, cpu_word_t addr, cpu_word_t size);

cpu_get_byte and cpu_set_byte read and write a single byte to specified address.

cpu_get_word and cpu_set_word read and write a word to specified address. WOPR is little endian, so the low byte is stored at addr and high byte is stored at addr + 1.

cpu_get_memory reads size bytes from memory.

Stack Access

void cpu_push(cpu_t* cpu, cpu_word_t val);
cpu_word_t cpu_pop(cpu_t* cpu);
cpu_word_t cpu_peek(cpu_t* cpu, cpu_word_t offset);

void cpu_pushr(cpu_t* cpu, cpu_word_t val);
cpu_word_t cpu_popr(cpu_t* cpu);

cpu_push and cpu_pop push and pop words from the evaluation stack. Evaluation stack grows down. Push will subract 2 from ES before writing new value to the stack, and pop will add 2 to ES after reading a value. cpu_peek returns the same value as cpu_pop except it does not change the ES.

cpu_pushr and cpu_popr push and pop are very similar to cpu_push and cpu_pop, except they work with the return stack. Return stack grows up. Pushr will add 2 to RS before writing new value. Popr will subract 2 form RS after reading a value.

CPU info

void cpuinfo_init();
bool cpuinfo_is_valid_op(cpu_op_t op);
const char* cpuinfo_op_name(cpu_op_t op);
int cpuinfo_op_param_size(cpu_op_t op);

These functions return static information about the CPU instructions.

typedef uint16_t cpu_word_t;
#define CPU_WORD_MAX UINT16_MAX

typedef int16_t cpu_int_t;
#define CPU_INT_MAX INT16_MAX
#define CPU_INT_MIN INT16_MIN

Defines C types for WOPR word and int types. int is a signed word.

#define CPU_MEM_SIZE 0x10000

Size of the WOPR memory in bytes.

#define CPU_INTERRUPT_COUNT 0x20
#define CPU_INTERRUPT_TABLE \
  ((cpu_word_t)(CPU_MEM_SIZE-(CPU_INTERRUPT_COUNT*sizeof(cpu_word_t))))

Number of interrupts supported by the CPU.

Address of the interrupt table. It is located at the very top of memory space. It consists of CPU_INTERRUPT_COUNT pointers to handlers, each one word in size.

#define CPU_STACK_SIZE 0x4000

Default size of the stack.

Stack is combined evaluation and return stack. They occupy the same memory area. Evaluation stack grows from the top and return stack grows from the bottom. If they grow too large, they would meet and ultimately corrupt each other. It is responsibility of the program and the OS to ensure enough stack is allocated.

Stack is located immediately below the interrupt table.

#define CPU_EVAL_STACK CPU_INTERRUPT_TABLE
#define CPU_RETURN_STACK ((cpu_word_t)(CPU_EVAL_STACK - CPU_STACK_SIZE))

Initial value for the ES and RS registers.

Instructions

typedef enum cpu_op_t {
   CPU_NOP = 0,

   CPU_PUSH, // param; push literal
   CPU_PUSHB, // param; push literal

   CPU_POP,

   CPU_DUP,
   CPU_DUP1,
   CPU_DUP2,
   CPU_DUP3,

   CPU_SWAP,
   CPU_SWAP1,
   CPU_SWAP2,
   CPU_SWAP3,

   CPU_LOAD, // P0 = [P0]
   CPU_LOADB, // P0 = [P0](byte)
   CPU_STO, // [P1] = P0
   CPU_STOB, // [P1](byte) = P0

   CPU_LOAD_PC,
   CPU_LOAD_ES,
   CPU_LOAD_RS,
   CPU_STO_ES,
   CPU_STO_RS,

   CPU_ADD,
   CPU_SUB,
   CPU_INC,
   CPU_DEC,
   CPU_NEG,
   CPU_MUL,
   CPU_MULS,
   CPU_DIV,
   CPU_DIVS,
   CPU_MOD,
   CPU_AND,
   CPU_OR,
   CPU_XOR,
   CPU_NOT,
   CPU_SHL,
   CPU_SHLS,
   CPU_SHR,
   CPU_SHRS,

   CPU_JMP,
   CPU_JE,
   CPU_JNE,
   CPU_JG,
   CPU_JGS,
   CPU_JL,
   CPU_JLS,
   CPU_JZ,
   CPU_JNZ,
   CPU_JGZS,
   CPU_JLZS,

   CPU_CALL,
   CPU_RET,
   CPU_IRET,
   CPU_OUT,
   CPU_HALT = 255
} cpu_op_t;

Device Interface

#define CPU_MAX_DEVICES 0x10

Maximum number of devices that can be attached to WOPR cpu.

#define CPU_CMD_GETTYPE      0x00
#define CPU_CMD_SETINTERRUPT 0x01
#define CPU_CMD_SETMEMMAP    0x02
#define CPU_CMD_USER         0x100
#define CPU_ERR_INVALIDCMD   0xBAAD

device commands

#define CPU_DEVICE_NONE     0x0000
#define CPU_DEVICE_SERIAL   0x0001
#define CPU_DEVICE_SCREEN   0x0002
#define CPU_DEVICE_KEYBOARD 0x0003

Device types. One of these values is returned by the device in response to CPU_CMD_GETTYPE command.

typedef struct cpu_device_t cpu_device_t;
typedef void (*cpu_device_handler_t) (cpu_t* cpu, void* data);

cpu_device_t* cpu_add_device(cpu_t* cpu);

cpu_t* cpu_device_get_cpu(cpu_device_t* dev);
int cpu_device_get_id(cpu_device_t* dev);

void cpu_device_set_data(cpu_device_t* dev, void* data);
void* cpu_device_get_data(cpu_device_t* dev);

void cpu_device_set_poll_handler(cpu_device_t* dev, cpu_device_handler_t h);
void cpu_device_set_out_handler(cpu_device_t* dev, cpu_device_handler_t h);
void cpu_device_set_done_handler(cpu_device_t* dev, cpu_device_handler_t h);

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License. Click to read license.