UNPKG

68kcounter

Version:
284 lines (262 loc) 7.62 kB
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 }); } } } }