2012-02-27 21:14:46 +00:00
|
|
|
// ACME - a crossassembler for producing 6502/65c02/65816 code.
|
2014-03-10 00:17:10 +00:00
|
|
|
// Copyright (C) 1998-2014 Marco Baye
|
2012-02-27 21:14:46 +00:00
|
|
|
// Have a look at "acme.c" for further info
|
|
|
|
//
|
2014-03-11 14:22:32 +00:00
|
|
|
// CPU type stuff
|
2012-02-27 21:14:46 +00:00
|
|
|
#include "config.h"
|
|
|
|
#include "alu.h"
|
|
|
|
#include "cpu.h"
|
|
|
|
#include "dynabuf.h"
|
|
|
|
#include "global.h"
|
|
|
|
#include "input.h"
|
|
|
|
#include "mnemo.h"
|
|
|
|
#include "output.h"
|
|
|
|
#include "tree.h"
|
|
|
|
|
|
|
|
|
|
|
|
// constants
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_6502 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_6502mnemo,
|
|
|
|
CPUFLAG_INDIRECTJMPBUGGY, // JMP ($xxFF) is buggy
|
2014-03-10 00:17:10 +00:00
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_6510 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_6510mnemo,
|
2014-03-10 00:17:10 +00:00
|
|
|
CPUFLAG_INDIRECTJMPBUGGY | // JMP ($xxFF) is buggy
|
|
|
|
CPUFLAG_AB_NEEDS_0_ARG, // LXA #$xx is unstable unless arg is $00
|
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_65c02 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_65c02mnemo,
|
|
|
|
0, // no flags
|
2014-03-10 00:17:10 +00:00
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
|
|
|
/*
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_Rockwell65c02 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_Rockwell65c02mnemo,
|
|
|
|
0, // no flags
|
2014-03-10 00:17:10 +00:00
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_WDC65c02 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_WDC65c02mnemo,
|
|
|
|
0, // no flags
|
2014-03-10 00:17:10 +00:00
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
|
|
|
*/
|
2014-03-10 00:17:10 +00:00
|
|
|
static struct cpu_type cpu_type_65816 = {
|
2012-02-27 21:14:46 +00:00
|
|
|
keyword_is_65816mnemo,
|
|
|
|
CPUFLAG_SUPPORTSLONGREGS, // allows A and XY to be 16bits wide
|
2014-03-10 00:17:10 +00:00
|
|
|
234 // !align fills with "NOP"
|
2012-02-27 21:14:46 +00:00
|
|
|
};
|
|
|
|
#define s_rl (s_brl + 1) // Yes, I know I'm sick
|
|
|
|
|
|
|
|
|
|
|
|
// variables
|
2014-03-10 00:17:10 +00:00
|
|
|
|
2012-02-27 21:14:46 +00:00
|
|
|
// predefined stuff
|
|
|
|
static struct node_t *CPU_tree = NULL; // tree to hold CPU types
|
|
|
|
static struct node_t CPUs[] = {
|
2014-03-10 00:17:10 +00:00
|
|
|
// PREDEFNODE("z80", &cpu_type_Z80),
|
|
|
|
PREDEFNODE("6502", &cpu_type_6502),
|
|
|
|
PREDEFNODE("6510", &cpu_type_6510),
|
|
|
|
PREDEFNODE("65c02", &cpu_type_65c02),
|
|
|
|
// PREDEFNODE("Rockwell65c02", &cpu_type_Rockwell65c02),
|
|
|
|
// PREDEFNODE("WDC65c02", &cpu_type_WDC65c02),
|
|
|
|
PREDEFLAST(s_65816, &cpu_type_65816),
|
2012-02-27 21:14:46 +00:00
|
|
|
// ^^^^ this marks the last element
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// insert byte until PC fits condition
|
2014-03-10 00:17:10 +00:00
|
|
|
// FIXME - move to basics.c
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_align(void)
|
|
|
|
{
|
2014-03-11 14:22:32 +00:00
|
|
|
// FIXME - read cpu state via function call!
|
2012-02-27 21:14:46 +00:00
|
|
|
intval_t and,
|
|
|
|
equal,
|
|
|
|
fill,
|
2014-06-02 00:47:46 +00:00
|
|
|
test = CPU_state.pc.val.intval;
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
// make sure PC is defined.
|
2014-03-10 00:17:10 +00:00
|
|
|
if ((CPU_state.pc.flags & MVALUE_DEFINED) == 0) {
|
2012-02-27 21:14:46 +00:00
|
|
|
Throw_error(exception_pc_undefined);
|
2014-03-10 00:17:10 +00:00
|
|
|
CPU_state.pc.flags |= MVALUE_DEFINED; // do not complain again
|
2012-02-27 21:14:46 +00:00
|
|
|
return SKIP_REMAINDER;
|
|
|
|
}
|
|
|
|
|
|
|
|
and = ALU_defined_int();
|
|
|
|
if (!Input_accept_comma())
|
|
|
|
Throw_error(exception_syntax);
|
|
|
|
equal = ALU_defined_int();
|
|
|
|
if (Input_accept_comma())
|
|
|
|
fill = ALU_any_int();
|
|
|
|
else
|
2014-03-10 00:17:10 +00:00
|
|
|
fill = CPU_state.type->default_align_value;
|
2012-02-27 21:14:46 +00:00
|
|
|
while ((test++ & and) != equal)
|
|
|
|
Output_8b(fill);
|
|
|
|
return ENSURE_EOS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// try to find CPU type held in DynaBuf. Returns whether succeeded.
|
2014-03-10 00:17:10 +00:00
|
|
|
// FIXME - why not return ptr (or NULL to indicate failure)?
|
|
|
|
int CPU_find_cpu_struct(const struct cpu_type **target)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
|
|
|
void *node_body;
|
|
|
|
|
|
|
|
if (!Tree_easy_scan(CPU_tree, &node_body, GlobalDynaBuf))
|
|
|
|
return 0;
|
|
|
|
*target = node_body;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// select CPU ("!cpu" pseudo opcode)
|
2014-03-10 00:17:10 +00:00
|
|
|
// FIXME - move to basics.c
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_cpu(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
const struct cpu_type *cpu_buffer = CPU_state.type; // remember current cpu
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
if (Input_read_and_lower_keyword())
|
2014-03-10 00:17:10 +00:00
|
|
|
if (!CPU_find_cpu_struct(&CPU_state.type))
|
2012-02-27 21:14:46 +00:00
|
|
|
Throw_error("Unknown processor.");
|
|
|
|
// if there's a block, parse that and then restore old value!
|
|
|
|
if (Parse_optional_block())
|
2014-03-10 00:17:10 +00:00
|
|
|
CPU_state.type = cpu_buffer;
|
2012-02-27 21:14:46 +00:00
|
|
|
return ENSURE_EOS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-10 00:17:10 +00:00
|
|
|
static const char Error_old_offset_assembly[] =
|
|
|
|
"\"!pseudopc/!realpc\" is obsolete; use \"!pseudopc {}\" instead.";
|
|
|
|
|
|
|
|
|
|
|
|
// "!realpc" pseudo opcode (now obsolete)
|
|
|
|
// FIXME - move to basics.c
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_realpc(void)
|
2014-03-10 00:17:10 +00:00
|
|
|
{
|
|
|
|
Throw_error(Error_old_offset_assembly);
|
|
|
|
return ENSURE_EOS;
|
|
|
|
}
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
// start offset assembly
|
2014-03-11 14:22:32 +00:00
|
|
|
// FIXME - split into PO (move to basics.c) and backend (move to output.c)
|
2014-03-10 00:17:10 +00:00
|
|
|
// TODO - add a label argument to assign the block size afterwards (for assemble-to-end-address)
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_pseudopc(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-11 14:22:32 +00:00
|
|
|
// FIXME - read pc using a function call!
|
2012-02-27 21:14:46 +00:00
|
|
|
intval_t new_pc,
|
2014-03-10 00:17:10 +00:00
|
|
|
new_offset;
|
|
|
|
int outer_flags = CPU_state.pc.flags;
|
2012-02-27 21:14:46 +00:00
|
|
|
|
|
|
|
// set new
|
2014-03-10 00:17:10 +00:00
|
|
|
new_pc = ALU_defined_int(); // FIXME - allow for undefined!
|
2014-06-02 00:47:46 +00:00
|
|
|
new_offset = (new_pc - CPU_state.pc.val.intval) & 0xffff;
|
|
|
|
CPU_state.pc.val.intval = new_pc;
|
2014-03-10 00:17:10 +00:00
|
|
|
CPU_state.pc.flags |= MVALUE_DEFINED; // FIXME - remove when allowing undefined!
|
2012-02-27 21:14:46 +00:00
|
|
|
// if there's a block, parse that and then restore old value!
|
|
|
|
if (Parse_optional_block()) {
|
|
|
|
// restore old
|
2014-06-02 00:47:46 +00:00
|
|
|
CPU_state.pc.val.intval = (CPU_state.pc.val.intval - new_offset) & 0xffff;
|
2014-03-10 00:17:10 +00:00
|
|
|
CPU_state.pc.flags = outer_flags;
|
2012-02-27 21:14:46 +00:00
|
|
|
} else {
|
2014-03-10 00:17:10 +00:00
|
|
|
// not using a block is no longer allowed
|
|
|
|
Throw_error(Error_old_offset_assembly);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
return ENSURE_EOS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// if cpu type and value match, set register length variable to value.
|
|
|
|
// if cpu type and value don't match, complain instead.
|
|
|
|
static void check_and_set_reg_length(int *var, int make_long)
|
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
if (((CPU_state.type->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) && make_long)
|
2012-02-27 21:14:46 +00:00
|
|
|
Throw_error("Chosen CPU does not support long registers.");
|
|
|
|
else
|
|
|
|
*var = make_long;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set register length, block-wise if needed.
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos set_register_length(int *var, int make_long)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
|
|
|
int old_size = *var;
|
|
|
|
|
|
|
|
// set new register length (or complain - whichever is more fitting)
|
|
|
|
check_and_set_reg_length(var, make_long);
|
|
|
|
// if there's a block, parse that and then restore old value!
|
|
|
|
if (Parse_optional_block())
|
|
|
|
check_and_set_reg_length(var, old_size); // restore old length
|
|
|
|
return ENSURE_EOS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 14:22:32 +00:00
|
|
|
// switch to long accumulator ("!al" pseudo opcode)
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_al(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
return set_register_length(&CPU_state.a_is_long, TRUE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-11 14:22:32 +00:00
|
|
|
// switch to short accumulator ("!as" pseudo opcode)
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_as(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
return set_register_length(&CPU_state.a_is_long, FALSE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// switch to long index registers ("!rl" pseudo opcode)
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_rl(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
return set_register_length(&CPU_state.xy_are_long, TRUE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// switch to short index registers ("!rs" pseudo opcode)
|
2014-03-11 12:07:11 +00:00
|
|
|
static enum eos PO_rs(void)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
2014-03-10 00:17:10 +00:00
|
|
|
return set_register_length(&CPU_state.xy_are_long, FALSE);
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// pseudo opcode table
|
2014-03-10 00:17:10 +00:00
|
|
|
// FIXME - move to basics.c
|
2012-02-27 21:14:46 +00:00
|
|
|
static struct node_t pseudo_opcodes[] = {
|
|
|
|
PREDEFNODE("align", PO_align),
|
|
|
|
PREDEFNODE("cpu", PO_cpu),
|
|
|
|
PREDEFNODE("pseudopc", PO_pseudopc),
|
|
|
|
PREDEFNODE("realpc", PO_realpc),
|
|
|
|
PREDEFNODE("al", PO_al),
|
|
|
|
PREDEFNODE("as", PO_as),
|
|
|
|
PREDEFNODE(s_rl, PO_rl),
|
|
|
|
PREDEFLAST("rs", PO_rs),
|
|
|
|
// ^^^^ this marks the last element
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// set default values for pass
|
2014-03-10 00:17:10 +00:00
|
|
|
void CPU_passinit(const struct cpu_type *cpu_type)
|
2012-02-27 21:14:46 +00:00
|
|
|
{
|
|
|
|
// handle cpu type (default is 6502)
|
2014-03-10 00:17:10 +00:00
|
|
|
CPU_state.type = cpu_type ? cpu_type : &cpu_type_6502;
|
2012-02-27 21:14:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// create cpu type tree (is done early)
|
|
|
|
void CPUtype_init(void)
|
|
|
|
{
|
|
|
|
Tree_add_table(&CPU_tree, CPUs);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// register pseudo opcodes (done later)
|
2014-03-10 00:17:10 +00:00
|
|
|
// FIXME - move to basics.c
|
2012-02-27 21:14:46 +00:00
|
|
|
void CPU_init(void)
|
|
|
|
{
|
|
|
|
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
|
|
|
|
}
|