mirror of
https://github.com/ksherlock/x65.git
synced 2024-12-29 10:30:32 +00:00
Cycle counter hierarchy
- Each scope in a file shows the cycle count added up for instructions within it. - Added Merlin version of XREF which is EXT (was previously ignored) - Added Merlin cycle counter on/off directive (CYC)
This commit is contained in:
parent
07b2b04763
commit
c50ae5027d
10
README.md
10
README.md
@ -59,7 +59,7 @@ x65 filename.s code.prg [options]
|
||||
* -a2b: Apple II Dos 3.3 Binary
|
||||
* -sym (file.sym): symbol file
|
||||
* -merlin: use Merlin syntax
|
||||
* -lst / -lst=(file.lst): generate disassembly text from result (file or stdout)
|
||||
* -lst / -lst=(file.lst): generate disassembly/cycle/source text from result (file or stdout)
|
||||
* -opcodes / -opcodes=(file.s): dump all available opcodes for current CPU (file or stdout)
|
||||
* -sect: display sections loaded and built
|
||||
* -vice (file.vs): export a vice symbol file
|
||||
@ -662,6 +662,10 @@ EXT imports an external label, same as [**XREF**](#xref).
|
||||
|
||||
LNK links the contents of an object file, to fit with the named section method of linking in x65 this keyword has been reworked to have a similar result, the actual linking doesn't begin until the current section is complete.
|
||||
|
||||
**CYC**
|
||||
|
||||
CYC starts and stops a cycle counter, x65 scoping allows for hierarchical cycle listings but the first merlin directive CYC starts the counter and the next CYC stops the counter and shows the result. This is 6502 only until data is entered for other CPUs.
|
||||
|
||||
**ADR**
|
||||
|
||||
Define byte triplets (like **DA** but three bytes instead of 2)
|
||||
@ -772,6 +776,8 @@ Currently macros with parameters use search and replace without checking if the
|
||||
|
||||
Scopes are lines inbetween '{' and '}' including macros. The purpose of scopes is to reduce the need for local labels and the scopes nest just like C code to support function level and loops and inner loop scoping. '!' is a label that is the first address of the scope and '%' the first address after the scope.
|
||||
|
||||
Additionally scopes have a meaning for counting cycles when exporting a .lst file, each open scope '{' will add a new counter of CPU cycles that will accumulate until the corresponding '}' which will be shown on that line in the listing file. Use -lst as a command line option to generate a listing file.
|
||||
|
||||
This means you can write
|
||||
```
|
||||
{
|
||||
@ -812,10 +818,10 @@ Fish food! Assembler has all important features and switching to make a 6502 pro
|
||||
|
||||
**TODO**
|
||||
* OMF export for Apple II GS/OS executables
|
||||
* Hierarchical cycle timing in list file
|
||||
* irp (indefinite repeat)
|
||||
|
||||
**FIXED**
|
||||
* Enabled EXT and CYC directive for merlin, accumulating cycle timing within scope (Hierarchical cycle timing in list file) / CYC...CYC (6502 only for now)
|
||||
* First pass cycle timing in listing file for 6502 targets
|
||||
* Enums can have comments
|
||||
* XREF required to reference XDEF symbols in object files (.x65)
|
||||
|
121
x65.cpp
121
x65.cpp
@ -249,6 +249,8 @@ enum AssemblerDirective {
|
||||
AD_ADR, // ADR: MERLIN store 3 byte word
|
||||
AD_ADRL, // ADRL: MERLIN store 4 byte word
|
||||
AD_ENT, // ENT: MERLIN extern this address label
|
||||
AD_EXT, // EXT: MERLIN reference this address label from a different file
|
||||
AD_CYC, // CYC: MERLIN start / stop cycle timer
|
||||
};
|
||||
|
||||
// Operators are either instructions or directives
|
||||
@ -937,9 +939,10 @@ DirectiveName aDirectiveNamesMerlin[] {
|
||||
{ "LNK", AD_LNK }, // MERLIN
|
||||
{ "XC", AD_XC }, // MERLIN
|
||||
{ "ENT", AD_ENT }, // MERLIN (xdef, but label on same line)
|
||||
{ "EXT", AD_EJECT }, // MERLIN (xref, which are implied in x65 object files)
|
||||
{ "EXT", AD_EXT }, // MERLIN (xref, which are implied in x65 object files)
|
||||
{ "ADR", AD_ADR }, // ADR: MERLIN store 3 byte word
|
||||
{ "ADRL", AD_ADRL }, // ADRL: MERLIN store 4 byte word
|
||||
{ "CYC", AD_CYC }, // MERLIN: Start and stop cycle counter
|
||||
};
|
||||
|
||||
static const int nDirectiveNames = sizeof(aDirectiveNames) / sizeof(aDirectiveNames[0]);
|
||||
@ -1074,12 +1077,22 @@ typedef std::vector<struct Reloc> relocList;
|
||||
|
||||
// For assembly listing this remembers the location of each line
|
||||
struct ListLine {
|
||||
enum Flags {
|
||||
MNEMONIC = 0x01,
|
||||
KEYWORD = 0x02,
|
||||
CYCLES_START = 0x04,
|
||||
CYCLES_STOP = 0x08,
|
||||
};
|
||||
strref source_name; // source file index name
|
||||
strref code; // line of code this represents
|
||||
int address; // start address of this line
|
||||
int size; // number of bytes generated for this line
|
||||
int line_offs; // offset into code
|
||||
bool was_mnemonic; // only output code if generated by code
|
||||
int flags; // only output code if generated by code
|
||||
|
||||
bool wasMnemonic() const { return !!(flags & MNEMONIC); }
|
||||
bool startClock() const { return !!(flags & CYCLES_START); }
|
||||
bool stopClock() const { return !!(flags & CYCLES_STOP); }
|
||||
};
|
||||
typedef std::vector<struct ListLine> Listing;
|
||||
|
||||
@ -1364,8 +1377,10 @@ public:
|
||||
|
||||
strref export_base_name; // binary output name if available
|
||||
strref last_label; // most recently defined label for Merlin macro
|
||||
char list_flags; // listing flags accumulating for each line
|
||||
bool accumulator_16bit; // 65816 specific software dependent immediate mode
|
||||
bool index_reg_16bit; // -"-
|
||||
char cycle_counter_level; // merlin toggles the cycle counter rather than hierarchically evals
|
||||
bool error_encountered; // if any error encountered, don't export binary
|
||||
bool list_assembly; // generate assembler listing
|
||||
bool end_macro_directive; // whether to use { } or macro / endmacro for macro scope
|
||||
@ -1468,6 +1483,7 @@ public:
|
||||
StatusCode Directive_LOAD(strref line);
|
||||
StatusCode Directive_LNK(strref line);
|
||||
StatusCode Directive_XDEF(strref line);
|
||||
StatusCode Directive_XREF(strref label);
|
||||
|
||||
// Assembler steps
|
||||
StatusCode GetAddressMode(strref line, bool flipXY, unsigned int validModes,
|
||||
@ -1537,6 +1553,7 @@ void Asm::Cleanup() {
|
||||
end_macro_directive = false;
|
||||
accumulator_16bit = false; // default 65816 8 bit immediate mode
|
||||
index_reg_16bit = false; // other CPUs won't be affected.
|
||||
cycle_counter_level = 0;
|
||||
}
|
||||
|
||||
int sortHashLookup(const void *A, const void *B) {
|
||||
@ -3888,6 +3905,24 @@ StatusCode Asm::Directive_XDEF(strref line)
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
StatusCode Asm::Directive_XREF(strref label)
|
||||
{
|
||||
// XREF already defined label => no action
|
||||
if (!GetLabel(label)) {
|
||||
Label *pLabelXREF = AddLabel(label.fnv1a());
|
||||
pLabelXREF->label_name = label;
|
||||
pLabelXREF->pool_name.clear();
|
||||
pLabelXREF->section = -1; // address labels are based on section
|
||||
pLabelXREF->value = 0;
|
||||
pLabelXREF->evaluated = true;
|
||||
pLabelXREF->pc_relative = true;
|
||||
pLabelXREF->external = false;
|
||||
pLabelXREF->constant = false;
|
||||
pLabelXREF->reference = true;
|
||||
}
|
||||
return STATUS_OK;
|
||||
}
|
||||
|
||||
// Action based on assembler directive
|
||||
StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref source_file)
|
||||
{
|
||||
@ -3940,28 +3975,20 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
|
||||
case AD_XDEF:
|
||||
return Directive_XDEF(line.get_trimmed_ws());
|
||||
|
||||
case AD_XREF: {
|
||||
strref label = line.split_range_trim(syntax==SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range);
|
||||
if (Label *pXRefLabel = GetLabel(label))
|
||||
break; // XREF already defined label => no action
|
||||
Label *pLabelXREF = AddLabel(label.fnv1a());
|
||||
pLabelXREF->label_name = label;
|
||||
pLabelXREF->pool_name.clear();
|
||||
pLabelXREF->section = -1; // address labels are based on section
|
||||
pLabelXREF->value = 0;
|
||||
pLabelXREF->evaluated = true;
|
||||
pLabelXREF->pc_relative = true;
|
||||
pLabelXREF->external = false;
|
||||
pLabelXREF->constant = false;
|
||||
pLabelXREF->reference = true;
|
||||
case AD_XREF:
|
||||
Directive_XREF(line.split_range_trim(
|
||||
syntax == SYNTAX_MERLIN ? label_end_char_range_merlin : label_end_char_range));
|
||||
break;
|
||||
}
|
||||
|
||||
case AD_ENT: // MERLIN version of xdef, makes most recently defined label external
|
||||
if (Label *pLastLabel = GetLabel(last_label))
|
||||
pLastLabel->external = true;
|
||||
break;
|
||||
|
||||
case AD_EXT:
|
||||
Directive_XREF(last_label);
|
||||
break;
|
||||
|
||||
case AD_ALIGN: // align: align address to multiple of value, fill space with 0
|
||||
if (line) {
|
||||
if (line[0]=='=' || keyword_equ.is_prefix_word(line))
|
||||
@ -4132,6 +4159,11 @@ StatusCode Asm::ApplyDirective(AssemblerDirective dir, strref line, strref sourc
|
||||
case AD_USR:
|
||||
line.clear();
|
||||
break;
|
||||
|
||||
case AD_CYC:
|
||||
list_flags |= cycle_counter_level ? ListLine::CYCLES_STOP : ListLine::CYCLES_START;
|
||||
cycle_counter_level = !!cycle_counter_level;
|
||||
break;
|
||||
|
||||
case AD_SAV:
|
||||
line.trim_whitespace();
|
||||
@ -4717,8 +4749,7 @@ StatusCode Asm::BuildLine(strref line)
|
||||
int start_section = SectionId();
|
||||
int start_address = CurrSection().address;
|
||||
strref code_line = line;
|
||||
bool built_opcode = false;
|
||||
bool list_keyword = false;
|
||||
list_flags = 0;
|
||||
while (line && error == STATUS_OK) {
|
||||
strref line_start = line;
|
||||
char char0 = line[0]; // first char including white space
|
||||
@ -4745,6 +4776,7 @@ StatusCode Asm::BuildLine(strref line)
|
||||
switch (line[0]) {
|
||||
case '{':
|
||||
error = EnterScope();
|
||||
list_flags |= ListLine::CYCLES_START;
|
||||
if (error == STATUS_OK) {
|
||||
++line;
|
||||
line.skip_whitespace();
|
||||
@ -4753,6 +4785,7 @@ StatusCode Asm::BuildLine(strref line)
|
||||
case '}':
|
||||
// check for late eval of anything with an end scope
|
||||
error = ExitScope();
|
||||
list_flags |= ListLine::CYCLES_STOP;
|
||||
if (error == STATUS_OK) {
|
||||
++line;
|
||||
line.skip_whitespace();
|
||||
@ -4782,10 +4815,10 @@ StatusCode Asm::BuildLine(strref line)
|
||||
}
|
||||
if (aInstructions[op_idx].type==OT_DIRECTIVE) {
|
||||
error = ApplyDirective((AssemblerDirective)aInstructions[op_idx].index, line, contextStack.curr().source_file);
|
||||
list_keyword = true;
|
||||
list_flags |= ListLine::KEYWORD;
|
||||
} else if (ConditionalAsm() && aInstructions[op_idx].type == OT_MNEMONIC) {
|
||||
error = AddOpcode(line, aInstructions[op_idx].index, contextStack.curr().source_file);
|
||||
built_opcode = true;
|
||||
list_flags |= ListLine::MNEMONIC;
|
||||
}
|
||||
line.clear();
|
||||
} else if (!ConditionalAsm()) {
|
||||
@ -4794,14 +4827,16 @@ StatusCode Asm::BuildLine(strref line)
|
||||
++line;
|
||||
error = AssignLabel(label, line);
|
||||
line.clear();
|
||||
list_keyword = true;
|
||||
} else if (keyword_equ.is_prefix_word(line)) {
|
||||
list_flags |= ListLine::KEYWORD;
|
||||
}
|
||||
else if (keyword_equ.is_prefix_word(line)) {
|
||||
line += keyword_equ.get_len();
|
||||
line.skip_whitespace();
|
||||
error = AssignLabel(label, line);
|
||||
line.clear();
|
||||
list_keyword = true;
|
||||
} else {
|
||||
list_flags |= ListLine::KEYWORD;
|
||||
}
|
||||
else {
|
||||
unsigned int nameHash = label.fnv1a();
|
||||
unsigned int macro = FindLabelIndex(nameHash, macros.getKeys(), macros.count());
|
||||
bool gotConstruct = false;
|
||||
@ -4838,7 +4873,7 @@ StatusCode Asm::BuildLine(strref line)
|
||||
line = line_start + int(label.get() + label.get_len() -line_start.get());
|
||||
if (line[0]==':' || line[0]=='?')
|
||||
++line; // there may be codes after the label
|
||||
list_keyword = true;
|
||||
list_flags |= ListLine::KEYWORD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4875,14 +4910,15 @@ StatusCode Asm::BuildLine(strref line)
|
||||
curr.pListing = new Listing;
|
||||
if (curr.pListing && curr.pListing->size() == curr.pListing->capacity())
|
||||
curr.pListing->reserve(curr.pListing->size() + 256);
|
||||
if ((list_keyword || (curr.address != start_address && curr.size())) && !curr.IsDummySection()) {
|
||||
if (((list_flags&(ListLine::KEYWORD|ListLine::CYCLES_START|ListLine::CYCLES_STOP)) ||
|
||||
(curr.address != start_address && curr.size())) && !curr.IsDummySection()) {
|
||||
struct ListLine lst;
|
||||
lst.address = start_address - curr.start_address;
|
||||
lst.size = curr.address - start_address;
|
||||
lst.code = contextStack.curr().source_file;
|
||||
lst.source_name = contextStack.curr().source_name;
|
||||
lst.line_offs = int(code_line.get() - lst.code.get());
|
||||
lst.was_mnemonic = built_opcode;
|
||||
lst.flags = list_flags;
|
||||
curr.pListing->push_back(lst);
|
||||
}
|
||||
}
|
||||
@ -4908,6 +4944,7 @@ StatusCode Asm::BuildSegment()
|
||||
}
|
||||
|
||||
// Produce the assembler listing
|
||||
#define MAX_DEPTH_CYCLE_COUNTER 64
|
||||
bool Asm::List(strref filename)
|
||||
{
|
||||
FILE *f = stdout;
|
||||
@ -4940,6 +4977,12 @@ bool Asm::List(strref filename)
|
||||
}
|
||||
}
|
||||
|
||||
short cycles[MAX_DEPTH_CYCLE_COUNTER][2];
|
||||
short cycles_depth = 0;
|
||||
|
||||
cycles[0][0] = 0;
|
||||
cycles[0][1] = 0;
|
||||
|
||||
strref prev_src;
|
||||
int prev_offs = 0;
|
||||
for (std::vector<Section>::iterator si = allSections.begin(); si != allSections.end(); ++si) {
|
||||
@ -4972,13 +5015,26 @@ bool Asm::List(strref filename)
|
||||
}
|
||||
}
|
||||
|
||||
out.sprintf_append("$%04x ", lst.address + si->start_address);
|
||||
int s = lst.was_mnemonic ? (lst.size < 4 ? lst.size : 4) : (lst.size < 8 ? lst.size : 8);
|
||||
if (lst.size)
|
||||
out.sprintf_append("$%04x ", lst.address + si->start_address);
|
||||
|
||||
int s = lst.wasMnemonic() ? (lst.size < 4 ? lst.size : 4) : (lst.size < 8 ? lst.size : 8);
|
||||
if (si->output && si->output_capacity >= (lst.address + s)) {
|
||||
for (int b = 0; b < s; ++b)
|
||||
out.sprintf_append("%02x ", si->output[lst.address + b]);
|
||||
}
|
||||
if (lst.size && lst.was_mnemonic) {
|
||||
if (lst.startClock() && cycles_depth<MAX_DEPTH_CYCLE_COUNTER) {
|
||||
cycles_depth++; cycles[cycles_depth][0] = 0; cycles[cycles_depth][1] = 0;
|
||||
out.append_to(' ', 25); out.sprintf_append("c>%d", cycles_depth);
|
||||
}
|
||||
if (lst.stopClock()) {
|
||||
out.append_to(' ', 25); out.sprintf_append("c<%d=%d+%d", cycles_depth, cycles[cycles_depth][0], cycles[cycles_depth][1]);
|
||||
if (cycles_depth) {
|
||||
cycles_depth--; cycles[cycles_depth][0] += cycles[cycles_depth + 1][0];
|
||||
cycles[cycles_depth][1] += cycles[cycles_depth + 1][1];
|
||||
}
|
||||
}
|
||||
if (lst.size && lst.wasMnemonic()) {
|
||||
out.append_to(' ', 18);
|
||||
unsigned char *buf = si->output + lst.address;
|
||||
unsigned char op = mnemonic[*buf];
|
||||
@ -5008,11 +5064,14 @@ bool Asm::List(strref filename)
|
||||
else
|
||||
out.sprintf_append(fmt, opcode_table[op].instr, buf[1]);
|
||||
if (aCPUs[cpu].timing) {
|
||||
cycles[cycles_depth][0] += aCPUs[cpu].timing[*buf] / 2;
|
||||
cycles[cycles_depth][1] += aCPUs[cpu].timing[*buf] & 1;
|
||||
out.append_to(' ', 33);
|
||||
out.sprintf_append("%x%s", aCPUs[cpu].timing[*buf] / 2, (aCPUs[cpu].timing[*buf] & 1) ? "+" : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.append_to(' ', aCPUs[cpu].timing ? 36 : 33);
|
||||
strref line = lst.code.get_skipped(lst.line_offs).get_line();
|
||||
line.clip_trailing_whitespace();
|
||||
|
Loading…
Reference in New Issue
Block a user