68kcounter
Version:
68000 ASM source code cycle counter
284 lines (262 loc) • 7.62 kB
text/typescript
import { baseTimes, lookupTimes } from "./tables";
import {
Qualifiers,
AddressingMode,
AddressingModes,
Mnemonics,
mnemonicGroups,
Mnemonic,
} from "../syntax";
import instructionQualifier from "../parse/instructionQualifier";
import { EffectiveAddressNode, InstructionStatement } from "../parse/nodes";
import evaluate, { Variables } from "../parse/evaluate";
/**
* Timing vector:
* cycles, reads, writes
*/
export type Timing = [number, number, number];
/**
* Result of timing lookup for an instruction
*/
export interface InstructionTiming {
values: Timing[];
labels: string[];
calculation?: Calculation;
}
/**
* Describes how the timings are calculated
*/
export interface Calculation {
base: Timing[];
ea?: Timing;
multiplier?: Timing;
/** Known number or range */
n?: number | [number, number];
}
export function popCount(x: number): number {
x -= (x >> 1) & 0x55555555;
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0f0f0f0f;
x += x >> 8;
x += x >> 16;
return x & 0x7f;
}
/**
* Look up timing information for a parsed instruction statement
*/
export function instructionTimings(
statement: InstructionStatement,
vars: Variables
): InstructionTiming | null {
const key = buildKey(statement);
if (!key || !timingMap.has(key)) {
return null;
}
const calculation = { ...(timingMap.get(key) as Calculation) };
const timings: Timing[] = [...calculation.base];
const {
opcode: { op },
operands,
} = statement;
const source = operands[0];
// Calculate n multiplier:
if (calculation.multiplier) {
// Shift
if (mnemonicGroups.SHIFT.includes(op.name)) {
if (source.mode === AddressingModes.Imm) {
calculation.n = evaluate(source.text, vars) || [1, 8];
} else {
// Range for register
calculation.n = [0, 63];
}
}
// MULU
else if (op.name === Mnemonics.MULU) {
// n = the number of ones in the <ea>
const value = evaluate(source.text, vars);
if (source.mode === AddressingModes.Imm && value !== undefined) {
calculation.n = popCount(value);
} else {
calculation.n = [0, 16];
}
}
// MULS
else if (op.name === Mnemonics.MULS) {
// n = concatenate the <ea> with a zero as the LSB;
// n is the resultant number of 10 or 01 patterns in the 17-bit source;
// i.e. worst case happens when the source is $5555
const value = evaluate(source.text, vars);
if (source.mode === AddressingModes.Imm && value !== undefined) {
calculation.n = popCount((value ^ (value << 1)) & 0xffff);
} else {
calculation.n = [0, 16];
}
}
// MOVEM
else if (op.name === Mnemonics.MOVEM) {
const listOperand = operands.find(
(o) => o.mode === AddressingModes.RegList
);
if (listOperand) {
calculation.n = listOperand && rangeN(listOperand.text);
} else {
calculation.n = 1;
}
}
// Apply multiplier:
if (calculation.n) {
// Range
if (Array.isArray(calculation.n)) {
for (const i in calculation.n) {
const m = multiplyTiming(calculation.multiplier, calculation.n[i]);
timings[i] = addTimings(calculation.base[0], m);
}
}
// Single value
else {
const m = multiplyTiming(calculation.multiplier, calculation.n);
for (const i in timings) {
timings[i] = addTimings(timings[i], m);
}
}
}
}
// Add effective address lookup
if (calculation.ea) {
for (const i in timings) {
timings[i] = addTimings(timings[i], calculation.ea);
}
}
// Add labels for multiple values
const labels = timings.length > 1 ? timingLabels(op.name) : [];
return { values: timings, labels, calculation };
}
/**
* Convert timing to string per the 68000 documentation
*
* clock(read/write)
*/
export const formatTiming = (timing: Timing): string =>
`${timing[0]}(${timing[1]}/${timing[2]})`;
/**
* Add two timing vectors
*/
export function addTimings(a: Timing, b: Timing): Timing {
return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
}
/**
* Multiply a timing vector by a scalar value
*/
export function multiplyTiming(t: Timing, scalar: number): Timing {
return [t[0] * scalar, t[1] * scalar, t[2] * scalar];
}
/**
* Calculate timing n value from register range used in MOVEM
*/
export function rangeN(range: string): number {
return range.split("/").reduce((acc, v) => {
const [from, to] = v.split("-").map((n) => {
const t = n[0].toUpperCase();
return parseInt(n.substr(1), 10) + (t === "A" ? 8 : 0);
});
return acc + (to ? to - from + 1 : 1);
}, 0);
}
/**
* Get text labels for multiple timings
*/
export function timingLabels(op: Mnemonic): string[] {
if ([...mnemonicGroups.SCC, ...mnemonicGroups.BCC].includes(op)) {
return ["Taken", "Not taken"];
}
if (mnemonicGroups.DBCC.includes(op)) {
return ["Taken", "Not taken", "Expired"];
}
if (op === Mnemonics.CHK) {
return ["No trap", "Trap >", "Trap <"];
}
if (op === Mnemonics.TRAPV) {
return ["No trap", "Trap"];
}
// Default
return ["Min", "Max"];
}
/**
* Build string key for map lookup
*/
function buildKey(statement: InstructionStatement): string | null {
const { opcode, operands } = statement;
if (!opcode) {
return null;
}
let key = opcode.op.name;
const qualifier = instructionQualifier(statement);
if (qualifier) {
key += "." + qualifier;
}
if (operands.length) {
key +=
" " + (operands as EffectiveAddressNode[]).map((o) => o.mode).join(",");
}
return key;
}
// Flatten table into key/value for simple lookup by instruction string
// e.g.
// "MOVE.L Dn,Dn": [4, 1, 0]
const timingMap = new Map<string, Calculation>();
for (const row of baseTimes) {
const [mnemonics, qualifiers, operands, base, multiplier] = row;
for (const mnemonic of mnemonics) {
for (const qualifier of qualifiers) {
let key = String(mnemonic);
if (qualifier) {
key += "." + qualifier;
}
const eaSize = qualifier === Qualifiers.L ? 1 : 0;
let o: AddressingMode;
if (Array.isArray(operands[0])) {
// EA lookup in source
for (o of operands[0]) {
let k = key + " " + o;
if (operands[1]) {
k += "," + operands[1];
}
let ea: Timing | undefined;
if (lookupTimes[o]) {
ea = lookupTimes[o][eaSize];
// Special cases:
if (mnemonic === Mnemonics.TAS && o === AddressingModes.AbsW) {
ea = [8, 1, 0];
}
if (
(mnemonic === Mnemonics.TAS ||
mnemonic === Mnemonics.CHK ||
mnemonic === Mnemonics.MULS ||
mnemonic === Mnemonics.MULU) &&
o === AddressingModes.AbsL
) {
ea = [12, 2, 0];
}
}
timingMap.set(k, { base, ea, multiplier });
}
} else if (Array.isArray(operands[1])) {
// EA lookup in dest
for (o of operands[1]) {
const k = key + " " + operands[0] + "," + o;
let ea: Timing | undefined;
if (lookupTimes[o]) {
ea = lookupTimes[o][eaSize];
}
timingMap.set(k, { base, ea, multiplier });
}
} else {
// Regular operands
if (operands.length) {
key += " " + operands.join(",");
}
timingMap.set(key, { base: base, multiplier });
}
}
}
}