| git.druid.rocks | index | druid520 | jury | src/ | isa.h |
src/isa.h
/* isa.h: the jury instruction set.
*
* jury, asm and disasm all build against isa.c, so they share one
* encoding table and one decoder and cant disagree about what a byte
* means. an op's encoding is its position in its mode's table:
* MODES[mode][op nibble].
*/
#ifndef ISA_H
#define ISA_H
#define RAM_SIZE 32768
#define NREGS 5
#define STACK_SIZE 256
#define STACK_BASE (RAM_SIZE - STACK_SIZE) /* lowest stack byte */
#define STACK_TOP RAM_SIZE /* sp when the stack is empty */
#define NMODES 5 /* 0 is the normal table, 1-4 are reached by MODE */
#define NCONDS 7 /* jump conditions, see CONDS */
#define INS_MAX 4 /* longest instruction: mode, op, addr hi, addr lo */
#define INS_TXT 32 /* room for one formatted instruction */
/* asm writes prog.bin plus prog.bin.vars, one "name addr len" line per
* variable (addr and len 4 hex digits), so disasm knows which bytes are
* data. jury never reads it, the .bin is exactly what gets loaded. */
#define VARS_EXT ".vars"
#define VARS_MAX 1024
/* operand formats. the format alone fixes how long an instruction is
* and what its low nibble means, and which order its operands are
* written in. two operands are always written source first,
* destination last, like at&t asm: "LD 0x7000, r0" is r0 = ram[0x7000],
* "ST r0, 0x7000" is ram[0x7000] = r0. LD/ST (and LDR/STR) share a byte
* layout but not a written order, so each order gets its own format
* rather than a special case. */
enum
{
FMT_NONE, /* op low nibble 0 */
FMT_R, /* op r low nibble r */
FMT_IR, /* op imm8, r low nibble r, 1 byte imm */
FMT_AR, /* op addr, r low nibble r, 2 byte addr */
FMT_RA, /* op r, addr low nibble r, 2 byte addr */
FMT_CA, /* jcc addr low nibble cond, 2 byte addr */
FMT_A, /* op addr low nibble 0, 2 byte addr */
FMT_P, /* op h:l low nibble 0, 1 reg byte */
FMT_PR, /* op h:l, r low nibble r, 1 reg byte */
FMT_RP, /* op r, h:l low nibble r, 1 reg byte */
FMT_RR, /* op s, d low nibble 0, 1 reg byte d:s */
FMT_MODE /* MODE m, prefix only, never a whole op */
};
/* one id per op, the vm dispatches on this. */
enum
{
/* normal */
OP_HALT, OP_LDI, OP_ADD, OP_SUB, OP_AND, OP_OR, OP_XOR, OP_CMP,
OP_JCC, OP_LD, OP_ST, OP_CALL, OP_MODE, OP_PUSH, OP_POP, OP_RET,
/* mode 1, through a register pair */
OP_LDR, OP_STR, OP_JMPR, OP_CALLR, OP_INCP, OP_DECP,
/* mode 2, one register */
OP_SHL, OP_SHR, OP_ROL, OP_ROR, OP_NOT, OP_NEG, OP_INC, OP_DEC,
OP_OUT, OP_OUTC,
/* mode 3, register/register */
OP_MOV, OP_SWAP, OP_ADDR, OP_ADCR, OP_SUBR, OP_SBCR, OP_CMPR,
OP_ANDR, OP_ORR, OP_XORR,
/* mode 4, more register/immediate */
OP_ADC, OP_SBC, OP_TST,
OP_COUNT
};
/* jump conditions, the low nibble of a jcc. */
enum
{
CC_ALWAYS, CC_Z, CC_NZ, CC_C, CC_NC, CC_N, CC_NN
};
/* decoder results */
enum
{
DEC_OK, DEC_SHORT, DEC_BADOP, DEC_BADMODE, DEC_BADREG
};
typedef struct {
const char* name; /* NULL for an unused slot */
int fmt;
int id;
} opdef;
typedef struct {
const opdef* def;
int mode; /* table it came from, 0-4 */
int op; /* high nibble of the op byte */
int nib; /* low nibble: register, condition, or 0 */
int x; /* reg byte high nibble: pair high, or dest (written 2nd) */
int y; /* reg byte low nibble: pair low, or source (written 1st) */
unsigned int val; /* imm8 or addr */
int len; /* bytes, mode prefix included */
} ins;
extern const opdef* MODES[NMODES];
extern const char* CONDS[NCONDS];
extern const char* DECMSG[];
int sameci(const char* a, const char* b);
int fmtextra(int fmt);
int isadecode(const unsigned char* mem, unsigned long len, unsigned long at, ins* in);
const char* isaname(const ins* in);
void isafmt(const ins* in, char* buf);
int isafind(const char* name, int* mode, int* op, int* nib);
#endif