839 lines
29 KiB
C
839 lines
29 KiB
C
/* This file is included into vbu.c and cannot be compiled on its own. */
|
|
#ifdef VBUAPI
|
|
|
|
|
|
|
|
/********************************* Constants *********************************/
|
|
|
|
/* Assembly text formats */
|
|
#define FMT_BITSTRING 0
|
|
#define FMT_BCOND 1
|
|
#define FMT_FLOATENDO 2
|
|
#define FMT_SETF 3
|
|
#define FMT_ILLEGAL 4
|
|
#define FMT_NORMAL 5
|
|
|
|
/* Operand types */
|
|
#define OP_COND 0
|
|
#define OP_DISP9 1
|
|
#define OP_DISP26 2
|
|
#define OP_IMM5S 3
|
|
#define OP_IMM5U 4
|
|
#define OP_IMM16S 5
|
|
#define OP_IMM16U 6
|
|
#define OP_JMP 7
|
|
#define OP_MEM 8
|
|
#define OP_REG1 9
|
|
#define OP_REG2 10
|
|
#define OP_SYS 11
|
|
|
|
|
|
|
|
/*********************************** Types ***********************************/
|
|
|
|
/* Disassembler output */
|
|
typedef struct {
|
|
char *buffer; /* Managed memory buffer */
|
|
size_t capacity; /* Size of allocated buffer */
|
|
size_t length; /* Bytes currently used in buffer */
|
|
} DasmOutput;
|
|
|
|
/* Opcode definition */
|
|
typedef struct {
|
|
char *mnemonic; /* Display text of instruction's mnemonic */
|
|
uint8_t codeLength; /* Number of bytes in instruction code */
|
|
uint8_t format; /* Display text format */
|
|
uint8_t operandsLength; /* Number of operands in display format */
|
|
uint8_t operands[3]; /* Types of display text operands */
|
|
} OpDef;
|
|
|
|
|
|
|
|
/******************************** Lookup Data ********************************/
|
|
|
|
/* Top-level opcodes */
|
|
static const OpDef OPDEFS[] = {
|
|
{ "MOV" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "ADD" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "SUB" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "CMP" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "SHL" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "SHR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "JMP" , 2, FMT_NORMAL , 1, { OP_JMP , 0 , 0 } },
|
|
{ "SAR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "MUL" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "DIV" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "MULU" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "DIVU" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "OR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "AND" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "XOR" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "NOT" , 2, FMT_NORMAL , 2, { OP_REG1 , OP_REG2, 0 } },
|
|
{ "MOV" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
|
|
{ "ADD" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
|
|
{ "SETF" , 2, FMT_SETF , 0, { 0 , 0 , 0 } },
|
|
{ "CMP" , 2, FMT_NORMAL , 2, { OP_IMM5S , OP_REG2, 0 } },
|
|
{ "SHL" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
|
|
{ "SHR" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
|
|
{ "CLI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
|
|
{ "SAR" , 2, FMT_NORMAL , 2, { OP_IMM5U , OP_REG2, 0 } },
|
|
{ "TRAP" , 2, FMT_NORMAL , 1, { OP_IMM5U , 0 , 0 } },
|
|
{ "RETI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
|
|
{ "HALT" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
|
|
{ "LDSR" , 2, FMT_NORMAL , 2, { OP_REG2 , OP_SYS , 0 } },
|
|
{ "STSR" , 2, FMT_NORMAL , 2, { OP_SYS , OP_REG2, 0 } },
|
|
{ "SEI" , 2, FMT_NORMAL , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BITSTRING, 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ NULL , 2, FMT_BCOND , 0, { 0 , 0 , 0 } },
|
|
{ "MOVEA", 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
|
|
{ "ADDI" , 4, FMT_NORMAL , 3, { OP_IMM16S, OP_REG1, OP_REG2 } },
|
|
{ "JR" , 4, FMT_NORMAL , 1, { OP_DISP26, 0 , 0 } },
|
|
{ "JAL" , 4, FMT_NORMAL , 1, { OP_DISP26, 0 , 0 } },
|
|
{ "ORI" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
|
|
{ "AND" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
|
|
{ "XOR" , 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
|
|
{ "MOVHI", 4, FMT_NORMAL , 3, { OP_IMM16U, OP_REG1, OP_REG2 } },
|
|
{ "LD.B" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "LD.H" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
|
|
{ "LD.W" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "ST.B" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
|
|
{ "ST.H" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL , 0, { 0 , 0 , 0 } },
|
|
{ "ST.W" , 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
|
|
{ "IN.B" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "IN.H" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "CAXI" , 2, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "IN.W" , 4, FMT_NORMAL , 2, { OP_MEM , OP_REG2, 0 } },
|
|
{ "OUT.B", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
|
|
{ "OUT.H", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } },
|
|
{ NULL , 4, FMT_FLOATENDO, 0, { 0 , 0 , 0 } },
|
|
{ "OUT.W", 4, FMT_NORMAL , 2, { OP_REG2 , OP_MEM , 0 } }
|
|
};
|
|
|
|
/* Format VII opcodes */
|
|
static const OpDef FLOATENDOS[] = {
|
|
{ "CMPF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ NULL , 4, FMT_ILLEGAL, 0, { 0 , 0 , 0 } },
|
|
{ "CVT.WS" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "CVT.SW" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "ADDF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "SUBF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "MULF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "DIVF.S" , 4, FMT_NORMAL , 2, { OP_REG1, OP_REG2, 0 } },
|
|
{ "XB" , 4, FMT_NORMAL , 1, { OP_REG2, 0 , 0 } },
|
|
{ "XH" , 4, FMT_NORMAL , 1, { OP_REG2, 0 , 0 } },
|
|
{ "REV" , 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } },
|
|
{ "TRNC.SW", 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } },
|
|
{ "MPYHW" , 4, FMT_NORMAL , 1, { OP_REG1, OP_REG2, 0 } }
|
|
};
|
|
|
|
/* Bit string mnemonics */
|
|
static const OpDef BITSTRINGS[] = {
|
|
{ "SCH0BSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "SCH0BSD", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "SCH1BSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "SCH1BSD", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
|
|
{ NULL , 2, FMT_ILLEGAL, 0, { 0, 0, 0 } },
|
|
{ "ORBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "ANDBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "XORBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "MOVBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "ORNBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "ANDNBSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "XORNBSU", 2, FMT_NORMAL , 0, { 0, 0, 0 } },
|
|
{ "NOTBSU" , 2, FMT_NORMAL , 0, { 0, 0, 0 } }
|
|
};
|
|
|
|
/* Special instructions */
|
|
static const OpDef
|
|
BCOND_J = { NULL , 2, FMT_NORMAL, 1, { OP_DISP9, 0 , 0 } },
|
|
BCOND_S = { "Bcond", 2, FMT_NORMAL, 2, { OP_COND , OP_DISP9, 0 } },
|
|
ILLEGAL = { "---" , 2, FMT_NORMAL, 0, { 0 , 0 , 0 } },
|
|
SETF_J = { NULL , 2, FMT_NORMAL, 1, { OP_REG2 , 0 , 0 } },
|
|
SETF_S = { "SETF" , 2, FMT_NORMAL, 2, { OP_COND , OP_REG2 , 0 } }
|
|
;
|
|
|
|
|
|
|
|
/***************************** Module Functions ******************************/
|
|
|
|
/* Format a number as hexadecimal */
|
|
static int dasmDec(char *dest, int32_t value) {
|
|
int digits; /* Number of digits in value */
|
|
int32_t divisor; /* Current power of 10 */
|
|
int len = 0; /* Length of resulting string */
|
|
int32_t x; /* Scratch */
|
|
|
|
/* Value is negative */
|
|
if (value < 0) {
|
|
dest[len++] = '-';
|
|
value = -value;
|
|
}
|
|
|
|
/* Count how many digits are needed to represent the number */
|
|
digits = 1;
|
|
divisor = 1;
|
|
for (x = value / 10; x > 0; x /= 10) {
|
|
digits++;
|
|
divisor *= 10;
|
|
}
|
|
|
|
/* Output each digit from highest to lowest */
|
|
for (x = 0; x < digits; x++) {
|
|
int digit = value / divisor;
|
|
dest[len++] = '0' + digit;
|
|
value -= digit * divisor;
|
|
divisor /= 10;
|
|
}
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Retrieve a joined condition-based mnemonic */
|
|
static char* dasmGetCondition(
|
|
VBU_DasmConfig *config, int id, int bcond, int setf) {
|
|
|
|
/* Joined Bcond mnemonics */
|
|
static const char *BCONDS[] = {
|
|
"BV" , "BL" , "BZ" , "BNH", "BN", "BR" , "BLT", "BLE",
|
|
"BNV", "BNL", "BNZ", "BH" , "BP", "NOP", "BGE", "BGT",
|
|
NULL , "BC" , "BE" , NULL , NULL, NULL , NULL , NULL ,
|
|
NULL , "BNC", "BNE", NULL , NULL, NULL , NULL , NULL
|
|
};
|
|
|
|
/* Split condition operand mnemonics */
|
|
static const char *CONDITIONS[] = {
|
|
"V" , "L" , "Z" , "NH", "N" , "T" , "LT", "LE",
|
|
"NV", "NL", "NZ", "H" , "P" , "F" , "GE", "GT",
|
|
NULL, "C" , "E" , NULL, NULL, NULL, NULL, NULL,
|
|
NULL, "NC", "NE", NULL, NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
/* Joined SETF mnemonics */
|
|
static const char *SETFS[] = {
|
|
"SETFV" , "SETFL" , "SETFZ" , "SETFNH",
|
|
"SETFN" , "SETFT" , "SETFLT", "SETFLE",
|
|
"SETFNV", "SETFNL", "SETFNZ", "SETFH" ,
|
|
"SETFP" , "SETFF" , "SETFGE", "SETFGT",
|
|
NULL , "SETFC" , "SETFE" , NULL ,
|
|
NULL , NULL , NULL , NULL ,
|
|
NULL , "SETFNC", "SETFNE", NULL ,
|
|
NULL , NULL , NULL , NULL
|
|
};
|
|
|
|
/* Alternate mnemonic for C/L and E/Z */
|
|
if (
|
|
(config->conditionCL == VBU_C && (id & 7) == 1) ||
|
|
(config->conditionEZ == VBU_E && (id & 7) == 2)
|
|
) id += 16;
|
|
|
|
/* Select the mnemonic from the appropriate list */
|
|
return (char *) (bcond ? BCONDS : setf ? SETFS : CONDITIONS)[id];
|
|
}
|
|
|
|
/* Format a number as hexadecimal */
|
|
static int dasmHex(char *dest,int format,int caps,int32_t value,int digits) {
|
|
int len = 0; /* Length of resulting string */
|
|
int x; /* Iterator */
|
|
|
|
/* Hex digits */
|
|
static const char DIGITS[] = "0123456789ABCDEF";
|
|
|
|
/* Prefix */
|
|
switch (format) {
|
|
case VBU_PREFIX_0X:
|
|
dest[len++] = '0';
|
|
dest[len++] = 'x';
|
|
break;
|
|
case VBU_PREFIX_DOLLAR:
|
|
dest[len++] = '$';
|
|
break;
|
|
case VBU_SUFFIX_H:
|
|
if ((value >> ((digits - 1) << 2) & 15) >= 10)
|
|
dest[len++] = '0';
|
|
break;
|
|
}
|
|
|
|
/* Value */
|
|
for (x = 0; x < digits; x++) {
|
|
int digit = value >> ((digits - 1 - x) << 2) & 15;
|
|
char c = DIGITS[digit];
|
|
if (caps == VBU_LOWER && digit >= 10)
|
|
c += 'a' - 'A';
|
|
dest[len++] = c;
|
|
}
|
|
|
|
/* Suffix */
|
|
if (format == VBU_SUFFIX_H)
|
|
dest[len++] = 'h';
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Format a branch operand */
|
|
static int dasmOpBranch(char *dest, VBU_DasmConfig *config,
|
|
uint16_t *code, int type, uint32_t address) {
|
|
int32_t disp; /* Displacement amount */
|
|
int len = 0; /* Length of resulting string */
|
|
|
|
/* Parse the displacement operand */
|
|
disp = type == OP_DISP9 ?
|
|
(int32_t) (int8_t ) (code[0] >> 1) << 1 | (code[0] & 1) :
|
|
(int32_t) (int16_t) (code[0] << 6) << 10 | code[1]
|
|
;
|
|
|
|
/* Absolute target address formatting */
|
|
if (config->branchNotation == VBU_ABSOLUTE)
|
|
return dasmHex(dest, -1, config->hexCase, address + disp, 8);
|
|
|
|
/* Negative displacement */
|
|
if (disp < 0) {
|
|
disp = -disp;
|
|
dest[len++] = '-';
|
|
}
|
|
|
|
/* Positive displacement */
|
|
else dest[len++] = '+';
|
|
|
|
/* Format the displacement as decimal */
|
|
return len + dasmDec(&dest[len], disp);
|
|
}
|
|
|
|
/* Format a condition operand */
|
|
static int dasmOpCondition(char *dest, VBU_DasmConfig *config, uint16_t *code){
|
|
char *src; /* Source mnemonic */
|
|
int x; /* Iterator */
|
|
|
|
/* Retrieve the standalone condition mnemonic */
|
|
src = dasmGetCondition(config,
|
|
code[0] >> ((code[0] >> 15 & 1) ? 9 : 0) & 15, 0, 0);
|
|
|
|
/* Copy the characters to the output */
|
|
for (x = 0; src[x]; x++) {
|
|
char c = src[x];
|
|
if (config->conditionCase == VBU_LOWER && c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
dest[x] = c;
|
|
}
|
|
|
|
/* Return length of string */
|
|
dest[x] = 0;
|
|
return x;
|
|
}
|
|
|
|
/* Format an immediate operand */
|
|
static int dasmOpImmediate(
|
|
char *dest, VBU_DasmConfig *config, uint16_t *code, int type) {
|
|
int len = 0; /* Length of resulting string */
|
|
int32_t value; /* Immediate value */
|
|
|
|
/* Prefix */
|
|
if (config->immediateNotation == VBU_PREFIX_NUMBER)
|
|
dest[len++] = '#';
|
|
|
|
/* Select format type */
|
|
switch (type) {
|
|
case OP_IMM5S:
|
|
value = code[0] & 31;
|
|
return len + dasmDec(&dest[len], value > 15 ? value - 32 : value);
|
|
case OP_IMM5U:
|
|
return len + dasmDec(&dest[len], code[0] & 31);
|
|
case OP_IMM16U:
|
|
return len + dasmHex(&dest[len],
|
|
config->hexNotation, config->hexCase, code[1], 4);
|
|
}
|
|
|
|
/* Parse the value */
|
|
value = (int16_t) code[1];
|
|
|
|
/* Magnitude is small: format as decimal */
|
|
if (value >= -256 && value <= 256)
|
|
return len + dasmDec(&dest[len], value);
|
|
|
|
/* Value is negative */
|
|
if (value < 0) {
|
|
dest[len++] = '-';
|
|
value = -value;
|
|
}
|
|
|
|
/* Magnitude is large: format as hexadecimal */
|
|
return len + dasmHex(&dest[len],
|
|
config->hexNotation, config->hexCase, value, 4);
|
|
}
|
|
|
|
/* Format a program register operand */
|
|
static int dasmOpProgram(
|
|
char *dest, VBU_DasmConfig *config, uint16_t *code, int type) {
|
|
int len = 0; /* Length of resulting string */
|
|
char *src; /* Program register mnemonic */
|
|
int x; /* Scratch */
|
|
|
|
/* Program registers by name */
|
|
static const char *NAMES[] = {
|
|
NULL, NULL, "HP", "SP", "GP", "TP", NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, "LP"
|
|
};
|
|
|
|
/* Program registers by number */
|
|
static const char *NUMBERS[] = {
|
|
"R0" , "R1" , "R2" , "R3" , "R4" , "R5" , "R6" , "R7" ,
|
|
"R8" , "R9" , "R10", "R11", "R12", "R13", "R14", "R15",
|
|
"R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23",
|
|
"R24", "R25", "R26", "R27", "R28", "R29", "R30", "R31"
|
|
};
|
|
|
|
/* Select the program register mnemonic */
|
|
x = code[0] >> (type == OP_REG1 ? 0 : 5) & 31;
|
|
src = (char *) NUMBERS[x];
|
|
if (config->programNotation == VBU_NAMES && NAMES[x] != NULL)
|
|
src = (char *) NAMES[x];
|
|
|
|
/* Format the operand characters */
|
|
for (x = 0; src[x]; x++) {
|
|
char c = src[x];
|
|
if (config->programCase == VBU_LOWER && c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
dest[len++] = c;
|
|
}
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Format a JMP operand */
|
|
static int dasmOpJMP(char *dest, VBU_DasmConfig *config, uint16_t *code) {
|
|
int len = 0; /* Length of resulting string */
|
|
|
|
/* Format the operand characters */
|
|
dest[len++] = '[';
|
|
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
|
|
dest[len++] = ']';
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Format a memory operand */
|
|
static int dasmOpMemory(char *dest, VBU_DasmConfig *config, uint16_t *code) {
|
|
int32_t disp; /* Memory displacement */
|
|
int len = 0; /* Length of resulting string */
|
|
|
|
/* Parse operand */
|
|
disp = (int16_t) code[1];
|
|
|
|
/* No-displacement notation */
|
|
if (disp == 0) {
|
|
dest[len++] = '[';
|
|
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
|
|
dest[len++] = ']';
|
|
}
|
|
|
|
/* Outside notation */
|
|
else if (config->memoryNotation == VBU_OUTSIDE) {
|
|
|
|
/* Magnitude is large: format as hexadecimal */
|
|
if (disp < -256 || disp > 256) {
|
|
|
|
/* Displacement is negative */
|
|
if (disp < 0) {
|
|
dest[len++] = '-';
|
|
disp = -disp;
|
|
}
|
|
|
|
/* Format displacement */
|
|
len += dasmHex(&dest[len], config->hexNotation,
|
|
config->hexCase, disp, 4);
|
|
}
|
|
|
|
/* Magnitude is small: format as decimal */
|
|
else len += dasmDec(&dest[len], disp);
|
|
|
|
/* Format operand characters */
|
|
dest[len++] = '[';
|
|
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
|
|
dest[len++] = ']';
|
|
}
|
|
|
|
/* Inside notation */
|
|
else {
|
|
|
|
/* Format operand characters */
|
|
dest[len++] = '[';
|
|
len += dasmOpProgram(&dest[len], config, code, OP_REG1);
|
|
dest[len++] = ' ';
|
|
|
|
/* Displacement is negative */
|
|
if (disp < 0) {
|
|
dest[len++] = '-';
|
|
disp = -disp;
|
|
}
|
|
|
|
/* Displacement is positive */
|
|
else dest[len++] = '+';
|
|
|
|
/* Format operand characters */
|
|
dest[len++] = ' ';
|
|
|
|
/* Magnitude is large: format as hexadecimal */
|
|
if (disp < -256 || disp > 256) {
|
|
len += dasmHex(&dest[len], config->hexNotation,
|
|
config->hexCase, disp, 4);
|
|
}
|
|
|
|
/* Magnitude is small: format as decimal */
|
|
else len += dasmDec(&dest[len], disp);
|
|
|
|
/* Format operand characters */
|
|
dest[len++] = ']';
|
|
}
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Format a system register operand */
|
|
static int dasmOpSystem(char *dest, VBU_DasmConfig *config, uint16_t *code) {
|
|
int len = 0; /* Length of resulting string */
|
|
char *src; /* System register mnemonic */
|
|
int x; /* Scratch */
|
|
|
|
/* System registers by name */
|
|
static const char *NAMES[] = {
|
|
"EIPC", "EIPSW", "FEPC", "FEPSW", "ECR", "PSW", "PIR", "TKCW",
|
|
NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
|
|
NULL , NULL , NULL , NULL , NULL , NULL , NULL , NULL ,
|
|
"CHCW", "ADTRE", NULL , NULL , NULL , NULL , NULL , NULL
|
|
};
|
|
|
|
/* System registers by number */
|
|
static const char *NUMBERS[] = {
|
|
"0" , "1" , "2" , "3" , "4" , "5" , "6" , "7" ,
|
|
"8" , "9" , "10", "11", "12", "13", "14", "15",
|
|
"16", "17", "18", "19", "20", "21", "22", "23",
|
|
"24", "25", "26", "27", "28", "29", "30", "31"
|
|
};
|
|
|
|
/* Select the system register mnemonic */
|
|
x = code[0] & 31;
|
|
src = (char *) NUMBERS[x];
|
|
if (config->systemNotation == VBU_NAMES && NAMES[x] != NULL)
|
|
src = (char *) NAMES[x];
|
|
|
|
/* Format the operand characters */
|
|
for (x = 0; src[x]; x++) {
|
|
char c = src[x];
|
|
if (config->systemCase == VBU_LOWER && c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
dest[len++] = c;
|
|
}
|
|
|
|
/* Return length of string */
|
|
dest[len] = 0;
|
|
return len;
|
|
}
|
|
|
|
/* Process one line of disassembly */
|
|
static uint32_t dasmLine(DasmOutput *ret, unsigned index,
|
|
VB *sim, uint32_t address, int isPC, VBU_DasmConfig *config) {
|
|
uint16_t code[2]; /* Instruction bits */
|
|
OpDef *def; /* Opcode definition */
|
|
VBU_DasmLine *line; /* Current output line */
|
|
char *mnemonic; /* Mnemonic display text */
|
|
int mnemonicLen; /* Length of mnemonic text */
|
|
char operands[3][16]; /* Operand display text */
|
|
int opLens[3]; /* Operand string lengths */
|
|
size_t textLen; /* Number of text bytes needed */
|
|
int x; /* Scratch */
|
|
|
|
/* Select the current line in the buffer */
|
|
line = &((VBU_DasmLine *) ret->buffer)[index];
|
|
line->address = address;
|
|
line->isPC = isPC;
|
|
|
|
/* Fetch first code halfword */
|
|
code[0] = vbRead(sim, address, VB_U16);
|
|
line->code[0] = code[0];
|
|
line->code[1] = code[0] >> 8;
|
|
|
|
/* Select opcode definition */
|
|
def = (OpDef *) &OPDEFS[code[0] >> 10 & 63];
|
|
line->codeLength = def->codeLength;
|
|
mnemonic = def->mnemonic;
|
|
|
|
/* Fetch second code halfword if necessary */
|
|
if (line->codeLength == 4) {
|
|
code[1] = vbRead(sim, address + 2, VB_U16);
|
|
line->code[2] = code[1];
|
|
line->code[3] = code[1] >> 8;
|
|
} else {
|
|
code[1] = 0;
|
|
line->code[2] = 0;
|
|
line->code[3] = 0;
|
|
}
|
|
|
|
/* Two-step processing */
|
|
switch (def->format) {
|
|
|
|
/* Bcond */
|
|
case FMT_BCOND:
|
|
if (config->bcondNotation == VBU_SPLIT) {
|
|
def = (OpDef *) &BCOND_S;
|
|
mnemonic = def->mnemonic;
|
|
} else {
|
|
x = code[0] >> 9 & 15;
|
|
def = (OpDef *) &BCOND_J;
|
|
mnemonic = dasmGetCondition(config, x, 1, 0);
|
|
}
|
|
break;
|
|
|
|
/* Bit string instructions */
|
|
case FMT_BITSTRING:
|
|
x = code[0] & 31;
|
|
def = (OpDef *) &BITSTRINGS[x > 15 ? 4 : x];
|
|
mnemonic = def->mnemonic;
|
|
break;
|
|
|
|
/* Floating-point and Nintendo instructions */
|
|
case FMT_FLOATENDO:
|
|
x = code[1] >> 10 & 63;
|
|
def = (OpDef *) &FLOATENDOS[x > 12 ? 1 : x];
|
|
mnemonic = def->mnemonic;
|
|
break;
|
|
|
|
/* SETF */
|
|
case FMT_SETF:
|
|
if (config->setfNotation == VBU_SPLIT) {
|
|
def = (OpDef *) &SETF_S;
|
|
mnemonic = def->mnemonic;
|
|
} else {
|
|
x = code[0] & 15;
|
|
def = (OpDef *) &SETF_J;
|
|
mnemonic = dasmGetCondition(config, x, 0, 1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Single template for illegal opcodes */
|
|
if (def->format == FMT_ILLEGAL) {
|
|
def = (OpDef *) &ILLEGAL;
|
|
mnemonic = def->mnemonic;
|
|
}
|
|
|
|
/* Working variables */
|
|
line->text.operandsLength = def->operandsLength;
|
|
for (mnemonicLen = 0; mnemonic[mnemonicLen]; mnemonicLen++);
|
|
textLen = 9 /* Address */ + line->codeLength * 3 + mnemonicLen + 1;
|
|
|
|
/* Process all operands */
|
|
for (x = 0; x < def->operandsLength; x++) {
|
|
int len = 0; /* Length of operand string */
|
|
int type = def->operands[x];
|
|
char *op = operands[x];
|
|
|
|
/* Select operand type */
|
|
switch (def->operands[x]) {
|
|
case OP_COND:
|
|
len = dasmOpCondition(op, config, code);
|
|
break;
|
|
case OP_DISP9: /* Fallthrough */
|
|
case OP_DISP26:
|
|
len = dasmOpBranch (op, config, code, type, address);
|
|
break;
|
|
case OP_IMM5U: /* Fallthrough */
|
|
case OP_IMM5S: /* Fallthrough */
|
|
case OP_IMM16U: /* Fallthrough */
|
|
case OP_IMM16S:
|
|
len = dasmOpImmediate(op, config, code, type);
|
|
break;
|
|
case OP_JMP:
|
|
len = dasmOpJMP (op, config, code);
|
|
break;
|
|
case OP_MEM:
|
|
len = dasmOpMemory (op, config, code);
|
|
break;
|
|
case OP_REG1: /* Fallthrough */
|
|
case OP_REG2:
|
|
len = dasmOpProgram (op, config, code, type);
|
|
break;
|
|
case OP_SYS:
|
|
len = dasmOpSystem (op, config, code);
|
|
break;
|
|
}
|
|
|
|
/* Incorporate text length */
|
|
opLens[x] = len;
|
|
textLen += len + 1;
|
|
}
|
|
|
|
/* Output buffer needs to grow */
|
|
if (ret->length + textLen > ret->capacity) {
|
|
ret->capacity *= 2;
|
|
ret->buffer = VBU_MEMMGR(ret->buffer, ret->capacity);
|
|
}
|
|
|
|
/* Output address */
|
|
line->text.address = (char *) ret->length;
|
|
ret->length += dasmHex(&ret->buffer[ret->length], -1,
|
|
config->hexCase, line->address, 8);
|
|
ret->buffer[ret->length++] = 0;
|
|
|
|
/* Output code */
|
|
for (x = 0; x < line->codeLength; x++) {
|
|
line->text.code[x] = (char *) ret->length;
|
|
ret->length += dasmHex(&ret->buffer[ret->length], -1,
|
|
config->hexCase, line->code[x], 2);
|
|
ret->buffer[ret->length++] = 0;
|
|
}
|
|
for (; x < 4; x++)
|
|
line->text.code[x] = NULL;
|
|
|
|
/* Output mnemonic */
|
|
line->text.mnemonic = (char *) ret->length;
|
|
for (x = 0; x < mnemonicLen; x++) {
|
|
char c = mnemonic[x];
|
|
if (config->mnemonicCase == VBU_LOWER && c >= 'A' && c <= 'Z')
|
|
c += 'a' - 'A';
|
|
ret->buffer[ret->length++] = c;
|
|
}
|
|
ret->buffer[ret->length++] = 0;
|
|
|
|
/* Output operands */
|
|
for (x = 0; x < line->text.operandsLength; x++) {
|
|
char *op = operands[x];
|
|
int len = opLens[x];
|
|
int y;
|
|
line->text.operands[x] = (char *) ret->length;
|
|
for (y = 0; y < len; y++)
|
|
ret->buffer[ret->length++] = op[y];
|
|
ret->buffer[ret->length++] = 0;
|
|
}
|
|
for (; x < 3; x++)
|
|
line->text.operands[x] = NULL;
|
|
|
|
/* Adjust operand order */
|
|
if (config->operandOrder==VBU_DEST_FIRST && line->text.operandsLength>=2) {
|
|
int l = line->text.operandsLength - 1;
|
|
char *t = line->text.operands[0];
|
|
line->text.operands[0] = line->text.operands[l];
|
|
line->text.operands[l] = t;
|
|
}
|
|
|
|
return line->codeLength;
|
|
}
|
|
|
|
|
|
|
|
/***************************** Library Functions *****************************/
|
|
|
|
/* Disassemble instructions from a simulation */
|
|
static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
|
VBU_DasmConfig *config, unsigned length, int line) {
|
|
uint32_t cur; /* Current disassembly address */
|
|
uint32_t pc; /* Current simulation program counter */
|
|
uint32_t rel; /* Line relative to start to the first output line */
|
|
DasmOutput ret; /* Output buffer state */
|
|
uint32_t start; /* At least 10 lines before the first output line */
|
|
unsigned x, y; /* Iterators */
|
|
|
|
/* Working variables */
|
|
pc = vbGetProgramCounter(sim);
|
|
start = (address - (line + 10) * 4) & 0xFFFFFFFE;
|
|
|
|
/* Locate the instruction containing the first line of output */
|
|
for (cur = start, rel = 0;; rel++) {
|
|
uint32_t size = vbuCodeSize(sim, cur);
|
|
|
|
/* Cannot skip over PC or target address */
|
|
if (cur != pc && pc - cur < size)
|
|
size = pc - cur;
|
|
if (cur != address && address - cur < size)
|
|
size = address - cur;
|
|
|
|
/* This instruction contains the target address */
|
|
if (address - cur < size)
|
|
break;
|
|
|
|
/* Advance to the next instruction */
|
|
cur += size;
|
|
}
|
|
|
|
/* Seek to the first line of output */
|
|
for (cur = start; rel > 0; rel--) {
|
|
uint32_t size = vbuCodeSize(sim, cur);
|
|
|
|
/* Cannot skip over PC or target address */
|
|
if (cur != pc && pc - cur < size)
|
|
size = pc - cur;
|
|
if (cur != address && address - cur < size)
|
|
size = address - cur;
|
|
|
|
/* Advance to the next instruction */
|
|
cur += size;
|
|
}
|
|
|
|
/* Process all lines */
|
|
ret.length = length * sizeof (VBU_DasmLine);
|
|
ret.capacity = ret.length * 2;
|
|
ret.buffer = VBU_MEMMGR(NULL, ret.capacity);
|
|
for (x = 0; x < length; x++) {
|
|
uint32_t size;
|
|
|
|
/* Disassemble the instruction */
|
|
size = dasmLine(&ret, x, sim, cur, cur == pc, config);
|
|
|
|
/* Cannot skip over PC or target address */
|
|
if (cur != pc && pc - cur < size)
|
|
size = pc - cur;
|
|
if (cur != address && address - cur < size)
|
|
size = address - cur;
|
|
|
|
/* Advance to the next instruction */
|
|
cur += size;
|
|
}
|
|
|
|
/* Finalize the output buffer */
|
|
if (ret.capacity > ret.length)
|
|
ret.buffer = VBU_MEMMGR(ret.buffer, ret.length);
|
|
for (x = 0; x < length; x++) {
|
|
VBU_DasmLine *line = &((VBU_DasmLine *) ret.buffer)[x];
|
|
|
|
/* Convert text offsets into proper pointers */
|
|
line->text.address = &ret.buffer[(size_t) line->text.address ];
|
|
line->text.mnemonic = &ret.buffer[(size_t) line->text.mnemonic];
|
|
for (y = 0; y < line->codeLength; y++)
|
|
line->text.code[y] = &ret.buffer[(size_t) line->text.code[y]];
|
|
for (y = 0; y < line->text.operandsLength; y++) {
|
|
line->text.operands[y] =
|
|
&ret.buffer[(size_t) line->text.operands[y]];
|
|
}
|
|
|
|
}
|
|
|
|
return (VBU_DasmLine *) ret.buffer;
|
|
}
|
|
|
|
|
|
|
|
#endif /* VBUAPI */
|