1
0
mirror of https://github.com/ksherlock/x65.git synced 2024-12-28 19:32:25 +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:
Carl-Henrik Skårstedt 2015-10-31 14:34:45 -07:00
parent 07b2b04763
commit c50ae5027d
2 changed files with 98 additions and 33 deletions

View File

@ -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
View File

@ -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();