527 lines
13 KiB
C
527 lines
13 KiB
C
|
/* cpu.c tr3200 cpu description file */
|
||
|
/* (c) in 2014 by Luis Panadero Guardeno */
|
||
|
|
||
|
#include "vasm.h"
|
||
|
|
||
|
/*#define CPU_DEBUG (1)*/
|
||
|
#ifdef CPU_DEBUG
|
||
|
#define OPERAND_DEBUG (1)
|
||
|
#define INSTR_DEBUG (1)
|
||
|
#endif
|
||
|
|
||
|
char *cpu_copyright="vasm TR3200 cpu module v0.1.2 by Luis Panadero Guardeno";
|
||
|
|
||
|
char *cpuname="tr3200";
|
||
|
int bitsperbyte=8;
|
||
|
int bytespertaddr=4;
|
||
|
|
||
|
mnemonic mnemonics[]={
|
||
|
#include "opcodes.h"
|
||
|
};
|
||
|
|
||
|
int mnemonic_cnt=sizeof(mnemonics)/sizeof(mnemonics[0]);
|
||
|
|
||
|
|
||
|
static taddr opsize(operand *p, unsigned char num_operands, section *sec, taddr pc);
|
||
|
|
||
|
/* parse instruction */
|
||
|
char *parse_instruction(char *s, int *inst_len, char **ext, int *ext_len,
|
||
|
int *ext_cnt)
|
||
|
{
|
||
|
char* inst = s;
|
||
|
#ifdef CPU_DEBUG
|
||
|
fprintf(stderr, "parse_inst : \"%.*s\"\n", *inst_len, s);
|
||
|
#endif
|
||
|
/*
|
||
|
while (*s && !isspace((unsigned char)*s))
|
||
|
s++;
|
||
|
*inst_len = s - inst; */
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
/* Sets op if is a valid register */
|
||
|
static int parse_reg(char **p, int len, operand *op)
|
||
|
{
|
||
|
char *rp = skip(*p);
|
||
|
int reg = -1;
|
||
|
|
||
|
if (len < 2) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (*rp != '%') {
|
||
|
return 0;
|
||
|
}
|
||
|
rp++;
|
||
|
|
||
|
if (tolower((unsigned char)rp[0]) != 'r') {
|
||
|
/* Could be y, bp, sp, ia or flags */
|
||
|
if (len == 2 && tolower((unsigned char)rp[0]) == 'y') {
|
||
|
rp++;
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = 11;
|
||
|
|
||
|
return 1;
|
||
|
} else if ( len == 3 && (tolower((unsigned char)rp[0]) == 'b')
|
||
|
&& (tolower((unsigned char)rp[1]) == 'p') ) {
|
||
|
rp++; rp++;
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = 12;
|
||
|
|
||
|
return 1;
|
||
|
} else if ( len == 3 && (tolower((unsigned char)rp[0]) == 's')
|
||
|
&& (tolower((unsigned char)rp[1]) == 'p') ) {
|
||
|
rp++; rp++;
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = 13;
|
||
|
|
||
|
return 1;
|
||
|
} else if ( len == 3 && (tolower((unsigned char)rp[0]) == 'i')
|
||
|
&& (tolower((unsigned char)rp[1]) == 'a') ) {
|
||
|
rp++; rp++;
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = 14;
|
||
|
|
||
|
return 1;
|
||
|
} else if ( len == 6
|
||
|
&& (tolower((unsigned char)rp[0]) == 'f')
|
||
|
&& (tolower((unsigned char)rp[1]) == 'l')
|
||
|
&& (tolower((unsigned char)rp[2]) == 'a')
|
||
|
&& (tolower((unsigned char)rp[3]) == 'g')
|
||
|
&& (tolower((unsigned char)rp[4]) == 's')
|
||
|
) {
|
||
|
rp += 5;
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = 14;
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
/* It's not a register */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
rp++;
|
||
|
/* Get number */
|
||
|
if (len < 2 || sscanf(rp, "%u", ®) != 1) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* "%r0 .. %r15" are valid */
|
||
|
if (reg < 0 || reg > 15) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* skip digits and return new pointer together with register number */
|
||
|
while ( isdigit((unsigned char)*rp) ) {
|
||
|
rp++;
|
||
|
}
|
||
|
|
||
|
*p = skip(rp);
|
||
|
op->type = OP_GPR;
|
||
|
op->reg = reg;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* Parses operands and reads expressions
|
||
|
* *p string
|
||
|
* len string length
|
||
|
* *op operand
|
||
|
* requires Type of operand expected
|
||
|
*/
|
||
|
int parse_operand(char *p, int len, operand *op, int requires)
|
||
|
{
|
||
|
op->type = NO_OP;
|
||
|
#ifdef OPERAND_DEBUG
|
||
|
fprintf(stderr, "parse_operand (reqs=%02x): \"%.*s\"\t",
|
||
|
(unsigned)requires, len, p);
|
||
|
#endif
|
||
|
|
||
|
/* Try to grab the register */
|
||
|
if (1 != parse_reg(&p, len, op) ) {
|
||
|
#ifdef OPERAND_DEBUG
|
||
|
fprintf(stderr, "imm\t");
|
||
|
#endif
|
||
|
/* Its not a register, should be a immediate value or a expression */
|
||
|
if(p[0]=='#') { /* Immediate value */
|
||
|
expr *tree;
|
||
|
#ifdef OPERAND_DEBUG
|
||
|
fprintf(stderr, "# ");
|
||
|
#endif
|
||
|
op->type = OP_IMM;
|
||
|
p=skip(p+1);
|
||
|
tree = parse_expr(&p);
|
||
|
if (!tree) { /* It's not a valid expresion */
|
||
|
return PO_NOMATCH;
|
||
|
}
|
||
|
op->value = tree;
|
||
|
} else { /* expresion that would be a immediate value */
|
||
|
#ifdef OPERAND_DEBUG
|
||
|
fprintf(stderr, "expr\t");
|
||
|
#endif
|
||
|
op->type = OP_IMM;
|
||
|
|
||
|
int parent=0;
|
||
|
expr *tree;
|
||
|
|
||
|
/*
|
||
|
if (*p=='(') {
|
||
|
parent=1;
|
||
|
p=skip(p+1);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
tree = parse_expr(&p);
|
||
|
if (!tree) { /* It's not a valid expresion */
|
||
|
return PO_NOMATCH ;
|
||
|
}
|
||
|
|
||
|
/* Inside of a ( ) */
|
||
|
/*
|
||
|
p=skip(p);
|
||
|
if(parent) {
|
||
|
if(*p!=')'){
|
||
|
cpu_error(0);
|
||
|
return 0;
|
||
|
} else
|
||
|
p=skip(p+1);
|
||
|
}
|
||
|
*/
|
||
|
|
||
|
op->value=tree;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef OPERAND_DEBUG
|
||
|
fprintf(stderr, "(type=%02x)\n", (unsigned) op->type);
|
||
|
#endif
|
||
|
|
||
|
if(requires == op->type) { /* Matched type */
|
||
|
return PO_MATCH;
|
||
|
}
|
||
|
|
||
|
return PO_NOMATCH; /* Ops! Not match */
|
||
|
}
|
||
|
|
||
|
/* Convert an instruction into a DATA atom including relocations,
|
||
|
if necessary. */
|
||
|
dblock *eval_instruction (instruction *p, section *sec, taddr pc)
|
||
|
{
|
||
|
/* Calc instruction size */
|
||
|
size_t size = instruction_size(p, sec, pc);
|
||
|
dblock *db = new_dblock();
|
||
|
mnemonic m = mnemonics[p->code];
|
||
|
unsigned char *opcode, *d; /* Data */
|
||
|
taddr val;
|
||
|
unsigned char ml_bits = 0;
|
||
|
unsigned char num_operands = 0;
|
||
|
unsigned char srn = 0; /* Size of Rn */
|
||
|
operand* rn = NULL;
|
||
|
symbol *base = NULL;
|
||
|
int btype;
|
||
|
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "eval_instruction code \"%s\" ", m.name);
|
||
|
#endif
|
||
|
|
||
|
num_operands += (m.operand_type[0] != NO_OP)? 1 : 0;
|
||
|
num_operands += (m.operand_type[1] != NO_OP)? 1 : 0;
|
||
|
num_operands += (m.operand_type[2] != NO_OP)? 1 : 0;
|
||
|
if (num_operands > 0) {
|
||
|
rn = p->op[m.ext.rn_pos];
|
||
|
}
|
||
|
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "P%d ", num_operands);
|
||
|
if (num_operands > 0)
|
||
|
fprintf(stderr, "rn type=%02x ", (unsigned) rn->type);
|
||
|
#endif
|
||
|
|
||
|
/* See if Rn is an immediate to set ML bits*/
|
||
|
if (rn != NULL && rn->type == OP_IMM) {
|
||
|
srn = opsize(rn, num_operands, sec, pc);
|
||
|
ml_bits = 2 | (srn == 4);
|
||
|
}
|
||
|
ml_bits = ml_bits << 6; /* Emplace ML bits */
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "IMM=%d ", srn);
|
||
|
fprintf(stderr, "ml %02x ", ml_bits);
|
||
|
#endif
|
||
|
|
||
|
db->size=size;
|
||
|
db->data = mymalloc(size); /* allocate for the data block */
|
||
|
memset(db->data, 0, db->size);
|
||
|
/* Here to write data ! */
|
||
|
d = db->data;
|
||
|
d[3] = m.ext.opcode; /* Common part */
|
||
|
d[2] = ml_bits;
|
||
|
|
||
|
switch (num_operands) {
|
||
|
case 3: /* format P3 */
|
||
|
/* Rn is always at the LSBytes side */
|
||
|
if( rn->type == OP_GPR) { /* Is a register */
|
||
|
d[0] = (rn->reg) & 0xF;
|
||
|
} else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
|
||
|
eval_expr(rn->value, &val, sec, pc);
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %04x ", val);
|
||
|
#endif
|
||
|
d[1] = (val >>8) & 0x3F;
|
||
|
d[0] = (val ) & 0xFF;
|
||
|
} else { /* 32 bit immediate */
|
||
|
eval_expr(rn->value, &val, sec, pc);
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %08x ", val);
|
||
|
#endif
|
||
|
d[7] = (val >>24) & 0xFF;
|
||
|
d[6] = (val >>16) & 0xFF;
|
||
|
d[5] = (val >>8 ) & 0xFF;
|
||
|
d[4] = (val ) & 0xFF;
|
||
|
}
|
||
|
|
||
|
if (m.ext.rn_pos == 1) { /* Special case of STORE */
|
||
|
d[1] |= (p->op[0]->reg & 0xF) << 6;
|
||
|
d[2] |= (p->op[0]->reg & 0xF) >> 2;
|
||
|
d[2] |= (p->op[2]->reg) << 2; /* rd */
|
||
|
} else {
|
||
|
d[1] |= (p->op[1]->reg & 0xF) << 6;
|
||
|
d[2] |= (p->op[1]->reg & 0xF) >> 2;
|
||
|
d[2] |= (p->op[0]->reg) << 2; /* rd */
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 2: /* format P2 */
|
||
|
if( rn->type == OP_GPR) { /* Is a register */
|
||
|
d[0] = (rn->reg) & 0xF;
|
||
|
} else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
|
||
|
eval_expr(rn->value, &val, sec, pc);
|
||
|
/* CALL/JUMP stuff */
|
||
|
if (m.ext.opcode == 0x4B || m.ext.opcode == 0x4C ) {
|
||
|
val = val >> 2; /* CALL/JMP does a left shift of two bits */
|
||
|
}
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %06x ", val);
|
||
|
#endif
|
||
|
d[2] |= (val >>16) & 0x03;
|
||
|
d[1] = (val >>8) & 0xFF;
|
||
|
d[0] = (val ) & 0xFF;
|
||
|
|
||
|
} else { /* 32 bit immediate */
|
||
|
eval_expr(rn->value, &val, sec, pc);
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %08x ", val);
|
||
|
#endif
|
||
|
d[7] = (val >>24) & 0xFF;
|
||
|
d[6] = (val >>16) & 0xFF;
|
||
|
d[5] = (val >>8 ) & 0xFF;
|
||
|
d[4] = (val ) & 0xFF;
|
||
|
}
|
||
|
|
||
|
if (m.ext.rn_pos == 0) { /* Special case of STORE */
|
||
|
d[2] |= (p->op[1]->reg) << 2;
|
||
|
} else {
|
||
|
d[2] |= (p->op[0]->reg) << 2;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 1: /* format P1 */
|
||
|
if( rn->type == OP_GPR) { /* Is a register */
|
||
|
d[0] = (rn->reg) & 0xF;
|
||
|
} else if (ml_bits == 0x80 ) { /* ML are 10 -> immediate */
|
||
|
if (!eval_expr(rn->value, &val, sec, pc))
|
||
|
btype = find_base(rn->value, &base, sec, pc);
|
||
|
/* CALL/JUMP stuff */
|
||
|
if (m.ext.opcode >= 0x27 && m.ext.opcode <= 0x28 ) {
|
||
|
if (base != NULL && btype == BASE_OK) {
|
||
|
if (is_pc_reloc(base, sec))
|
||
|
add_extnreloc_masked(&db->relocs, base, val-4, REL_PC,
|
||
|
0, 22, 0, 0xfffffc);
|
||
|
else if (LOCREF(base))
|
||
|
val = val - pc - 4; /* Relative jump/call (%pc has been increased) */
|
||
|
base = NULL;
|
||
|
}
|
||
|
else
|
||
|
val = val - pc - 4; /* Relative jump/call (%pc has been increased) */
|
||
|
val = val >> 2; /* CALL/JMP does a left shift of two bits */
|
||
|
} else if (m.ext.opcode >= 0x25 && m.ext.opcode <= 0x26 ) {
|
||
|
if (base != NULL && btype != BASE_ILLEGAL) {
|
||
|
add_extnreloc_masked(&db->relocs, base, val,
|
||
|
btype == BASE_PCREL ? REL_PC : REL_ABS,
|
||
|
0, 22, 0, 0xfffffc);
|
||
|
base = NULL;
|
||
|
}
|
||
|
val = val >> 2; /* CALL/JMP does a left shift of two bits */
|
||
|
}
|
||
|
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %06x ", val);
|
||
|
#endif
|
||
|
d[2] |= (val >>16) & 0x3F;
|
||
|
d[1] = (val >>8) & 0xFF;
|
||
|
d[0] = (val ) & 0xFF;
|
||
|
|
||
|
} else { /* 32 bit immediate */
|
||
|
eval_expr(rn->value, &val, sec, pc);
|
||
|
/* CALL/JUMP stuff */
|
||
|
if (m.ext.opcode >= 0x27 && m.ext.opcode <= 0x28 ) {
|
||
|
val = val - pc - 4; /* Relative jump/call */
|
||
|
}
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "val %08x ", val);
|
||
|
#endif
|
||
|
d[7] = (val >>24) & 0xFF;
|
||
|
d[6] = (val >>16) & 0xFF;
|
||
|
d[5] = (val >>8 ) & 0xFF;
|
||
|
d[4] = (val ) & 0xFF;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case 0: /* format NP */
|
||
|
#ifdef INSTR_DEBUG
|
||
|
fprintf(stderr, "NP ");
|
||
|
#endif
|
||
|
default:
|
||
|
;
|
||
|
}
|
||
|
#ifdef INSTR_DEBUG
|
||
|
{
|
||
|
int i;
|
||
|
for(i= db->size -1; i >= 0; i-- ) {
|
||
|
fprintf(stderr, "%02X ", d[i]);
|
||
|
}
|
||
|
}
|
||
|
fprintf(stderr, "\n");
|
||
|
#endif
|
||
|
|
||
|
return db;
|
||
|
}
|
||
|
|
||
|
/* Create a dblock (with relocs, if necessary) for size bits of data. */
|
||
|
dblock *eval_data(operand *op, size_t bitsize, section *sec, taddr pc)
|
||
|
{
|
||
|
dblock *new=new_dblock();
|
||
|
taddr val;
|
||
|
new->size = bitsize >> 3;
|
||
|
new->data = mymalloc(new->size);
|
||
|
#ifdef CPU_DEBUG
|
||
|
fprintf(stderr, "eval_data ");
|
||
|
#endif
|
||
|
if(op->type != OP_IMM) { /* ??? */
|
||
|
#ifdef CPU_DEBUG
|
||
|
fprintf(stderr, "!= OP_IMM\n");
|
||
|
#endif
|
||
|
ierror(0);
|
||
|
}
|
||
|
|
||
|
if(bitsize!=8 && bitsize!=16 && bitsize!=32) {
|
||
|
#ifdef CPU_DEBUG
|
||
|
fprintf(stderr, "bad data size\n");
|
||
|
#endif
|
||
|
cpu_error(2); /* Invalid data size */
|
||
|
}
|
||
|
|
||
|
if(!eval_expr(op->value, &val, sec, pc) ) {
|
||
|
symbol *base;
|
||
|
int btype;
|
||
|
|
||
|
btype = find_base(op->value, &base, sec, pc);
|
||
|
if (base)
|
||
|
add_extnreloc(&new->relocs, base, val,
|
||
|
btype==BASE_PCREL ? REL_PC : REL_ABS, 0, bitsize, 0);
|
||
|
else if (btype != BASE_NONE)
|
||
|
general_error(38); /* illegal relocation */
|
||
|
}
|
||
|
|
||
|
if(bitsize == 8){
|
||
|
new->data[0] = val & 0xFF;
|
||
|
|
||
|
} else if (bitsize == 16){
|
||
|
new->data[1] = (val>>8) & 0xFF;
|
||
|
new->data[0] = val & 0xFF;
|
||
|
|
||
|
} else if (bitsize == 32){
|
||
|
new->data[3] = (val>>24) & 0xFF;
|
||
|
new->data[2] = (val>>16) & 0xFF;
|
||
|
new->data[1] = (val>>8) & 0xFF;
|
||
|
new->data[0] = val & 0xFF;
|
||
|
}
|
||
|
return new;
|
||
|
}
|
||
|
|
||
|
/* Size of a operand
|
||
|
* *p operand
|
||
|
* num_operand Total number of operands
|
||
|
* */
|
||
|
static taddr opsize(operand *p, unsigned char num_operands, section *sec, taddr pc)
|
||
|
{
|
||
|
taddr val = 0;
|
||
|
if(!p) {
|
||
|
return 0;
|
||
|
|
||
|
} else if (p->type == OP_IMM ) {
|
||
|
eval_expr(p->value, &val, sec, pc);
|
||
|
if (num_operands == 3
|
||
|
&& (val < -8192 || val > 8191) ) { /* 14 bits */
|
||
|
return 4; /* 32 bit immediate */
|
||
|
} else if (num_operands == 2
|
||
|
&& (val < -131072 || val > 131071) ) { /* 18 bits */
|
||
|
return 4; /* 32 bit immediate */
|
||
|
} else if (num_operands == 1
|
||
|
&& (val < -2097152 || val > 2097151) ) { /* 22 bits */
|
||
|
return 4; /* 32 bit immediate */
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* Calculate the size of the current instruction; must be identical
|
||
|
to the data created by eval_instruction. */
|
||
|
size_t instruction_size (instruction *p, section *sec, taddr pc)
|
||
|
{
|
||
|
size_t size = 4; /* four bytes */
|
||
|
unsigned char num_operands = 0;
|
||
|
num_operands += (p->op[0] != 0)? 1 : 0;
|
||
|
num_operands += (p->op[1] != 0)? 1 : 0;
|
||
|
num_operands += (p->op[2] != 0)? 1 : 0;
|
||
|
|
||
|
size += opsize(p->op[0], num_operands, sec, pc);
|
||
|
size += opsize(p->op[1], num_operands, sec, pc);
|
||
|
size += opsize(p->op[2], num_operands, sec, pc);
|
||
|
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
operand *new_operand()
|
||
|
{
|
||
|
operand *new = mymalloc(sizeof(*new));
|
||
|
new->type=-1;
|
||
|
return new;
|
||
|
}
|
||
|
|
||
|
/* return true, if initialization was successfull */
|
||
|
int init_cpu()
|
||
|
{
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* return true, if the passed argument is understood */
|
||
|
int cpu_args(char *p)
|
||
|
{
|
||
|
/* no args */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* parse cpu-specific directives; return pointer to end of
|
||
|
cpu-specific text */
|
||
|
char *parse_cpu_special(char *s)
|
||
|
{
|
||
|
/* no specials */
|
||
|
return s;
|
||
|
}
|