├── .husky ├── .gitignore └── pre-commit ├── .gitignore ├── images ├── icon.png └── logo.svg ├── babel.config.js ├── src ├── index.ts ├── parse │ ├── index.ts │ ├── evaluate.ts │ ├── instructionQualifier.ts │ ├── tokenize.ts │ ├── operandMode.ts │ ├── nodes.ts │ └── Parser.ts ├── sizes │ ├── index.ts │ ├── directiveSize.ts │ └── instructionSize.ts ├── levels.ts ├── totals.ts ├── cli.ts ├── formatters.ts ├── timings │ ├── index.ts │ └── tables.ts └── syntax.ts ├── .editorconfig ├── tsconfig.json ├── test ├── syntax.test.ts ├── parse │ ├── evaluate.test.ts │ ├── instructionQualifier.test.ts │ ├── operandMode.test.ts │ └── index.test.ts ├── levels.test.ts ├── sizes │ ├── instructionSize.test.ts │ └── directiveSize.test.ts ├── totals.test.ts ├── timings │ ├── instructions.test.ts │ └── index.test.ts └── examples │ └── example.s ├── .eslintrc.js ├── LICENSE ├── package.json └── README.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /coverage 3 | /dist 4 | .vscode 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grahambates/68kcounter/HEAD/images/icon.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | npm run test 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: "> 1%, not dead" }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import parse from "./parse"; 2 | 3 | export default parse; 4 | export * from "./parse"; 5 | export * from "./timings"; 6 | export * from "./levels"; 7 | export * from "./totals"; 8 | export * from "./syntax"; 9 | -------------------------------------------------------------------------------- /src/parse/index.ts: -------------------------------------------------------------------------------- 1 | import Parser, { Line } from "./Parser"; 2 | 3 | export * from "./Parser"; 4 | export * from "./nodes"; 5 | 6 | /** 7 | * Parse multiple lines of ASM code 8 | */ 9 | export default function parse(input: string): Line[] { 10 | const parser = new Parser(); 11 | return parser.parse(input); 12 | } 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es2020" 5 | ], 6 | "target": "es2020", 7 | "module": "commonjs", 8 | "strict": true, 9 | "noImplicitAny": true, 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "declaration": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "outDir": "dist" 15 | }, 16 | "exclude": ["node_modules"], 17 | "include": ["src/**/*.ts"] 18 | } -------------------------------------------------------------------------------- /src/sizes/index.ts: -------------------------------------------------------------------------------- 1 | import { Variables } from "../parse/evaluate"; 2 | import { StatementNode } from "../parse/nodes"; 3 | import directiveSize from "./directiveSize"; 4 | import instructionSize from "./instructionSize"; 5 | 6 | export default function statementSize( 7 | statement: StatementNode, 8 | vars: Variables 9 | ): number { 10 | if (statement.isDirective()) { 11 | return directiveSize(statement, vars); 12 | } 13 | if (statement.isInstruction()) { 14 | return instructionSize(statement); 15 | } 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /test/syntax.test.ts: -------------------------------------------------------------------------------- 1 | import { isMnemonic, isQualifier } from "../src/syntax"; 2 | 3 | describe("isMnemonic()", () => { 4 | test("valid", () => { 5 | expect(isMnemonic("MOVE")).toBeTruthy(); 6 | }); 7 | test("invalid", () => { 8 | expect(isMnemonic("FOO")).toBeFalsy(); 9 | }); 10 | }); 11 | 12 | describe("isQualifier()", () => { 13 | test("valid", () => { 14 | expect(isQualifier("B")).toBeTruthy(); 15 | expect(isQualifier("W")).toBeTruthy(); 16 | expect(isQualifier("L")).toBeTruthy(); 17 | }); 18 | test("invalid", () => { 19 | expect(isMnemonic("X")).toBeFalsy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | extends: [ 4 | "eslint:recommended", 5 | "plugin:@typescript-eslint/recommended", 6 | "plugin:prettier/recommended", 7 | "plugin:import/errors", 8 | "plugin:import/warnings", 9 | "plugin:import/typescript", 10 | ], 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: "module", 14 | }, 15 | env: { 16 | node: true, 17 | jest: true, 18 | es6: true, 19 | }, 20 | rules: { 21 | "prettier/prettier": "error", 22 | }, 23 | plugins: ["import", "@typescript-eslint/eslint-plugin"], 24 | settings: { 25 | "import/resolver": { 26 | node: { 27 | extensions: [".js", ".jsx", ".ts"], 28 | }, 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/levels.ts: -------------------------------------------------------------------------------- 1 | import { Timing } from "./timings"; 2 | 3 | export type Level = "vhigh" | "high" | "med" | "low"; 4 | 5 | /** 6 | * Warning level timing/length display 7 | */ 8 | export const Levels = { 9 | VHigh: "vhigh", 10 | High: "high", 11 | Med: "med", 12 | Low: "low", 13 | } as const; 14 | 15 | /** 16 | * Get warning level for timing 17 | */ 18 | export function timingLevel(timing: Timing): Level { 19 | if (timing[0] > 30) { 20 | return Levels.VHigh; 21 | } 22 | if (timing[0] > 20) { 23 | return Levels.High; 24 | } 25 | if (timing[0] >= 12) { 26 | return Levels.Med; 27 | } 28 | return Levels.Low; 29 | } 30 | 31 | /** 32 | * Get warning level for byte length 33 | */ 34 | export function lengthLevel(bytes: number): Level { 35 | if (bytes > 6) { 36 | return Levels.VHigh; 37 | } 38 | if (bytes > 4) { 39 | return Levels.High; 40 | } 41 | if (bytes === 4) { 42 | return Levels.Med; 43 | } 44 | return Levels.Low; 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) (c) 2023 Graham Bates 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/parse/evaluate.ts: -------------------------------------------------------------------------------- 1 | import * as expEval from "expression-eval"; 2 | 3 | export type Variables = Record; 4 | 5 | /** 6 | * Try to evaluate an ASM expression to a numeric value. 7 | * 8 | * @param expression Text value 9 | * @param vars Optional variables to substitute in expression 10 | */ 11 | export default function evaluate( 12 | expression: string, 13 | vars: Variables = {} 14 | ): number | undefined { 15 | // Transform ASM expression syntax to be compatible with `expression-eval` 16 | const preprocessed = expression 17 | // Remove immediate prefix 18 | .replace(/^#/, "") 19 | // Hex 20 | .replace(/\$([0-9a-f]+)/gi, (_, p1) => eval("0x" + p1)) 21 | // Binary 22 | .replace(/%([0-1]+)/gi, (_, p1) => eval("0b" + p1)) 23 | // Octal 24 | .replace(/@([0-7]+)/gi, (_, p1) => eval("0o" + p1)) 25 | // OR 26 | .replace(/(?<=[a-z0-9_])!(?=[a-z0-9_])/g, "|") 27 | // XOR 28 | .replace(/(?<=[a-z0-9_])~(?=[a-z0-9_])/g, "^"); 29 | 30 | try { 31 | const ast = expEval.parse(preprocessed); 32 | return expEval.eval(ast, vars); 33 | } catch (e) { 34 | // ignore 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/parse/evaluate.test.ts: -------------------------------------------------------------------------------- 1 | import evaluate from "../../src/parse/evaluate"; 2 | 3 | describe("evaluate", () => { 4 | test("decimal", () => { 5 | expect(evaluate("3")).toEqual(3); 6 | }); 7 | 8 | test("hex", () => { 9 | expect(evaluate("$ff")).toEqual(255); 10 | }); 11 | 12 | test("octal", () => { 13 | expect(evaluate("@10")).toEqual(8); 14 | }); 15 | 16 | test("binary", () => { 17 | expect(evaluate("%110")).toEqual(6); 18 | }); 19 | 20 | test("addition", () => { 21 | expect(evaluate("$2+%10+2")).toEqual(6); 22 | }); 23 | 24 | test("parens", () => { 25 | expect(evaluate("(3+1)*2")).toEqual(8); 26 | }); 27 | 28 | test("vars", () => { 29 | expect(evaluate("x+y", { x: 1, y: 2 })).toEqual(3); 30 | }); 31 | 32 | test("xor", () => { 33 | expect(evaluate("12^8")).toEqual(4); 34 | expect(evaluate("12~8")).toEqual(4); 35 | }); 36 | 37 | test("or", () => { 38 | expect(evaluate("2|4")).toEqual(6); 39 | expect(evaluate("2!4")).toEqual(6); 40 | }); 41 | 42 | test("modulo", () => { 43 | expect(evaluate("11%4")).toEqual(3); 44 | }); 45 | 46 | test("not", () => { 47 | expect(evaluate("~1")).toEqual(-2); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /test/levels.test.ts: -------------------------------------------------------------------------------- 1 | import { lengthLevel, Levels, timingLevel } from "../src/levels"; 2 | 3 | describe("lengthLevel()", () => { 4 | test("low", () => { 5 | const result = lengthLevel(2); 6 | expect(result).toEqual(Levels.Low); 7 | }); 8 | 9 | test("med", () => { 10 | const result = lengthLevel(4); 11 | expect(result).toEqual(Levels.Med); 12 | }); 13 | 14 | test("high", () => { 15 | const result = lengthLevel(6); 16 | expect(result).toEqual(Levels.High); 17 | }); 18 | 19 | test("vhigh", () => { 20 | const result = lengthLevel(8); 21 | expect(result).toEqual(Levels.VHigh); 22 | }); 23 | }); 24 | 25 | describe("timingLevel()", () => { 26 | test("low", () => { 27 | const result = timingLevel([4, 0, 0]); 28 | expect(result).toEqual(Levels.Low); 29 | }); 30 | 31 | test("med", () => { 32 | const result = timingLevel([12, 0, 0]); 33 | expect(result).toEqual(Levels.Med); 34 | }); 35 | 36 | test("high", () => { 37 | const result = timingLevel([21, 0, 0]); 38 | expect(result).toEqual(Levels.High); 39 | }); 40 | 41 | test("vhigh", () => { 42 | const result = timingLevel([31, 0, 0]); 43 | expect(result).toEqual(Levels.VHigh); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/sizes/instructionSize.test.ts: -------------------------------------------------------------------------------- 1 | import parse from "../../src/parse"; 2 | 3 | describe("instructionSize", () => { 4 | test("immediate W", () => { 5 | const [result] = parse(" add.w #1,d0"); 6 | expect(result.bytes).toEqual(4); 7 | }); 8 | 9 | test("immediate L", () => { 10 | const [result] = parse(" add.l #1,d0"); 11 | expect(result.bytes).toEqual(6); 12 | }); 13 | 14 | test("immediate quick", () => { 15 | const [result] = parse(" addq #1,d0"); 16 | expect(result.bytes).toEqual(2); 17 | }); 18 | 19 | test("abs.w", () => { 20 | const [result] = parse(" add.l d0,$f00.w"); 21 | expect(result.bytes).toEqual(4); 22 | }); 23 | 24 | test("abs.l", () => { 25 | const [result] = parse(" add.l d0,$f00"); 26 | expect(result.bytes).toEqual(6); 27 | }); 28 | 29 | test("bit operation", () => { 30 | const [result] = parse(" bchg #1,d0"); 31 | expect(result.bytes).toEqual(4); 32 | }); 33 | 34 | test("Bcc short", () => { 35 | const [result] = parse(" bra.s #foo"); 36 | expect(result.bytes).toEqual(2); 37 | }); 38 | 39 | test("Bcc word", () => { 40 | const [result] = parse(" bra.w #foo"); 41 | expect(result.bytes).toEqual(4); 42 | }); 43 | 44 | test("movem", () => { 45 | const [result] = parse(" movem.w d0-d6,-(sp)"); 46 | expect(result.bytes).toEqual(4); 47 | }); 48 | 49 | test("nop", () => { 50 | const [result] = parse(" nop"); 51 | expect(result.bytes).toEqual(2); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /test/sizes/directiveSize.test.ts: -------------------------------------------------------------------------------- 1 | import parse from "../../src/parse"; 2 | 3 | describe("directiveSize", () => { 4 | test("dc.w", () => { 5 | const [result] = parse(" dc.w 0,0,0"); 6 | expect(result.bytes).toEqual(6); 7 | }); 8 | 9 | test("dw", () => { 10 | const [result] = parse(" dw 0,0,0"); 11 | expect(result.bytes).toEqual(6); 12 | }); 13 | 14 | test("dc.x", () => { 15 | const [result] = parse(" dc.x 0,0,0"); 16 | expect(result.bytes).toEqual(36); 17 | }); 18 | 19 | test("dcb.w", () => { 20 | const [result] = parse(" dcb.w #3"); 21 | expect(result.bytes).toEqual(6); 22 | }); 23 | 24 | test("ds.w", () => { 25 | const [result] = parse(" ds.w #3"); 26 | expect(result.bytes).toEqual(6); 27 | }); 28 | 29 | test("dcb.w from vars", () => { 30 | const code = ` 31 | foo=2 32 | dcb.w #foo*2 33 | `; 34 | const result = parse(code); 35 | expect(result[2].bytes).toEqual(8); 36 | }); 37 | 38 | test("dc.b string", () => { 39 | const [result] = parse('.GfxLib:dc.b "graphics.library",0,0'); 40 | expect(result.bytes).toEqual(18); 41 | }); 42 | 43 | test("dc.b string: single quotes", () => { 44 | const [result] = parse(".GfxLib:dc.b 'graphics.library',0,0"); 45 | expect(result.bytes).toEqual(18); 46 | }); 47 | 48 | test("dcb.w from label range", () => { 49 | const code = ` 50 | start: 51 | dc.w 0 52 | dc.l 0 53 | end: 54 | dcb.b end-start 55 | `; 56 | const result = parse(code); 57 | expect(result[5].bytes).toEqual(6); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/totals.test.ts: -------------------------------------------------------------------------------- 1 | import { Line, StatementNode } from "../src/parse"; 2 | import { calculateTotals } from "../src/totals"; 3 | 4 | describe("calculateTotals", () => { 5 | const statement = new StatementNode(" move.w d0,d1"); 6 | test("single timings", () => { 7 | const lines: Line[] = [ 8 | { statement, timing: { values: [[4, 2, 1]], labels: [] }, bytes: 2 }, 9 | { statement, timing: { values: [[8, 1, 0]], labels: [] }, bytes: 1 }, 10 | { statement }, 11 | { statement, timing: { values: [[12, 0, 1]], labels: [] }, bytes: 2 }, 12 | ]; 13 | const result = calculateTotals(lines); 14 | expect(result.isRange).toEqual(false); 15 | expect(result.min).toEqual([24, 3, 2]); 16 | expect(result.max).toEqual([24, 3, 2]); 17 | expect(result.bytes).toEqual(5); 18 | }); 19 | 20 | test("range", () => { 21 | const lines: Line[] = [ 22 | { 23 | statement, 24 | timing: { 25 | values: [ 26 | [4, 2, 1], 27 | [6, 2, 1], 28 | ], 29 | labels: [], 30 | }, 31 | bytes: 2, 32 | }, 33 | { statement, timing: { values: [[8, 1, 0]], labels: [] }, bytes: 1 }, 34 | { statement }, 35 | { statement, timing: { values: [[12, 0, 1]], labels: [] }, bytes: 2 }, 36 | ]; 37 | const result = calculateTotals(lines); 38 | expect(result.isRange).toEqual(true); 39 | expect(result.min).toEqual([24, 3, 2]); 40 | expect(result.max).toEqual([26, 3, 2]); 41 | expect(result.bytes).toEqual(5); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/parse/instructionQualifier.test.ts: -------------------------------------------------------------------------------- 1 | import { InstructionStatement } from "../../src/parse/nodes"; 2 | import { AddressingModes, Mnemonics, Qualifiers } from "../../src/syntax"; 3 | import instructionQualifier from "../../src/parse/instructionQualifier"; 4 | 5 | describe("instructionQualifier()", () => { 6 | test("word default", () => { 7 | const result = instructionQualifier({ 8 | opcode: { op: { name: Mnemonics.MOVE } }, 9 | } as InstructionStatement); 10 | expect(result).toEqual(Qualifiers.W); 11 | }); 12 | 13 | test("long default", () => { 14 | const result = instructionQualifier({ 15 | opcode: { op: { name: Mnemonics.MOVEQ } }, 16 | } as InstructionStatement); 17 | expect(result).toEqual(Qualifiers.L); 18 | }); 19 | 20 | test("byte default", () => { 21 | const result = instructionQualifier({ 22 | opcode: { op: { name: Mnemonics.SCC } }, 23 | } as InstructionStatement); 24 | expect(result).toEqual(Qualifiers.B); 25 | }); 26 | 27 | test("bit op - register", () => { 28 | const result = instructionQualifier({ 29 | opcode: { op: { name: Mnemonics.BTST } }, 30 | operands: [{}, { mode: AddressingModes.Dn }], 31 | } as InstructionStatement); 32 | expect(result).toEqual(Qualifiers.L); 33 | }); 34 | 35 | test("bit op - memory", () => { 36 | const result = instructionQualifier({ 37 | opcode: { op: { name: Mnemonics.BTST } }, 38 | operands: [{}, { mode: AddressingModes.AbsW }], 39 | } as InstructionStatement); 40 | expect(result).toEqual(Qualifiers.B); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "68kcounter", 3 | "version": "3.2.0", 4 | "description": "68000 ASM source code cycle counter", 5 | "keywords": [ 6 | "68000", 7 | "assembly", 8 | "asm", 9 | "optimization", 10 | "amiga", 11 | "commodore", 12 | "fs-uae", 13 | "win-uae" 14 | ], 15 | "scripts": { 16 | "test": "jest", 17 | "lint": "eslint . --ext .ts --ignore-path .gitignore", 18 | "build": "tsc --build", 19 | "clean": "tsc --build --clean", 20 | "prepare": "husky install" 21 | }, 22 | "main": "dist/index.js", 23 | "bin": { 24 | "68kcounter": "dist/cli.js" 25 | }, 26 | "author": "Graham Bates ", 27 | "license": "MIT", 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/grahambates/68kcounter.git" 31 | }, 32 | "devDependencies": { 33 | "@babel/preset-env": "^7.13.12", 34 | "@babel/preset-typescript": "^7.13.0", 35 | "@types/jest": "^26.0.22", 36 | "@types/node": "^14.14.37", 37 | "@typescript-eslint/eslint-plugin": "^4.20.0", 38 | "@typescript-eslint/parser": "^4.20.0", 39 | "eslint": "^7.23.0", 40 | "eslint-config-prettier": "^8.1.0", 41 | "eslint-plugin-import": "^2.22.1", 42 | "eslint-plugin-prettier": "^3.3.1", 43 | "husky": "^6.0.0", 44 | "jest": "^26.6.3", 45 | "prettier": "^2.2.1", 46 | "typescript": "^4.2.3" 47 | }, 48 | "dependencies": { 49 | "@types/yargs": "^17.0.24", 50 | "chalk": "^4.1.0", 51 | "expression-eval": "^4.0.0", 52 | "json5": "^2.2.0", 53 | "yargs": "^17.7.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Logo](images/icon.png) 2 | 3 | # 68k Counter 4 | 5 | Analyses 68000 assembly source to profile resource and size data. For each instruction it will tell you. 6 | 7 | - CPU cycles 8 | - Bus read cycles 9 | - Bus write cycles 10 | - Size in bytes 11 | 12 | ## Usage: 13 | 14 | ### Web app 15 | 16 | You can try out the tool in a web-based version. 17 | 18 | ### CLI 19 | 20 | To analyse a source file run: 21 | 22 | `npx 68kcounter mysource.s` 23 | 24 | This will output each line prefixed with profile data in the following format: 25 | 26 | `[cycles]([reads]/[writes]) [size]` 27 | 28 | See `npx 68kcounter --help` for more options. 29 | 30 | ### VS Code extension 31 | 32 | Available as VS Code extension to provide live annotations and totals. 33 | 34 | ![Output window screenshot](https://github.com/grahambates/68kcounter-vscode/raw/HEAD/images/demo.gif) 35 | 36 | ## Limitations: 37 | 38 | - Because it analyses your pre-assembled source, it can't take into account 39 | optimisations made by your assembler. 40 | - Total timings for a whole file are pretty meaningless as it doesn't take 41 | into account branching etc, but it can be useful for smaller blocks. 42 | - While it adds profile information inside any macro definitions, it doesn't 43 | currently process macro invocations 44 | - Where timings are based on an 'n' multiplier from an immediate value, it 45 | will parse simple expressions but doesn't currently substitute constants 46 | defined elsewhere. 47 | -------------------------------------------------------------------------------- /src/totals.ts: -------------------------------------------------------------------------------- 1 | import { Line } from "./parse"; 2 | import { Timing } from "./timings"; 3 | 4 | export interface Totals { 5 | /** 6 | * Does this show a range of values based on whether branches are followed or not? 7 | * i.e. are max and min different? 8 | */ 9 | isRange: boolean; 10 | /** Maximum total times */ 11 | max: Timing; 12 | /** Minimum total times */ 13 | min: Timing; 14 | /** Total bytes */ 15 | bytes: number; 16 | /** BSS bytes */ 17 | bssBytes: number; 18 | /** Object (non-BSS) bytes */ 19 | objectBytes: number; 20 | } 21 | 22 | /** 23 | * Total timings and lengths across a range of lines 24 | */ 25 | export function calculateTotals(lines: Line[]): Totals { 26 | let bytes = 0; 27 | let bssBytes = 0; 28 | let objectBytes = 0; 29 | const min: Timing = [0, 0, 0]; 30 | const max: Timing = [0, 0, 0]; 31 | 32 | for (const line of lines) { 33 | if (line.bytes) { 34 | bytes += line.bytes; 35 | if (line.bss) { 36 | bssBytes += line.bytes; 37 | } else { 38 | objectBytes += line.bytes; 39 | } 40 | } 41 | const timings = line.timing?.values; 42 | if (!timings) { 43 | continue; 44 | } 45 | 46 | const clocks = timings.map((n) => n[0]); 47 | const reads = timings.map((n) => n[1]); 48 | const writes = timings.map((n) => n[2]); 49 | min[0] += Math.min(...clocks); 50 | min[1] += Math.min(...reads); 51 | min[2] += Math.min(...writes); 52 | max[0] += Math.max(...clocks); 53 | max[1] += Math.max(...reads); 54 | max[2] += Math.max(...writes); 55 | } 56 | 57 | const isRange = min[0] !== max[0] || min[1] !== max[1] || min[2] !== max[2]; 58 | 59 | return { min, max, isRange, bytes, bssBytes, objectBytes }; 60 | } 61 | -------------------------------------------------------------------------------- /src/parse/instructionQualifier.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AddressingModes, 3 | Mnemonic, 4 | mnemonicGroups, 5 | Mnemonics, 6 | Qualifier, 7 | Qualifiers, 8 | } from "../syntax"; 9 | import { InstructionStatement } from "./nodes"; 10 | 11 | /** 12 | * Gets the size qualifier of an instruction, applying defaults where not specified 13 | */ 14 | export default function instructionQualifier({ 15 | opcode: { op, qualifier }, 16 | operands, 17 | }: InstructionStatement): Qualifier | null { 18 | if (qualifier) { 19 | return qualifier.name; 20 | } else if (longDefault.includes(op.name)) { 21 | return Qualifiers.L; 22 | } else if (byteDefault.includes(op.name)) { 23 | return Qualifiers.B; 24 | } else if (bitOps.includes(op.name)) { 25 | return operands[1] && operands[1].mode === AddressingModes.Dn 26 | ? Qualifiers.L 27 | : Qualifiers.B; 28 | } else if (unsized.includes(op.name)) { 29 | return null; 30 | } 31 | return Qualifiers.W; 32 | } 33 | 34 | // Default to word size for instructions apart from these exceptions: 35 | const unsized: Mnemonic[] = [ 36 | Mnemonics.JMP, 37 | Mnemonics.JSR, 38 | Mnemonics.NOP, 39 | Mnemonics.RESET, 40 | Mnemonics.RTE, 41 | Mnemonics.RTR, 42 | Mnemonics.RTS, 43 | Mnemonics.STOP, 44 | Mnemonics.TRAP, 45 | Mnemonics.TRAPV, 46 | Mnemonics.UNLK, 47 | Mnemonics.ILLEGAL, 48 | ]; 49 | const longDefault: Mnemonic[] = [ 50 | Mnemonics.MOVEQ, 51 | Mnemonics.EXG, 52 | Mnemonics.LEA, 53 | Mnemonics.PEA, 54 | ]; 55 | const byteDefault: Mnemonic[] = [ 56 | Mnemonics.NBCD, 57 | Mnemonics.ABCD, 58 | Mnemonics.SBCD, 59 | Mnemonics.SCC, 60 | Mnemonics.TAS, 61 | ...mnemonicGroups.SCC, 62 | ]; 63 | const bitOps: Mnemonic[] = [ 64 | Mnemonics.BCHG, 65 | Mnemonics.BSET, 66 | Mnemonics.BCLR, 67 | Mnemonics.BTST, 68 | ]; 69 | -------------------------------------------------------------------------------- /src/sizes/directiveSize.ts: -------------------------------------------------------------------------------- 1 | import evaluate, { Variables } from "../parse/evaluate"; 2 | import { DirectiveStatement, Node, StringNode } from "../parse/nodes"; 3 | import { Directives, Qualifier, Qualifiers } from "../syntax"; 4 | 5 | /** 6 | * Get byte size of directive statement 7 | */ 8 | export default function directiveSize( 9 | { opcode: { op, qualifier }, operands }: DirectiveStatement, 10 | vars: Variables 11 | ): number { 12 | // DC: 13 | if (op.name === Directives.DC && qualifier) { 14 | if (qualifier.name === Qualifiers.B) { 15 | return operandBytes(operands); 16 | } else { 17 | return operands.length * qualifierBytes[qualifier.name]; 18 | } 19 | } 20 | if (op.name === Directives.DB) { 21 | return operandBytes(operands); 22 | } 23 | if (op.name === Directives.DW) { 24 | return operands.length * 2; 25 | } 26 | if (op.name === Directives.DL) { 27 | return operands.length * 4; 28 | } 29 | 30 | // DCB / DS: 31 | if ( 32 | (op.name === Directives.DCB || op.name === Directives.DS) && 33 | qualifier && 34 | operands[0] 35 | ) { 36 | const n = evaluate(operands[0].text, vars); 37 | if (n) { 38 | const bytes = qualifierBytes[qualifier.name]; 39 | return bytes * n; 40 | } 41 | } 42 | return 0; 43 | } 44 | 45 | const qualifierBytes: Record = { 46 | B: 1, 47 | W: 2, 48 | L: 4, 49 | S: 4, 50 | D: 8, 51 | Q: 8, 52 | X: 12, 53 | }; 54 | 55 | /** 56 | * Get byte count for a list of operands on dc.b / db 57 | * 58 | * Handles quoted strings as well as individual byte values. 59 | */ 60 | function operandBytes(operands: Node[]): number { 61 | let count = 0; 62 | for (const arg of operands) { 63 | if (arg instanceof StringNode) { 64 | count += arg.value.length; 65 | } else { 66 | count++; 67 | } 68 | } 69 | return count; 70 | } 71 | -------------------------------------------------------------------------------- /src/sizes/instructionSize.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Mnemonics, 3 | mnemonicGroups, 4 | Mnemonic, 5 | AddressingModes, 6 | AddressingMode, 7 | Qualifiers, 8 | } from "../syntax"; 9 | import { InstructionStatement } from "../parse/nodes"; 10 | 11 | const bitOps: Mnemonic[] = [Mnemonics.BCHG, Mnemonics.BCLR, Mnemonics.BTST]; 12 | const branchOps: Mnemonic[] = [ 13 | Mnemonics.BRA, 14 | Mnemonics.BSR, 15 | ...mnemonicGroups.BCC, 16 | ]; 17 | const quick: Mnemonic[] = [Mnemonics.MOVEQ, Mnemonics.ADDQ, Mnemonics.SUBQ]; 18 | const doubles: Mnemonic[] = [ 19 | ...mnemonicGroups.DBCC, 20 | Mnemonics.LINK, 21 | Mnemonics.MOVEM, 22 | Mnemonics.MOVEP, 23 | Mnemonics.STOP, 24 | ]; 25 | 26 | const dispTypes: AddressingMode[] = [ 27 | AddressingModes.AnDisp, 28 | AddressingModes.AnDispIx, 29 | AddressingModes.PcDisp, 30 | AddressingModes.PcDispIx, 31 | ]; 32 | 33 | /** 34 | * Get byte size of instruction statement 35 | */ 36 | export default function instructionSize({ 37 | opcode: { op, qualifier }, 38 | operands, 39 | }: InstructionStatement): number { 40 | // Bcc.W is 2 words 41 | if (branchOps.includes(op.name)) { 42 | return qualifier && qualifier.name === Qualifiers.B ? 2 : 4; 43 | } 44 | // These instructions are always 2 words 45 | if (doubles.includes(op.name)) { 46 | return 4; 47 | } 48 | // Unary instructions are always 1 word 49 | if (!operands.length) { 50 | return 2; 51 | } 52 | 53 | let words = 1; 54 | 55 | for (const { mode } of operands) { 56 | // Absolute value: 57 | if (mode === AddressingModes.AbsW) { 58 | words += 1; 59 | } else if (mode === AddressingModes.AbsL) { 60 | words += 2; 61 | } 62 | // Displacement 63 | else if (dispTypes.includes(mode)) { 64 | words += 1; 65 | } 66 | // Immediate value: 67 | else if ( 68 | mode === AddressingModes.Imm && 69 | !quick.includes(op.name) && 70 | !mnemonicGroups.SHIFT.includes(op.name) 71 | ) { 72 | if (bitOps.includes(op.name)) { 73 | words += 1; 74 | } else { 75 | words += qualifier && qualifier.name === Qualifiers.L ? 2 : 1; 76 | } 77 | } 78 | } 79 | 80 | return words * 2; 81 | } 82 | -------------------------------------------------------------------------------- /src/parse/tokenize.ts: -------------------------------------------------------------------------------- 1 | export type Token = [number, string]; 2 | 3 | const separators = [" ", "\t", ",", ":"]; 4 | 5 | /** 6 | * Split text into tokens 7 | */ 8 | export default function tokenize(text: string): Token[] { 9 | let parenLevel = 0; 10 | let inDoubleQuotes = false; 11 | let inSingleQuotes = false; 12 | let started = false; 13 | let startIndex = 0; 14 | 15 | const tokens: Token[] = []; 16 | 17 | for (let i = 0; i < text.length; i++) { 18 | const char = text[i]; 19 | 20 | // Toggle quoting 21 | if (!inSingleQuotes && char === '"') { 22 | inDoubleQuotes = !inDoubleQuotes; 23 | } 24 | if (!inDoubleQuotes && char === "'") { 25 | inSingleQuotes = !inSingleQuotes; 26 | } 27 | const inQuotes = inDoubleQuotes || inSingleQuotes; 28 | 29 | // Update parenthesis nesting level 30 | if (!inQuotes) { 31 | if (char === "(") { 32 | parenLevel++; 33 | } else if (char === ")") { 34 | parenLevel--; 35 | } 36 | } 37 | 38 | if (inQuotes || parenLevel) { 39 | if (!started) { 40 | started = true; 41 | startIndex = i; 42 | } 43 | continue; 44 | } 45 | 46 | // Comment start: 47 | if (char === ";" || (char === "*" && !started && text[i + 1] !== "+")) { 48 | // Finish any token in progress 49 | if (started) { 50 | tokens.push([startIndex, text.slice(startIndex, i)]); 51 | } 52 | // Push rest of text as comment and done 53 | tokens.push([i, text.slice(i)]); 54 | return tokens; 55 | } 56 | 57 | if (char === "=") { 58 | if (started) { 59 | tokens.push([startIndex, text.slice(startIndex, i)]); 60 | } 61 | tokens.push([i, char]); 62 | started = false; 63 | continue; 64 | } 65 | 66 | if (separators.includes(char)) { 67 | if (started) { 68 | // End of token 69 | tokens.push([startIndex, text.slice(startIndex, i)]); 70 | started = false; 71 | } 72 | } else if (!started) { 73 | // First non-separator value - start new token 74 | started = true; 75 | startIndex = i; 76 | } 77 | } 78 | 79 | // Finish any token in progress 80 | if (started) { 81 | tokens.push([startIndex, text.slice(startIndex)]); 82 | } 83 | 84 | return tokens; 85 | } 86 | -------------------------------------------------------------------------------- /src/parse/operandMode.ts: -------------------------------------------------------------------------------- 1 | import { AddressingMode, AddressingModes } from "../syntax"; 2 | 3 | /** 4 | * Look up addressing mode of an operand string 5 | */ 6 | export default function operandMode(operand: string): AddressingMode { 7 | const match = addressingModePatterns.find((t) => t.exp.exec(operand)); 8 | return match ? match.type : AddressingModes.AbsL; 9 | } 10 | 11 | // Common regex components 12 | const dn = "(d[0-7])"; 13 | const an = "(a[0-7]|sp)"; 14 | const rn = "([ad][0-7]|sp)"; 15 | const pc = "pc"; 16 | const op = "\\(\\s*"; 17 | const cp = "\\s*\\)"; 18 | const comma = "\\s*,\\s*"; 19 | const idx = `${rn}(\\.(w|l))?`; 20 | 21 | /** 22 | * Regular expressions to identify operand type from text. 23 | */ 24 | const addressingModePatterns: { type: AddressingMode; exp: RegExp }[] = [ 25 | { type: AddressingModes.Dn, exp: new RegExp(`^${dn}$`, "i") }, 26 | { type: AddressingModes.An, exp: new RegExp(`^${an}$`, "i") }, 27 | { 28 | type: AddressingModes.AnIndir, 29 | exp: new RegExp(`^${op + an + cp}$`, "i"), 30 | }, 31 | { 32 | type: AddressingModes.AnPostInc, 33 | // (An)+ 34 | exp: new RegExp(`^${op + an + cp}\\+$`, "i"), 35 | }, 36 | { 37 | type: AddressingModes.AnPreDec, 38 | // -(An) 39 | exp: new RegExp(`^-${op + an + cp}$`, "i"), 40 | }, 41 | { 42 | type: AddressingModes.AnDispIx, 43 | // An,Idx) 44 | exp: new RegExp(an + comma + idx + cp, "i"), 45 | }, 46 | { 47 | type: AddressingModes.AnDisp, 48 | exp: new RegExp( 49 | // d(An) | (d,An) 50 | `(\\w${op + an}|${op}[-\\w]+${comma + an + cp}$)`, 51 | "i" 52 | ), 53 | }, 54 | { 55 | type: AddressingModes.PcDispIx, 56 | // PC,Idx) 57 | exp: new RegExp(pc + comma + idx + cp, "i"), 58 | }, 59 | { 60 | type: AddressingModes.PcDisp, 61 | exp: new RegExp( 62 | // d(PC) | (d,PC) 63 | `(\\w${op + pc}|${op}[-\\w]+${comma + pc + cp}$)`, 64 | "i" 65 | ), 66 | }, 67 | { type: AddressingModes.Imm, exp: /^#./i }, 68 | { 69 | type: AddressingModes.RegList, 70 | // Rn[/-]Rn 71 | exp: new RegExp(`${rn}[\\/-]${rn}`, "i"), 72 | }, 73 | { type: AddressingModes.CCR, exp: /^ccr$/i }, 74 | { type: AddressingModes.SR, exp: /^sr$/i }, 75 | { type: AddressingModes.USP, exp: /^usp$/i }, 76 | { type: AddressingModes.AbsW, exp: /\.W$/i }, 77 | ]; 78 | -------------------------------------------------------------------------------- /test/timings/instructions.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses and asserts correct timings for every instruction in `instructions.s` 3 | */ 4 | 5 | import fs from "fs"; 6 | import parse from "../../src/parse"; 7 | import evaluate from "../../src/parse/evaluate"; 8 | import { Timing } from "../../src/timings"; 9 | 10 | const instructions = fs 11 | .readFileSync(__dirname + "/../examples/instructions.s") 12 | .toString(); 13 | 14 | /** 15 | * Extract expected timings from line comment 16 | */ 17 | function parseComment( 18 | comment: string, 19 | m?: number 20 | ): { total: Timing; index: number } { 21 | let index = 0; 22 | const [timings, caseText] = comment 23 | .substr(1) 24 | .split(";") 25 | .map((n) => n.trim()); 26 | 27 | // The comment suffix determines which timing we looks at where there are multiple values 28 | switch (caseText) { 29 | case "Branch not taken": 30 | case "Branch not taken, cc true": 31 | case "cc true": 32 | case "Trap": 33 | case "Trap, d0 > ": 34 | index = 1; 35 | break; 36 | case "Branch not taken, counter expired": 37 | case "Trap, d0 < 0": 38 | index = 2; 39 | break; 40 | } 41 | 42 | if (timings.includes("=")) { 43 | const total = parseTime(timings.split("=").pop().trim(), m); 44 | return { total, index }; 45 | } else { 46 | return { 47 | total: parseTime(timings, m), 48 | index, 49 | }; 50 | } 51 | } 52 | 53 | /** 54 | * Convert string formatted timing 'x(y/z)' 55 | */ 56 | const parseTime = (str: string, m?: number): Timing => 57 | str 58 | .match(/([\d+m]+)\(([\d+m]+)\/([\d+m]+)\)/) 59 | .slice(1) 60 | .map((n) => evaluate(n.replace(/(\d)m/, "$1*m"), { m })) as Timing; 61 | 62 | describe("instruction timings", () => { 63 | const lines = parse(instructions); 64 | for (let i = 0; i < lines.length; i++) { 65 | const { statement, timing } = lines[i]; 66 | if ( 67 | // Does this look like it should be an instruction? 68 | // Don't rely on `isInstruction()` as this won't catch unsupported mnemonics 69 | !statement.isDirective() && 70 | !statement.isLabel() && 71 | statement.text.trim() && // ignore empty lines 72 | !statement.text.match(/^\s*;/) // ignore comment-only lines 73 | ) { 74 | test(statement.text, () => { 75 | expect(statement.isInstruction()).toBeTruthy(); 76 | const { values: timings, calculation } = timing; 77 | const m = Array.isArray(calculation.n) 78 | ? calculation.n[0] 79 | : calculation.n; 80 | const { total, index } = parseComment(statement.comment.text, m); 81 | const idx = Math.min(index, timings.length - 1); 82 | expect(timings[idx]).toEqual(total); 83 | }); 84 | } 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fs from "fs"; 3 | import path from "path"; 4 | import yargs from "yargs"; 5 | import { hideBin } from "yargs/helpers"; 6 | 7 | import parse, { calculateTotals } from "."; 8 | import { 9 | Formatter, 10 | IncludedElements, 11 | JsonFormatter, 12 | PlainTextFormatter, 13 | } from "./formatters"; 14 | 15 | // CLI args: 16 | 17 | const argv = yargs(hideBin(process.argv)) 18 | .usage("Usage: $0 [options] [file]") 19 | .options({ 20 | format: { 21 | describe: "Output format", 22 | choices: ["text", "json"] as const, 23 | default: "text", 24 | alias: "f", 25 | }, 26 | color: { 27 | describe: "Color plain text output", 28 | type: "boolean", 29 | default: true, 30 | alias: "c", 31 | }, 32 | prettyPrint: { 33 | describe: "Print JSON with whitespace", 34 | type: "boolean", 35 | default: true, 36 | }, 37 | include: { 38 | describe: "Elements to include (default all)", 39 | type: "string", 40 | default: "text,timings,bytes,totals", 41 | alias: "i", 42 | }, 43 | width: { 44 | describe: "Width of annotation column in text output", 45 | type: "number", 46 | default: 30, 47 | alias: "w", 48 | }, 49 | }) 50 | .parseSync(); 51 | 52 | // Get input data: 53 | 54 | const options = { 55 | ...argv, 56 | include: parseIncludeList(argv.include), 57 | }; 58 | 59 | let formatter: Formatter; 60 | if (argv.format === "json") { 61 | formatter = new JsonFormatter(options); 62 | } else { 63 | formatter = new PlainTextFormatter(options); 64 | } 65 | 66 | const file = argv._[0]; 67 | 68 | if (file) { 69 | const filePath = path.resolve(String(file)); 70 | if (!fs.existsSync(filePath)) { 71 | console.error(`File "${file}" not found`); 72 | process.exit(1); 73 | } 74 | const text = fs.readFileSync(filePath).toString(); 75 | processText(text); 76 | } else { 77 | let buf = ""; 78 | process.stdin 79 | .on("data", (data) => { 80 | const str = data.toString(); 81 | buf = buf + str; 82 | // Flush on EOF character (ctrl+z) 83 | if (str.endsWith("\x26")) { 84 | processText(buf); 85 | buf = ""; 86 | } 87 | }) 88 | .on("end", () => { 89 | processText(buf); 90 | buf = ""; 91 | }); 92 | } 93 | 94 | function processText(text: string): void { 95 | const lines = parse(text); 96 | const totals = calculateTotals(lines); 97 | const output = formatter.format(lines, totals); 98 | console.log(output); 99 | } 100 | 101 | function parseIncludeList(list: string): IncludedElements { 102 | const elements = list.toLowerCase().split(","); 103 | return { 104 | text: elements.includes("text"), 105 | timings: elements.includes("timings"), 106 | bytes: elements.includes("bytes"), 107 | totals: elements.includes("totals"), 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /src/formatters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Formatter implementations for CLI output 3 | */ 4 | 5 | import chalk, { Color } from "chalk"; 6 | 7 | import { formatTiming, Level, Levels, timingLevel } from "."; 8 | import { Timing } from "./timings"; 9 | import { Line } from "./parse"; 10 | import { Totals } from "./totals"; 11 | 12 | export interface IncludedElements { 13 | text: boolean; 14 | timings: boolean; 15 | bytes: boolean; 16 | totals: boolean; 17 | } 18 | 19 | export interface Formatter { 20 | format(lines: Line[], totals: Totals): string; 21 | } 22 | 23 | export interface JsonOptions { 24 | /** Pretty print JSON with whtespace */ 25 | prettyPrint: boolean; 26 | /** Elements to include in output */ 27 | include: IncludedElements; 28 | } 29 | 30 | export interface PlainTextOptions { 31 | /** Color text output in terminal */ 32 | color: boolean; 33 | /** Elements to include in output */ 34 | include: IncludedElements; 35 | /** Width of annotation column */ 36 | width: number; 37 | } 38 | 39 | export class JsonFormatter implements Formatter { 40 | constructor(private options: JsonOptions) {} 41 | 42 | format(lines: Line[], totals: Totals): string { 43 | const inc = this.options.include; 44 | 45 | const output = { 46 | lines: 47 | inc.text || inc.timings || inc.bytes 48 | ? lines.map((l) => ({ 49 | text: inc.text ? l.statement.text : undefined, 50 | timing: inc.timings ? l.timing : undefined, 51 | bytes: inc.bytes ? l.bytes : undefined, 52 | })) 53 | : undefined, 54 | totals: inc.totals ? totals : undefined, 55 | }; 56 | 57 | return this.options.prettyPrint 58 | ? JSON.stringify(output, null, 2) 59 | : JSON.stringify(output); 60 | } 61 | } 62 | 63 | export class PlainTextFormatter implements Formatter { 64 | constructor(private options: PlainTextOptions) {} 65 | 66 | static levelToColor: Record = { 67 | [Levels.VHigh]: "bgRed", 68 | [Levels.High]: "red", 69 | [Levels.Med]: "yellow", 70 | [Levels.Low]: "green", 71 | }; 72 | 73 | format(lines: Line[], totals: Totals): string { 74 | const inc = this.options.include; 75 | 76 | let output: string[] = []; 77 | 78 | if (inc.text || inc.timings || inc.bytes) { 79 | output = lines.map((l) => { 80 | let annotation = ""; 81 | if (l.timing && inc.timings) { 82 | const format = this.options.color 83 | ? this.formatTimingColored 84 | : formatTiming; 85 | annotation += l.timing.values.map(format).join(" / "); 86 | } 87 | if (l.bytes && inc.bytes) { 88 | annotation += " " + this.formatNumber(l.bytes); 89 | } 90 | annotation = this.pad(annotation, this.options.width); 91 | if (inc.text) { 92 | annotation += " | " + l.statement.text; 93 | } 94 | return annotation; 95 | }); 96 | } 97 | 98 | if (inc.totals) { 99 | output.push("\nTotals:"); 100 | if (totals.isRange) { 101 | output.push( 102 | formatTiming(totals.min) + " - " + formatTiming(totals.max) 103 | ); 104 | } else { 105 | output.push(formatTiming(totals.min)); 106 | } 107 | output.push( 108 | `${this.formatNumber(totals.bytes)} bytes (${this.formatNumber( 109 | totals.objectBytes 110 | )} object, ${this.formatNumber(totals.bssBytes)} BSS)` 111 | ); 112 | } 113 | 114 | return output.join("\n"); 115 | } 116 | 117 | private formatTimingColored(timing: Timing) { 118 | const output = formatTiming(timing); 119 | const level = timingLevel(timing); 120 | return chalk[PlainTextFormatter.levelToColor[level]](output); 121 | } 122 | 123 | /** 124 | * Display a string with padding 125 | */ 126 | private pad(str: string, l: number) { 127 | /*eslint-disable no-control-regex */ 128 | const strClean = str.replace(/(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]/g, ""); 129 | const p = l - strClean.length; 130 | return p > 0 ? Array(p).fill(" ").join("") + str : str; 131 | } 132 | 133 | private formatNumber(num: number): string { 134 | return num.toLocaleString("en"); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /test/timings/index.test.ts: -------------------------------------------------------------------------------- 1 | import parse from "../../src/parse"; 2 | import { Mnemonics } from "../../src/syntax"; 3 | import { 4 | formatTiming, 5 | rangeN, 6 | popCount, 7 | timingLabels, 8 | } from "../../src/timings"; 9 | 10 | describe("timings", () => { 11 | test("move", () => { 12 | const [result] = parse(" move.w d0,d1"); 13 | expect(result.timing.values).toEqual([[4, 1, 0]]); 14 | }); 15 | 16 | test("EA source", () => { 17 | const [result] = parse(" add.w (a0),d1"); 18 | expect(result.timing.values).toEqual([[8, 2, 0]]); 19 | }); 20 | 21 | test("EA dest", () => { 22 | const [result] = parse(" add.w d1,(a0)"); 23 | expect(result.timing.values).toEqual([[12, 2, 1]]); 24 | }); 25 | 26 | test("movem n multiplier - source", () => { 27 | const [result] = parse(" movem.l d0-a6,-(sp)"); 28 | const n = 15; 29 | expect(result.timing.values).toEqual([[8 + 8 * n, 2, 2 * n]]); 30 | }); 31 | 32 | test("movem n multiplier - dest", () => { 33 | const [result] = parse(" movem.l DrawBuffer(PC),a2-a3"); 34 | const n = 2; 35 | expect(result.timing.values).toEqual([[16 + 8 * n, 4 + 2 * n, 0]]); 36 | }); 37 | 38 | test("shift n multiplier", () => { 39 | const [result] = parse(" lsl.w #4,d0"); 40 | const n = 4; 41 | expect(result.timing.values).toEqual([[6 + 2 * n, 1, 0]]); 42 | }); 43 | 44 | test("shift n multiplier unresolved", () => { 45 | const [result] = parse(" lsl.w #unresolved,d0"); 46 | const minN = 1; 47 | const maxN = 8; 48 | expect(result.timing.values).toEqual([ 49 | [6 + 2 * minN, 1, 0], 50 | [6 + 2 * maxN, 1, 0], 51 | ]); 52 | }); 53 | 54 | test("shift n multiplier - single", () => { 55 | const [result] = parse(" lsl.w d0"); 56 | const n = 1; 57 | expect(result.timing.values).toEqual([[6 + 2 * n, 1, 0]]); 58 | }); 59 | 60 | test("shift n multiplier - register", () => { 61 | const [result] = parse(" lsl.w d0,d1"); 62 | const minN = 0; 63 | const maxN = 63; 64 | expect(result.timing.values).toEqual([ 65 | [6 + 2 * minN, 1, 0], 66 | [6 + 2 * maxN, 1, 0], 67 | ]); 68 | }); 69 | 70 | test("btst register -> immediate", () => { 71 | const [result] = parse(" btst.b d0,#1"); 72 | expect(result.timing.values).toEqual([[10, 2, 0]]); 73 | }); 74 | 75 | test("mulu best case", () => { 76 | const [result] = parse(" mulu #0,d0"); 77 | expect(result.timing.values).toEqual([[42, 2, 0]]); 78 | }); 79 | 80 | test("mulu worst case", () => { 81 | const [result] = parse(" mulu #$ffff,d0"); 82 | expect(result.timing.values).toEqual([[74, 2, 0]]); 83 | }); 84 | 85 | test("muls best case", () => { 86 | const [result] = parse(" muls #0,d0"); 87 | expect(result.timing.values).toEqual([[42, 2, 0]]); 88 | }); 89 | 90 | test("muls worst case", () => { 91 | const [result] = parse(" muls #$5555,d0"); 92 | expect(result.timing.values).toEqual([[74, 2, 0]]); 93 | }); 94 | 95 | test("muls range", () => { 96 | const [result] = parse(" muls d0,d1"); 97 | expect(result.timing.values).toEqual([ 98 | [38, 1, 0], 99 | [70, 1, 0], 100 | ]); 101 | }); 102 | 103 | test("nop", () => { 104 | const [result] = parse(" nop"); 105 | expect(result.timing.values).toEqual([[4, 1, 0]]); 106 | }); 107 | 108 | test("movem - single data src", () => { 109 | const [result] = parse(" movem.w d0,(a0)"); 110 | expect(result.timing.values).toEqual([[12, 2, 1]]); 111 | }); 112 | 113 | test("movem - single address src", () => { 114 | const [result] = parse(" movem.w a0,(a0)"); 115 | expect(result.timing.values).toEqual([[12, 2, 1]]); 116 | }); 117 | 118 | test("movem - single data dest", () => { 119 | const [result] = parse(" movem.w (a0),d0"); 120 | expect(result.timing.values).toEqual([[16, 4, 0]]); 121 | }); 122 | 123 | test("movem - single address dest", () => { 124 | const [result] = parse(" movem.w (a0),a0"); 125 | expect(result.timing.values).toEqual([[16, 4, 0]]); 126 | }); 127 | }); 128 | 129 | describe("formatTiming", () => { 130 | test("formats a timing", () => { 131 | const result = formatTiming([4, 2, 1]); 132 | expect(result).toBe("4(2/1)"); 133 | }); 134 | }); 135 | 136 | describe("rangeCount", () => { 137 | test("single range", () => { 138 | expect(rangeN("d0-d4")).toEqual(5); 139 | }); 140 | 141 | test("multiple regs", () => { 142 | expect(rangeN("d0/d4/d6")).toEqual(3); 143 | }); 144 | 145 | test("two ranges", () => { 146 | expect(rangeN("d0-d4/a0-a2")).toEqual(8); 147 | }); 148 | 149 | test("mixed", () => { 150 | expect(rangeN("d0-d4/d6/a0-a2/a4")).toEqual(10); 151 | }); 152 | 153 | test("combined range", () => { 154 | expect(rangeN("d0-a6")).toEqual(15); 155 | }); 156 | 157 | test("popCount", () => { 158 | expect(popCount(0)).toEqual(0); 159 | expect(popCount(0b1101)).toEqual(3); 160 | expect(popCount(0x5555)).toEqual(8); 161 | }); 162 | }); 163 | 164 | describe("timingLabels", () => { 165 | test("BCC", () => { 166 | const result = timingLabels(Mnemonics.BLT); 167 | expect(result).toEqual(["Taken", "Not taken"]); 168 | }); 169 | 170 | test("DBCC", () => { 171 | const result = timingLabels(Mnemonics.DBCS); 172 | expect(result).toEqual(["Taken", "Not taken", "Expired"]); 173 | }); 174 | 175 | test("CHK", () => { 176 | const result = timingLabels(Mnemonics.CHK); 177 | expect(result).toEqual(["No trap", "Trap >", "Trap <"]); 178 | }); 179 | 180 | test("TRAPV", () => { 181 | const result = timingLabels(Mnemonics.TRAPV); 182 | expect(result).toEqual(["No trap", "Trap"]); 183 | }); 184 | 185 | test("Default", () => { 186 | const result = timingLabels(Mnemonics.MULS); 187 | expect(result).toEqual(["Min", "Max"]); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/parse/operandMode.test.ts: -------------------------------------------------------------------------------- 1 | import operandMode from "../../src/parse/operandMode"; 2 | import { AddressingModes } from "../../src/syntax"; 3 | 4 | describe("operandMode", () => { 5 | test("direct data", () => { 6 | expect(operandMode("d0")).toEqual(AddressingModes.Dn); 7 | expect(operandMode("d7")).toEqual(AddressingModes.Dn); 8 | }); 9 | 10 | test("direct address", () => { 11 | expect(operandMode("a0")).toEqual(AddressingModes.An); 12 | expect(operandMode("a7")).toEqual(AddressingModes.An); 13 | expect(operandMode("sp")).toEqual(AddressingModes.An); 14 | }); 15 | 16 | test("indirect", () => { 17 | expect(operandMode("(a0)")).toEqual(AddressingModes.AnIndir); 18 | expect(operandMode("( a0 )")).toEqual(AddressingModes.AnIndir); 19 | expect(operandMode("(a7)")).toEqual(AddressingModes.AnIndir); 20 | expect(operandMode("(sp)")).toEqual(AddressingModes.AnIndir); 21 | }); 22 | 23 | test("indirect post increment", () => { 24 | expect(operandMode("(a0)+")).toEqual(AddressingModes.AnPostInc); 25 | expect(operandMode("( a0 )+")).toEqual(AddressingModes.AnPostInc); 26 | expect(operandMode("(a7)+")).toEqual(AddressingModes.AnPostInc); 27 | expect(operandMode("(sp)+")).toEqual(AddressingModes.AnPostInc); 28 | }); 29 | 30 | test("indirect pre decrement", () => { 31 | expect(operandMode("-(a0)")).toEqual(AddressingModes.AnPreDec); 32 | expect(operandMode("-( a0 )")).toEqual(AddressingModes.AnPreDec); 33 | expect(operandMode("-(a7)")).toEqual(AddressingModes.AnPreDec); 34 | expect(operandMode("-(sp)")).toEqual(AddressingModes.AnPreDec); 35 | }); 36 | 37 | test("indirect displacement", () => { 38 | expect(operandMode("1(a0)")).toEqual(AddressingModes.AnDisp); 39 | expect(operandMode("example(a0)")).toEqual(AddressingModes.AnDisp); 40 | }); 41 | 42 | test("indirect displacement - old", () => { 43 | expect(operandMode("(1,a0)")).toEqual(AddressingModes.AnDisp); 44 | expect(operandMode("(-1,a0)")).toEqual(AddressingModes.AnDisp); 45 | expect(operandMode("( 1,a0 )")).toEqual(AddressingModes.AnDisp); 46 | }); 47 | 48 | test("indirect displacement with index", () => { 49 | // Dn index 50 | expect(operandMode("1(a0,d0)")).toEqual(AddressingModes.AnDispIx); 51 | expect(operandMode("1(a0,d0.w)")).toEqual(AddressingModes.AnDispIx); 52 | expect(operandMode("1(a0,d0.l)")).toEqual(AddressingModes.AnDispIx); 53 | // An index 54 | expect(operandMode("1(a0,a0)")).toEqual(AddressingModes.AnDispIx); 55 | expect(operandMode("1(a0,a0.w)")).toEqual(AddressingModes.AnDispIx); 56 | expect(operandMode("1(a0,a0.l)")).toEqual(AddressingModes.AnDispIx); 57 | 58 | // sp 59 | expect(operandMode("1(sp,sp)")).toEqual(AddressingModes.AnDispIx); 60 | expect(operandMode("1(sp,sp.w)")).toEqual(AddressingModes.AnDispIx); 61 | expect(operandMode("1(sp,sp.l)")).toEqual(AddressingModes.AnDispIx); 62 | 63 | // old style 64 | expect(operandMode("(1,a0,d0)")).toEqual(AddressingModes.AnDispIx); 65 | expect(operandMode("(1,a0,d0.w)")).toEqual(AddressingModes.AnDispIx); 66 | expect(operandMode("(1,a0,a0)")).toEqual(AddressingModes.AnDispIx); 67 | expect(operandMode("(1,a0,a0.w)")).toEqual(AddressingModes.AnDispIx); 68 | expect(operandMode("(1,sp,sp)")).toEqual(AddressingModes.AnDispIx); 69 | expect(operandMode("(1,sp,sp.w)")).toEqual(AddressingModes.AnDispIx); 70 | 71 | // no displacement 72 | expect(operandMode("(a0,d0)")).toEqual(AddressingModes.AnDispIx); 73 | expect(operandMode("(a0,d0.w)")).toEqual(AddressingModes.AnDispIx); 74 | expect(operandMode("(a0,a0.w)")).toEqual(AddressingModes.AnDispIx); 75 | expect(operandMode("(sp,sp)")).toEqual(AddressingModes.AnDispIx); 76 | expect(operandMode("(sp,sp.w)")).toEqual(AddressingModes.AnDispIx); 77 | 78 | // whitespace 79 | expect(operandMode("1( a0,d0 )")).toEqual(AddressingModes.AnDispIx); 80 | expect(operandMode("( 1,a0,d0 )")).toEqual(AddressingModes.AnDispIx); 81 | expect(operandMode("( a2,a5.w)")).toEqual(AddressingModes.AnDispIx); 82 | }); 83 | 84 | test("indirect displacement", () => { 85 | expect(operandMode("1(pc)")).toEqual(AddressingModes.PcDisp); 86 | expect(operandMode("(1,pc)")).toEqual(AddressingModes.PcDisp); 87 | expect(operandMode("(-1,pc)")).toEqual(AddressingModes.PcDisp); 88 | expect(operandMode("( 1,pc )")).toEqual(AddressingModes.PcDisp); 89 | }); 90 | 91 | test("indirect displacement with index PC", () => { 92 | expect(operandMode("1(pc,d0)")).toEqual(AddressingModes.PcDispIx); 93 | expect(operandMode("1( pc,d0 )")).toEqual(AddressingModes.PcDispIx); 94 | expect(operandMode("( 1,pc,d0 )")).toEqual(AddressingModes.PcDispIx); 95 | }); 96 | 97 | test("immediate", () => { 98 | expect(operandMode("#123")).toEqual(AddressingModes.Imm); 99 | expect(operandMode("#$12a")).toEqual(AddressingModes.Imm); 100 | expect(operandMode("#%01010")).toEqual(AddressingModes.Imm); 101 | }); 102 | 103 | test("immediate symbol", () => { 104 | expect(operandMode("#foo")).toEqual(AddressingModes.Imm); 105 | }); 106 | 107 | test("register list (movem)", () => { 108 | expect(operandMode("a0-a4")).toEqual(AddressingModes.RegList); 109 | expect(operandMode("a0/d0")).toEqual(AddressingModes.RegList); 110 | expect(operandMode("a0-a4/a6/d0-d3/d5/d7")).toEqual( 111 | AddressingModes.RegList 112 | ); 113 | }); 114 | 115 | test("absolute word", () => { 116 | expect(operandMode("foo.w")).toEqual(AddressingModes.AbsW); 117 | expect(operandMode("$12a.w")).toEqual(AddressingModes.AbsW); 118 | }); 119 | 120 | test("absolute long", () => { 121 | expect(operandMode("foo.l")).toEqual(AddressingModes.AbsL); 122 | expect(operandMode("$12a.l")).toEqual(AddressingModes.AbsL); 123 | }); 124 | 125 | test("absolute long default", () => { 126 | expect(operandMode("foo")).toEqual(AddressingModes.AbsL); 127 | expect(operandMode("$12a")).toEqual(AddressingModes.AbsL); 128 | }); 129 | 130 | test("case insensitive", () => { 131 | expect(operandMode("D0")).toEqual(AddressingModes.Dn); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /src/parse/nodes.ts: -------------------------------------------------------------------------------- 1 | import json5 from "json5"; 2 | import { 3 | AddressingMode, 4 | aliases, 5 | Directive, 6 | isDirective, 7 | isMnemonic, 8 | isQualifier, 9 | Mnemonic, 10 | Qualifier, 11 | Qualifiers, 12 | } from "../syntax"; 13 | import lookupAddressingMode from "./operandMode"; 14 | import tokenize from "./tokenize"; 15 | 16 | export abstract class Node { 17 | type: string; 18 | loc: Location; 19 | text: string; 20 | 21 | constructor(start: number, text: string, type = "") { 22 | this.type = type; 23 | this.text = text; 24 | this.loc = { 25 | start, 26 | end: start + text.length, 27 | }; 28 | } 29 | } 30 | 31 | export interface Location { 32 | start: number; 33 | end: number; 34 | } 35 | 36 | export class LabelNode extends Node { 37 | name: string; 38 | local: boolean; 39 | macro: boolean; 40 | 41 | constructor(start: number, text: string) { 42 | super(start, text, "Label"); 43 | this.name = text; 44 | this.local = text.indexOf(".") === 0; 45 | this.macro = text.includes("@"); 46 | } 47 | } 48 | 49 | export class OpcodeNode extends Node { 50 | op: MnemonicNode | DirectiveNode | MacroNode; 51 | qualifier?: QualifierNode; 52 | 53 | constructor(start: number, text: string) { 54 | super(start, text, "Opcode"); 55 | 56 | let [op, qualifier] = text.toUpperCase().split("."); 57 | if (aliases[op]) op = aliases[op]; 58 | 59 | if (isMnemonic(op)) { 60 | this.op = new MnemonicNode(start, op); 61 | if (qualifier === Qualifiers.S) { 62 | qualifier = Qualifiers.B; 63 | } 64 | } else if (isDirective(op)) { 65 | this.op = new DirectiveNode(start, op); 66 | } else { 67 | this.op = new MacroNode(start, op); 68 | } 69 | 70 | if (isQualifier(qualifier)) { 71 | const sepPos = text.indexOf("."); 72 | this.qualifier = new QualifierNode(start + sepPos + 1, qualifier); 73 | } 74 | } 75 | } 76 | 77 | export class MnemonicNode extends Node { 78 | name: Mnemonic; 79 | constructor(start: number, name: Mnemonic) { 80 | super(start, name, "Mnemonic"); 81 | this.name = name; 82 | } 83 | } 84 | 85 | export class DirectiveNode extends Node { 86 | name: Directive; 87 | constructor(start: number, name: Directive) { 88 | super(start, name, "Directive"); 89 | this.name = name; 90 | } 91 | } 92 | 93 | export class MacroNode extends Node { 94 | name: string; 95 | constructor(start: number, name: string) { 96 | super(start, name, "Macro"); 97 | this.name = name; 98 | } 99 | } 100 | 101 | export class QualifierNode extends Node { 102 | name: Qualifier; 103 | constructor(start: number, name: Qualifier) { 104 | super(start, name, "Qualifier"); 105 | this.name = name; 106 | } 107 | } 108 | 109 | export class EffectiveAddressNode extends Node { 110 | mode: AddressingMode; 111 | constructor(start: number, text: string) { 112 | super(start, text, "EffectiveAddress"); 113 | this.mode = lookupAddressingMode(text); 114 | } 115 | } 116 | 117 | export class StringNode extends Node { 118 | value: string; 119 | constructor(start: number, text: string) { 120 | super(start, text, "String"); 121 | try { 122 | this.value = json5.parse(text); 123 | } catch (_) { 124 | this.value = ""; 125 | console.error("Unable to parse text", { text }); 126 | } 127 | } 128 | } 129 | 130 | export class MacroArgNode extends Node { 131 | index: number; 132 | constructor(start: number, text: string) { 133 | super(start, text, "MacroArg"); 134 | this.index = parseInt(text.substring(1), 10); 135 | } 136 | } 137 | 138 | export class MacroInvocationsNode extends Node { 139 | constructor(start: number, text: string) { 140 | super(start, text, "MacroInvocations"); 141 | } 142 | } 143 | 144 | export class CommentNode extends Node { 145 | constructor(start: number, text: string) { 146 | super(start, text, "Comment"); 147 | } 148 | } 149 | 150 | export class StatementNode extends Node { 151 | label?: LabelNode; 152 | opcode?: OpcodeNode; 153 | operands: Node[]; 154 | comment?: CommentNode; 155 | 156 | constructor(text: string) { 157 | super(0, text); 158 | this.operands = []; 159 | const tokens = tokenize(text); 160 | 161 | for (const i in tokens) { 162 | const [start, text] = tokens[i]; 163 | if (text[0] === ";" || (text[0] === "*" && text[1] !== "+")) { 164 | this.comment = new CommentNode(start, text); 165 | } else if (start === 0) { 166 | this.label = new LabelNode(start, text); 167 | } else if (!this.opcode) { 168 | this.opcode = new OpcodeNode(start, text); 169 | } else { 170 | // Operands 171 | if (["'", '"'].includes(text[0])) { 172 | this.operands.push(new StringNode(start, text)); 173 | } else if (text === "\\@") { 174 | this.operands.push(new MacroInvocationsNode(start, text)); 175 | } else if (text[0] === "\\") { 176 | this.operands.push(new MacroArgNode(start, text)); 177 | } else { 178 | this.operands.push(new EffectiveAddressNode(start, text)); 179 | } 180 | } 181 | } 182 | } 183 | 184 | isLabel(): this is LabelStatement { 185 | return !this.opcode && this.label !== undefined; 186 | } 187 | 188 | isInstruction(): this is InstructionStatement { 189 | return this.opcode !== undefined && this.opcode.op instanceof MnemonicNode; 190 | } 191 | 192 | isDirective(): this is DirectiveStatement { 193 | return this.opcode !== undefined && this.opcode.op instanceof DirectiveNode; 194 | } 195 | 196 | isMacro(): this is MacroStatement { 197 | return this.opcode !== undefined && this.opcode.op instanceof MacroNode; 198 | } 199 | } 200 | 201 | export interface InstructionStatement { 202 | opcode: { 203 | op: MnemonicNode; 204 | qualifier?: QualifierNode; 205 | }; 206 | operands: EffectiveAddressNode[]; 207 | } 208 | 209 | export interface DirectiveStatement { 210 | opcode: { 211 | op: DirectiveNode; 212 | qualifier?: QualifierNode; 213 | }; 214 | operands: Node[]; 215 | } 216 | 217 | export interface MacroStatement { 218 | opcode: { 219 | op: MacroNode; 220 | }; 221 | operands: Node[]; 222 | } 223 | 224 | export interface LabelStatement { 225 | label: LabelNode; 226 | } 227 | -------------------------------------------------------------------------------- /src/parse/Parser.ts: -------------------------------------------------------------------------------- 1 | import { InstructionTiming, instructionTimings } from "../timings"; 2 | import { Directives, Directive } from "../syntax"; 3 | import { 4 | DirectiveStatement, 5 | InstructionStatement, 6 | LabelStatement, 7 | MacroStatement, 8 | StatementNode, 9 | } from "./nodes"; 10 | import evaluate, { Variables } from "./evaluate"; 11 | import statementSize from "../sizes"; 12 | import { calculateTotals } from "../totals"; 13 | 14 | export interface Line { 15 | statement: StatementNode; 16 | macroLines?: Line[]; 17 | bytes?: number; 18 | bss?: boolean; 19 | timing?: InstructionTiming; 20 | } 21 | 22 | export default class Parser { 23 | /** Variable/constants state */ 24 | private vars: Variables = {}; 25 | 26 | /** Macro definitions */ 27 | private macros: Record = {}; 28 | 29 | /** Running total of parsed statement bytes sizes */ 30 | private totalBytes = 0; 31 | 32 | /** The name of the macro currently being defined */ 33 | private currentMacro: string | null = null; 34 | 35 | /** Start of the current repeating group */ 36 | private reptStart: Line | null = null; 37 | 38 | /** Statements in the current repeating group */ 39 | private reptStatements: StatementNode[] = []; 40 | 41 | /** Are we currently in a BSS section */ 42 | private bss = false; 43 | 44 | // Directive groups: 45 | 46 | private assignments: Directive[] = [ 47 | Directives["="], 48 | Directives.EQU, 49 | Directives.FEQU, 50 | Directives.SET, 51 | ]; 52 | 53 | private sections: Directive[] = [ 54 | Directives.SECTION, 55 | Directives.BSS, 56 | Directives.BSS_C, 57 | Directives.BSS_F, 58 | Directives.DATA, 59 | Directives.DATA_C, 60 | Directives.DATA_F, 61 | Directives.CODE, 62 | Directives.CODE_C, 63 | Directives.CODE_F, 64 | ]; 65 | 66 | /** 67 | * Parse multiple lines of ASM code 68 | */ 69 | parse(input: string): Line[] { 70 | // Split input into lines 71 | const inputLines = input 72 | .replace(/\r\n/g, "\n") 73 | .replace(/\r/g, "\n") 74 | .split("\n"); 75 | 76 | // Parse individual statements: 77 | const statements = inputLines.map((l) => new StatementNode(l)); 78 | 79 | // Now do processing to add size/timing info and expand macros: 80 | 81 | // Reset state 82 | this.vars = {}; 83 | this.macros = {}; 84 | 85 | // Needs two passes to catch all references. 86 | let lines: Line[] = []; 87 | for (let i = 0; i < 2; i++) { 88 | lines = this.processStatements(statements); 89 | } 90 | 91 | return lines; 92 | } 93 | 94 | /** 95 | * Add size and timing info to statements 96 | */ 97 | private processStatements(statements: StatementNode[]): Line[] { 98 | this.totalBytes = 0; 99 | this.currentMacro = null; 100 | this.bss = false; 101 | this.reptStart = null; 102 | this.reptStatements = []; 103 | 104 | return statements.map(this.processStatement.bind(this)); 105 | } 106 | 107 | private processStatement(statement: StatementNode): Line { 108 | let line: Line = { statement }; 109 | 110 | // Currently defining a macro - store statements against this name rather than processing now 111 | if (this.currentMacro) { 112 | if (statement.opcode?.op.name === Directives.ENDM) { 113 | // End macro 114 | this.currentMacro = null; 115 | } else { 116 | // Add statement to macro 117 | this.macros[this.currentMacro].push(statement); 118 | } 119 | return line; 120 | } 121 | 122 | // Inside repeating section: 123 | if (this.reptStart) { 124 | if (statement.opcode?.op.name === Directives.ENDR) { 125 | // End of repeat 126 | line.macroLines = []; 127 | // Expand and process repeated statements 128 | const countOp = this.reptStart.statement.operands[0]?.text; 129 | const reptCount = evaluate(countOp) || 0; 130 | const statements = this.reptStatements; 131 | 132 | this.reptStart = null; 133 | this.reptStatements = []; 134 | 135 | // TODO: support REPTN 136 | for (let i = 0; i < reptCount; i++) { 137 | line.macroLines = [ 138 | ...line.macroLines, 139 | ...this.processStatements(statements), 140 | ]; 141 | } 142 | } else { 143 | // Add statement to repeated list 144 | this.reptStatements.push(statement); 145 | } 146 | } 147 | 148 | // Process types: 149 | else if (statement.isLabel()) { 150 | line = this.processLabel(statement); 151 | } else if (statement.isMacro()) { 152 | line = this.processMacro(statement); 153 | } else if (statement.isDirective()) { 154 | line = this.processDirective(statement); 155 | } else if (statement.isInstruction()) { 156 | line = this.processInstruction(statement); 157 | } 158 | 159 | // Calculate total of lines added by macro/rept: 160 | if (line.macroLines) { 161 | const macroTotals = calculateTotals(line.macroLines); 162 | line.bytes = macroTotals.bytes; 163 | if (macroTotals.min[0]) { 164 | line.timing = macroTotals.isRange 165 | ? { 166 | values: [macroTotals.min, macroTotals.max], 167 | labels: ["Min", "Max"], 168 | } 169 | : { 170 | values: [macroTotals.min], 171 | labels: [], 172 | }; 173 | } 174 | } 175 | 176 | // Calculate byte size of this statement: 177 | else if (!line.bytes) { 178 | line.bytes = statementSize(statement, this.vars); 179 | line.bss = this.bss; 180 | } 181 | this.totalBytes += line.bytes; 182 | 183 | return line; 184 | } 185 | 186 | private processLabel(statement: StatementNode & LabelStatement) { 187 | // Assign running total of bytes to labels names 188 | // This allows expressions to get byte count from ranges e.g. `dcb.b END-START` 189 | this.vars[statement.label.text] = this.totalBytes; 190 | return { statement, bytes: 0, bss: this.bss }; 191 | } 192 | 193 | private processMacro(statement: StatementNode & MacroStatement) { 194 | const line: Line = { statement }; 195 | const macroName = statement.opcode.op.text; 196 | if (this.macros[macroName]) { 197 | const macroStatements = this.macros[macroName].map( 198 | ({ text }): StatementNode => { 199 | for (let i = 1; i <= statement.operands.length; i++) { 200 | const placeholder = "\\" + i; 201 | text = text.replace(placeholder, statement.operands[i - 1].text); 202 | } 203 | return new StatementNode(text); 204 | } 205 | ); 206 | line.macroLines = this.processStatements(macroStatements); 207 | } 208 | return line; 209 | } 210 | 211 | private processDirective(statement: StatementNode & DirectiveStatement) { 212 | const line: Line = { statement }; 213 | 214 | // Variable Assignment: 215 | if ( 216 | statement.label && 217 | this.assignments.includes(statement.opcode.op.name) && 218 | statement.operands[0] 219 | ) { 220 | const value = evaluate(statement.operands[0].text, this.vars); 221 | if (value) { 222 | this.vars[statement.label.text] = value; 223 | } 224 | } 225 | 226 | // Macro definition: 227 | else if (statement.opcode.op.name === Directives.MACRO) { 228 | const macroName = statement.label 229 | ? statement.label.text 230 | : statement.operands[0]?.text; 231 | if (macroName) { 232 | this.currentMacro = macroName.toUpperCase(); 233 | this.macros[this.currentMacro] = []; 234 | } 235 | } 236 | 237 | // Rept start: 238 | else if (statement.opcode.op.name === Directives.REPT) { 239 | this.reptStart = line; 240 | } 241 | 242 | // Section: 243 | else if (this.sections.includes(statement.opcode.op.name)) { 244 | // Is this a BSS section? 245 | this.bss = 246 | statement.opcode.op.name.includes("BSS") || 247 | statement.operands.some((o) => o.text.match(/^bss/i)); 248 | } 249 | return line; 250 | } 251 | 252 | private processInstruction(statement: StatementNode & InstructionStatement) { 253 | const line: Line = { 254 | statement, 255 | bss: this.bss, 256 | timing: instructionTimings(statement, this.vars) || undefined, 257 | }; 258 | return line; 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /src/timings/index.ts: -------------------------------------------------------------------------------- 1 | import { baseTimes, lookupTimes } from "./tables"; 2 | import { 3 | Qualifiers, 4 | AddressingMode, 5 | AddressingModes, 6 | Mnemonics, 7 | mnemonicGroups, 8 | Mnemonic, 9 | } from "../syntax"; 10 | import instructionQualifier from "../parse/instructionQualifier"; 11 | import { EffectiveAddressNode, InstructionStatement } from "../parse/nodes"; 12 | import evaluate, { Variables } from "../parse/evaluate"; 13 | 14 | /** 15 | * Timing vector: 16 | * cycles, reads, writes 17 | */ 18 | export type Timing = [number, number, number]; 19 | 20 | /** 21 | * Result of timing lookup for an instruction 22 | */ 23 | export interface InstructionTiming { 24 | values: Timing[]; 25 | labels: string[]; 26 | calculation?: Calculation; 27 | } 28 | 29 | /** 30 | * Describes how the timings are calculated 31 | */ 32 | export interface Calculation { 33 | base: Timing[]; 34 | ea?: Timing; 35 | multiplier?: Timing; 36 | /** Known number or range */ 37 | n?: number | [number, number]; 38 | } 39 | 40 | export function popCount(x: number): number { 41 | x -= (x >> 1) & 0x55555555; 42 | x = (x & 0x33333333) + ((x >> 2) & 0x33333333); 43 | x = (x + (x >> 4)) & 0x0f0f0f0f; 44 | x += x >> 8; 45 | x += x >> 16; 46 | return x & 0x7f; 47 | } 48 | 49 | /** 50 | * Look up timing information for a parsed instruction statement 51 | */ 52 | export function instructionTimings( 53 | statement: InstructionStatement, 54 | vars: Variables 55 | ): InstructionTiming | null { 56 | const key = buildKey(statement); 57 | if (!key || !timingMap.has(key)) { 58 | return null; 59 | } 60 | const calculation = { ...(timingMap.get(key) as Calculation) }; 61 | const timings: Timing[] = [...calculation.base]; 62 | 63 | const { 64 | opcode: { op }, 65 | operands, 66 | } = statement; 67 | const source = operands[0]; 68 | 69 | // Calculate n multiplier: 70 | if (calculation.multiplier) { 71 | // Shift 72 | if (mnemonicGroups.SHIFT.includes(op.name)) { 73 | if (source.mode === AddressingModes.Imm) { 74 | calculation.n = evaluate(source.text, vars) || [1, 8]; 75 | } else { 76 | // Range for register 77 | calculation.n = [0, 63]; 78 | } 79 | } 80 | // MULU 81 | else if (op.name === Mnemonics.MULU) { 82 | // n = the number of ones in the 83 | const value = evaluate(source.text, vars); 84 | if (source.mode === AddressingModes.Imm && value !== undefined) { 85 | calculation.n = popCount(value); 86 | } else { 87 | calculation.n = [0, 16]; 88 | } 89 | } 90 | // MULS 91 | else if (op.name === Mnemonics.MULS) { 92 | // n = concatenate the with a zero as the LSB; 93 | // n is the resultant number of 10 or 01 patterns in the 17-bit source; 94 | // i.e. worst case happens when the source is $5555 95 | const value = evaluate(source.text, vars); 96 | if (source.mode === AddressingModes.Imm && value !== undefined) { 97 | calculation.n = popCount((value ^ (value << 1)) & 0xffff); 98 | } else { 99 | calculation.n = [0, 16]; 100 | } 101 | } 102 | // MOVEM 103 | else if (op.name === Mnemonics.MOVEM) { 104 | const listOperand = operands.find( 105 | (o) => o.mode === AddressingModes.RegList 106 | ); 107 | if (listOperand) { 108 | calculation.n = listOperand && rangeN(listOperand.text); 109 | } else { 110 | calculation.n = 1; 111 | } 112 | } 113 | 114 | // Apply multiplier: 115 | if (calculation.n) { 116 | // Range 117 | if (Array.isArray(calculation.n)) { 118 | for (const i in calculation.n) { 119 | const m = multiplyTiming(calculation.multiplier, calculation.n[i]); 120 | timings[i] = addTimings(calculation.base[0], m); 121 | } 122 | } 123 | // Single value 124 | else { 125 | const m = multiplyTiming(calculation.multiplier, calculation.n); 126 | for (const i in timings) { 127 | timings[i] = addTimings(timings[i], m); 128 | } 129 | } 130 | } 131 | } 132 | 133 | // Add effective address lookup 134 | if (calculation.ea) { 135 | for (const i in timings) { 136 | timings[i] = addTimings(timings[i], calculation.ea); 137 | } 138 | } 139 | 140 | // Add labels for multiple values 141 | const labels = timings.length > 1 ? timingLabels(op.name) : []; 142 | 143 | return { values: timings, labels, calculation }; 144 | } 145 | 146 | /** 147 | * Convert timing to string per the 68000 documentation 148 | * 149 | * clock(read/write) 150 | */ 151 | export const formatTiming = (timing: Timing): string => 152 | `${timing[0]}(${timing[1]}/${timing[2]})`; 153 | 154 | /** 155 | * Add two timing vectors 156 | */ 157 | export function addTimings(a: Timing, b: Timing): Timing { 158 | return [a[0] + b[0], a[1] + b[1], a[2] + b[2]]; 159 | } 160 | 161 | /** 162 | * Multiply a timing vector by a scalar value 163 | */ 164 | export function multiplyTiming(t: Timing, scalar: number): Timing { 165 | return [t[0] * scalar, t[1] * scalar, t[2] * scalar]; 166 | } 167 | 168 | /** 169 | * Calculate timing n value from register range used in MOVEM 170 | */ 171 | export function rangeN(range: string): number { 172 | return range.split("/").reduce((acc, v) => { 173 | const [from, to] = v.split("-").map((n) => { 174 | const t = n[0].toUpperCase(); 175 | return parseInt(n.substr(1), 10) + (t === "A" ? 8 : 0); 176 | }); 177 | return acc + (to ? to - from + 1 : 1); 178 | }, 0); 179 | } 180 | 181 | /** 182 | * Get text labels for multiple timings 183 | */ 184 | export function timingLabels(op: Mnemonic): string[] { 185 | if ([...mnemonicGroups.SCC, ...mnemonicGroups.BCC].includes(op)) { 186 | return ["Taken", "Not taken"]; 187 | } 188 | if (mnemonicGroups.DBCC.includes(op)) { 189 | return ["Taken", "Not taken", "Expired"]; 190 | } 191 | if (op === Mnemonics.CHK) { 192 | return ["No trap", "Trap >", "Trap <"]; 193 | } 194 | if (op === Mnemonics.TRAPV) { 195 | return ["No trap", "Trap"]; 196 | } 197 | // Default 198 | return ["Min", "Max"]; 199 | } 200 | 201 | /** 202 | * Build string key for map lookup 203 | */ 204 | function buildKey(statement: InstructionStatement): string | null { 205 | const { opcode, operands } = statement; 206 | if (!opcode) { 207 | return null; 208 | } 209 | let key = opcode.op.name; 210 | const qualifier = instructionQualifier(statement); 211 | if (qualifier) { 212 | key += "." + qualifier; 213 | } 214 | if (operands.length) { 215 | key += 216 | " " + (operands as EffectiveAddressNode[]).map((o) => o.mode).join(","); 217 | } 218 | return key; 219 | } 220 | 221 | // Flatten table into key/value for simple lookup by instruction string 222 | // e.g. 223 | // "MOVE.L Dn,Dn": [4, 1, 0] 224 | 225 | const timingMap = new Map(); 226 | 227 | for (const row of baseTimes) { 228 | const [mnemonics, qualifiers, operands, base, multiplier] = row; 229 | for (const mnemonic of mnemonics) { 230 | for (const qualifier of qualifiers) { 231 | let key = String(mnemonic); 232 | if (qualifier) { 233 | key += "." + qualifier; 234 | } 235 | const eaSize = qualifier === Qualifiers.L ? 1 : 0; 236 | let o: AddressingMode; 237 | 238 | if (Array.isArray(operands[0])) { 239 | // EA lookup in source 240 | for (o of operands[0]) { 241 | let k = key + " " + o; 242 | if (operands[1]) { 243 | k += "," + operands[1]; 244 | } 245 | let ea: Timing | undefined; 246 | if (lookupTimes[o]) { 247 | ea = lookupTimes[o][eaSize]; 248 | // Special cases: 249 | if (mnemonic === Mnemonics.TAS && o === AddressingModes.AbsW) { 250 | ea = [8, 1, 0]; 251 | } 252 | if ( 253 | (mnemonic === Mnemonics.TAS || 254 | mnemonic === Mnemonics.CHK || 255 | mnemonic === Mnemonics.MULS || 256 | mnemonic === Mnemonics.MULU) && 257 | o === AddressingModes.AbsL 258 | ) { 259 | ea = [12, 2, 0]; 260 | } 261 | } 262 | timingMap.set(k, { base, ea, multiplier }); 263 | } 264 | } else if (Array.isArray(operands[1])) { 265 | // EA lookup in dest 266 | for (o of operands[1]) { 267 | const k = key + " " + operands[0] + "," + o; 268 | let ea: Timing | undefined; 269 | if (lookupTimes[o]) { 270 | ea = lookupTimes[o][eaSize]; 271 | } 272 | timingMap.set(k, { base, ea, multiplier }); 273 | } 274 | } else { 275 | // Regular operands 276 | if (operands.length) { 277 | key += " " + operands.join(","); 278 | } 279 | timingMap.set(key, { base: base, multiplier }); 280 | } 281 | } 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/syntax.ts: -------------------------------------------------------------------------------- 1 | type Values = T[keyof T]; 2 | 3 | export const Mnemonics = { 4 | ABCD: "ABCD", 5 | ADD: "ADD", 6 | ADDA: "ADDA", 7 | ADDI: "ADDI", 8 | ADDQ: "ADDQ", 9 | ADDX: "ADDX", 10 | AND: "AND", 11 | ANDI: "ANDI", 12 | ASL: "ASL", 13 | ASR: "ASR", 14 | BCC: "BCC", 15 | BCHG: "BCHG", 16 | BCLR: "BCLR", 17 | BCS: "BCS", 18 | BEQ: "BEQ", 19 | BGE: "BGE", 20 | BGT: "BGT", 21 | BHI: "BHI", 22 | BLE: "BLE", 23 | BLT: "BLT", 24 | BMI: "BMI", 25 | BNE: "BNE", 26 | BPL: "BPL", 27 | BRA: "BRA", 28 | BSET: "BSET", 29 | BSR: "BSR", 30 | BTST: "BTST", 31 | BVC: "BVC", 32 | BVS: "BVS", 33 | BLS: "BLS", 34 | CHK: "CHK", 35 | CLR: "CLR", 36 | CMP: "CMP", 37 | CMPA: "CMPA", 38 | CMPI: "CMPI", 39 | CMPM: "CMPM", 40 | DBCC: "DBCC", 41 | DBCS: "DBCS", 42 | DBEQ: "DBEQ", 43 | DBF: "DBF", 44 | DBGE: "DBGE", 45 | DBGT: "DBGT", 46 | DBHI: "DBHI", 47 | DBLE: "DBLE", 48 | DBLT: "DBLT", 49 | DBMI: "DBMI", 50 | DBNE: "DBNE", 51 | DBPL: "DBPL", 52 | DBT: "DBT", 53 | DBVC: "DBVC", 54 | DBVS: "DBVS", 55 | DBLS: "DBLS", 56 | DIVS: "DIVS", 57 | DIVU: "DIVU", 58 | EOR: "EOR", 59 | EORI: "EORI", 60 | EXG: "EXG", 61 | EXT: "EXT", 62 | JMP: "JMP", 63 | JSR: "JSR", 64 | LEA: "LEA", 65 | LINK: "LINK", 66 | LSL: "LSL", 67 | LSR: "LSR", 68 | MOVE: "MOVE", 69 | MOVEA: "MOVEA", 70 | MOVEM: "MOVEM", 71 | MOVEP: "MOVEP", 72 | MOVEQ: "MOVEQ", 73 | MULS: "MULS", 74 | MULU: "MULU", 75 | NBCD: "NBCD", 76 | NEG: "NEG", 77 | NEGX: "NEGX", 78 | NOP: "NOP", 79 | NOT: "NOT", 80 | OR: "OR", 81 | ORI: "ORI", 82 | PEA: "PEA", 83 | RESET: "RESET", 84 | ROL: "ROL", 85 | ROR: "ROR", 86 | ROXL: "ROXL", 87 | ROXR: "ROXR", 88 | RTE: "RTE", 89 | RTR: "RTR", 90 | RTS: "RTS", 91 | SBCD: "SBCD", 92 | SCC: "SCC", 93 | SCS: "SCS", 94 | SEQ: "SEQ", 95 | SGE: "SGE", 96 | SGT: "SGT", 97 | SHI: "SHI", 98 | SLE: "SLE", 99 | SLT: "SLT", 100 | SMI: "SMI", 101 | SNE: "SNE", 102 | SPL: "SPL", 103 | SF: "SF", 104 | ST: "ST", 105 | SLS: "SLS", 106 | STOP: "STOP", 107 | SUB: "SUB", 108 | SUBA: "SUBA", 109 | SUBI: "SUBI", 110 | SUBQ: "SUBQ", 111 | SUBX: "SUBX", 112 | SVC: "SVC", 113 | SVS: "SVS", 114 | SWAP: "SWAP", 115 | TAS: "TAS", 116 | TRAP: "TRAP", 117 | TRAPV: "TRAPV", 118 | TST: "TST", 119 | UNLK: "UNLK", 120 | ILLEGAL: "ILLEGAL", 121 | } as const; 122 | 123 | export type Mnemonic = Values; 124 | 125 | export function isMnemonic(v: string): v is Mnemonic { 126 | return Mnemonics[v as Mnemonic] !== undefined; 127 | } 128 | 129 | export const Qualifiers = { 130 | B: "B", 131 | W: "W", 132 | L: "L", 133 | Q: "Q", 134 | S: "S", 135 | D: "D", 136 | X: "X", 137 | } as const; 138 | 139 | export type Qualifier = Values; 140 | 141 | export function isQualifier(v: string): v is Qualifier { 142 | return Qualifiers[v as Qualifier] !== undefined; 143 | } 144 | 145 | export const AddressingModes = { 146 | Dn: "Dn", 147 | An: "An", 148 | AnIndir: "(An)", 149 | AnPostInc: "(An)+", 150 | AnPreDec: "-(An)", 151 | AnDisp: "d(An)", 152 | AnDispIx: "d(An,ix)", 153 | PcDisp: "d(PC)", 154 | PcDispIx: "d(PC,ix)", 155 | AbsW: "xxx.W", 156 | AbsL: "xxx.L", 157 | RegList: "RegList", 158 | Imm: "#xxx", 159 | CCR: "ccr", 160 | SR: "sr", 161 | USP: "usp", 162 | } as const; 163 | 164 | export type AddressingMode = Values; 165 | 166 | // Groups 167 | 168 | export type MnemonicGroup = "BCC" | "DBCC" | "SCC" | "SHIFT"; 169 | 170 | export const mnemonicGroups: Record = { 171 | SCC: [ 172 | Mnemonics.SCC, 173 | Mnemonics.SCS, 174 | Mnemonics.SEQ, 175 | Mnemonics.SGE, 176 | Mnemonics.SGT, 177 | Mnemonics.SHI, 178 | Mnemonics.SLE, 179 | Mnemonics.SLT, 180 | Mnemonics.SMI, 181 | Mnemonics.SNE, 182 | Mnemonics.SPL, 183 | Mnemonics.SVC, 184 | Mnemonics.SVS, 185 | Mnemonics.ST, 186 | Mnemonics.SF, 187 | Mnemonics.SLS, 188 | ], 189 | BCC: [ 190 | Mnemonics.BCC, 191 | Mnemonics.BCS, 192 | Mnemonics.BEQ, 193 | Mnemonics.BGE, 194 | Mnemonics.BGT, 195 | Mnemonics.BHI, 196 | Mnemonics.BLE, 197 | Mnemonics.BLT, 198 | Mnemonics.BMI, 199 | Mnemonics.BNE, 200 | Mnemonics.BPL, 201 | Mnemonics.BVC, 202 | Mnemonics.BVS, 203 | Mnemonics.BLS, 204 | ], 205 | DBCC: [ 206 | Mnemonics.DBCC, 207 | Mnemonics.DBCS, 208 | Mnemonics.DBEQ, 209 | Mnemonics.DBF, 210 | Mnemonics.DBGE, 211 | Mnemonics.DBGT, 212 | Mnemonics.DBHI, 213 | Mnemonics.DBLE, 214 | Mnemonics.DBLT, 215 | Mnemonics.DBMI, 216 | Mnemonics.DBNE, 217 | Mnemonics.DBPL, 218 | Mnemonics.DBT, 219 | Mnemonics.DBVC, 220 | Mnemonics.DBVS, 221 | Mnemonics.DBLS, 222 | ], 223 | SHIFT: [ 224 | Mnemonics.LSL, 225 | Mnemonics.LSR, 226 | Mnemonics.ASL, 227 | Mnemonics.ASR, 228 | Mnemonics.ROL, 229 | Mnemonics.ROR, 230 | Mnemonics.ROXL, 231 | Mnemonics.ROXR, 232 | ], 233 | }; 234 | 235 | export type AddressingModeGroup = "EA" | "DI" | "M"; 236 | 237 | export const addressingModeGroups: Record< 238 | AddressingModeGroup, 239 | AddressingMode[] 240 | > = { 241 | EA: [ 242 | AddressingModes.Dn, 243 | AddressingModes.An, 244 | AddressingModes.AnIndir, 245 | AddressingModes.AnPostInc, 246 | AddressingModes.AnPreDec, 247 | AddressingModes.AnDisp, 248 | AddressingModes.AnDispIx, 249 | AddressingModes.PcDisp, 250 | AddressingModes.PcDispIx, 251 | AddressingModes.AbsW, 252 | AddressingModes.AbsL, 253 | AddressingModes.Imm, 254 | ], 255 | DI: [AddressingModes.Dn, AddressingModes.An, AddressingModes.Imm], 256 | M: [ 257 | AddressingModes.AnIndir, 258 | AddressingModes.AnPostInc, 259 | AddressingModes.AnPreDec, 260 | AddressingModes.AnDisp, 261 | AddressingModes.AnDispIx, 262 | AddressingModes.PcDisp, 263 | AddressingModes.PcDispIx, 264 | AddressingModes.AbsW, 265 | AddressingModes.AbsL, 266 | ], 267 | }; 268 | 269 | export const Directives = { 270 | // Memory: 271 | DC: "DC", 272 | DCB: "DCB", 273 | DS: "DS", 274 | DB: "DB", 275 | DW: "DW", 276 | DL: "DL", 277 | // Sections: 278 | SECTION: "SECTION", 279 | BSS: "BSS", 280 | BSS_C: "BSS_C", 281 | BSS_F: "BSS_F", 282 | CSEG: "CSEG", 283 | CODE: "CODE", 284 | CODE_C: "CODE_C", 285 | CODE_F: "CODE_F", 286 | DATA: "DATA", 287 | DATA_C: "DATA_C", 288 | DATA_F: "DATA_F", 289 | DSEG: "DSEG", 290 | // Assignements: 291 | EQU: "EQU", 292 | FEQU: "FEQU", 293 | "=": "=", 294 | SET: "SET", 295 | // Imports: 296 | INCLUDE: "INCLUDE", 297 | INCDIR: "INCDIR", 298 | INCBIN: "INCBIN", 299 | // Conditions: 300 | IFEQ: "IFEQ", 301 | IFNE: "IFNE", 302 | IFGT: "IFGT", 303 | IFGE: "IFGE", 304 | IFLT: "IFLT", 305 | IFLE: "IFLE", 306 | IFB: "IFB", 307 | IFNB: "IFNB", 308 | IFC: "IFC", 309 | IFNC: "IFNC", 310 | IFD: "IFD", 311 | IFND: "IFND", 312 | IFMACROD: "IFMACROD", 313 | IFMACROND: "IFMACROND", 314 | ELSE: "ELSE", 315 | ENDC: "ENDC", 316 | ENDIF: "ENDIF", 317 | // Macro: 318 | MACRO: "MACRO", 319 | ENDM: "ENDM", 320 | // Repeat: 321 | REPT: "REPT", 322 | ENDR: "ENDR", 323 | // Alignment: 324 | ALIGN: "ALIGN", 325 | EVEN: "EVEN", 326 | ODD: "ODD", 327 | CNOP: "CNOP", 328 | // Other: 329 | OPT: "OPT", 330 | CARGS: "CARGS", 331 | CLRFO: "CLRFO", 332 | CLRSO: "CLRSO", 333 | COMM: "COMM", 334 | COMMENT: "COMMENT", 335 | ECHO: "ECHO", 336 | EINLINE: "EINLINE", 337 | END: "END", 338 | ENDF: "ENDF", 339 | ENDP: "ENDP", 340 | EREM: "EREM", 341 | FAIL: "FAIL", 342 | FPU: "FPU", 343 | IDNT: "IDNT", 344 | INLINE: "INLINE", 345 | JUMPPTR: "JUMPPTR", 346 | LIST: "LIST", 347 | LLEN: "LLEN", 348 | LOAD: "LOAD", 349 | MACHINE: "MACHINE", 350 | MEXIT: "MEXIT", 351 | MMU: "MMU", 352 | NOLIST: "NOLIST", 353 | NOPAGE: "NOPAGE", 354 | NREF: "NREF", 355 | OFFSET: "OFFSET", 356 | OPWORD: "OPWORD", 357 | ORG: "ORG", 358 | OUTPUT: "OUTPUT", 359 | PAGE: "PAGE", 360 | PLEN: "PLEN", 361 | PRINTT: "PRINTT", 362 | PRINTV: "PRINTV", 363 | PUBLIC: "PUBLIC", 364 | RECORD: "RECORD", 365 | REM: "REM", 366 | RORG: "RORG", 367 | RSRESET: "RSRESET", 368 | RSSET: "RSSET", 369 | SETFO: "SETFO", 370 | SETSO: "SETSO", 371 | SPC: "SPC", 372 | TEXT: "TEXT", 373 | TTL: "TTL", 374 | WEAK: "WEAK", 375 | XDEF: "XDEF", 376 | XREF: "XREF", 377 | } as const; 378 | 379 | export type Directive = Values; 380 | export function isDirective(v: string): v is Directive { 381 | return Directives[v as Directive] !== undefined; 382 | } 383 | 384 | /** 385 | * Map alternate to canonical mnemonics used in our mappings. 386 | */ 387 | export const aliases: Record = { 388 | // Non-standard mnemonics 389 | BLO: Mnemonics.BCS, 390 | DBLO: Mnemonics.DBCS, 391 | SLO: Mnemonics.SCS, 392 | DBRA: Mnemonics.DBF, 393 | BHS: Mnemonics.BCC, 394 | DBHS: Mnemonics.DBCC, 395 | SHS: Mnemonics.SCC, 396 | BLK: Directives.DCB, 397 | }; 398 | -------------------------------------------------------------------------------- /images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/parse/index.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import parse from "../../src/parse"; 3 | import { EffectiveAddressNode } from "../../src/parse/nodes"; 4 | import { 5 | Mnemonics, 6 | AddressingModes, 7 | Qualifiers, 8 | Directives, 9 | } from "../../src/syntax"; 10 | 11 | describe("parse()", () => { 12 | test("parse file", () => { 13 | const file = fs 14 | .readFileSync(__dirname + "/../examples/example.s") 15 | .toString(); 16 | const result = parse(file); 17 | expect(result).toHaveLength(843); 18 | }); 19 | 20 | describe("instruction parsing", () => { 21 | test("two operands", () => { 22 | const [result] = parse(" move.w d0,(a0) ; foo"); 23 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 24 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 25 | expect( 26 | ((result.statement 27 | .operands[0] as EffectiveAddressNode) as EffectiveAddressNode).mode 28 | ).toEqual(AddressingModes.Dn); 29 | expect( 30 | ((result.statement 31 | .operands[1] as EffectiveAddressNode) as EffectiveAddressNode).mode 32 | ).toEqual(AddressingModes.AnIndir); 33 | expect(result.timing).toBeTruthy(); 34 | expect(result.bytes).toBeTruthy(); 35 | }); 36 | 37 | test("single operand", () => { 38 | const [result] = parse(" ext.w d0"); 39 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.EXT); 40 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 41 | expect( 42 | ((result.statement 43 | .operands[0] as EffectiveAddressNode) as EffectiveAddressNode).mode 44 | ).toEqual(AddressingModes.Dn); 45 | expect(result.timing).toBeTruthy(); 46 | expect(result.bytes).toBeTruthy(); 47 | }); 48 | 49 | test("unary", () => { 50 | const [result] = parse(" rts"); 51 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.RTS); 52 | expect(result.timing).toBeTruthy(); 53 | expect(result.bytes).toBeTruthy(); 54 | }); 55 | 56 | test("complex expression", () => { 57 | const [result] = parse( 58 | "foo: move.w #(CopperE-Copper)/4-1,d0;foo bar baz" 59 | ); 60 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 61 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 62 | expect( 63 | (result.statement.operands[0] as EffectiveAddressNode).mode 64 | ).toEqual(AddressingModes.Imm); 65 | expect( 66 | (result.statement.operands[1] as EffectiveAddressNode).mode 67 | ).toEqual(AddressingModes.Dn); 68 | expect(result.timing).toBeTruthy(); 69 | expect(result.bytes).toBeTruthy(); 70 | }); 71 | 72 | test("with comment", () => { 73 | const [result] = parse(" move.w d0,(a0);foo bar baz"); 74 | expect(result.statement.comment.text).toEqual(";foo bar baz"); 75 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 76 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 77 | expect( 78 | (result.statement.operands[0] as EffectiveAddressNode).mode 79 | ).toEqual(AddressingModes.Dn); 80 | expect( 81 | (result.statement.operands[1] as EffectiveAddressNode).mode 82 | ).toEqual(AddressingModes.AnIndir); 83 | expect(result.timing).toBeTruthy(); 84 | expect(result.bytes).toBeTruthy(); 85 | }); 86 | 87 | test("with comment - alt", () => { 88 | const [result] = parse(" move.w d0,(a0) *foo bar baz"); 89 | expect(result.statement.comment.text).toEqual("*foo bar baz"); 90 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 91 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 92 | expect( 93 | (result.statement.operands[0] as EffectiveAddressNode).mode 94 | ).toEqual(AddressingModes.Dn); 95 | expect( 96 | (result.statement.operands[1] as EffectiveAddressNode).mode 97 | ).toEqual(AddressingModes.AnIndir); 98 | expect(result.timing).toBeTruthy(); 99 | expect(result.bytes).toBeTruthy(); 100 | }); 101 | 102 | test("with label", () => { 103 | const [result] = parse("foo: move.w d0,(a0)"); 104 | expect(result.statement.label.text).toEqual("foo"); 105 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 106 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 107 | expect( 108 | (result.statement.operands[0] as EffectiveAddressNode).mode 109 | ).toEqual(AddressingModes.Dn); 110 | expect( 111 | (result.statement.operands[1] as EffectiveAddressNode).mode 112 | ).toEqual(AddressingModes.AnIndir); 113 | expect(result.timing).toBeTruthy(); 114 | expect(result.bytes).toBeTruthy(); 115 | }); 116 | 117 | test("with label - local", () => { 118 | const [result] = parse(".foo: move.w d0,(a0)"); 119 | expect(result.statement.label.text).toEqual(".foo"); 120 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 121 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 122 | expect( 123 | (result.statement.operands[0] as EffectiveAddressNode).mode 124 | ).toEqual(AddressingModes.Dn); 125 | expect( 126 | (result.statement.operands[1] as EffectiveAddressNode).mode 127 | ).toEqual(AddressingModes.AnIndir); 128 | expect(result.timing).toBeTruthy(); 129 | expect(result.bytes).toBeTruthy(); 130 | }); 131 | 132 | test("with label - no colon", () => { 133 | const [result] = parse("foo move.w d0,(a0)"); 134 | expect(result.statement.label.text).toEqual("foo"); 135 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 136 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 137 | expect( 138 | (result.statement.operands[0] as EffectiveAddressNode).mode 139 | ).toEqual(AddressingModes.Dn); 140 | expect( 141 | (result.statement.operands[1] as EffectiveAddressNode).mode 142 | ).toEqual(AddressingModes.AnIndir); 143 | expect(result.timing).toBeTruthy(); 144 | expect(result.bytes).toBeTruthy(); 145 | }); 146 | 147 | test("default qualifier", () => { 148 | const [result] = parse(" MOVE D0,(A0)"); 149 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 150 | expect(result.statement.opcode.qualifier).toBeFalsy(); 151 | expect( 152 | (result.statement.operands[0] as EffectiveAddressNode).mode 153 | ).toEqual(AddressingModes.Dn); 154 | expect( 155 | (result.statement.operands[1] as EffectiveAddressNode).mode 156 | ).toEqual(AddressingModes.AnIndir); 157 | expect(result.timing).toBeTruthy(); 158 | expect(result.bytes).toBeTruthy(); 159 | }); 160 | 161 | test("case insensitive", () => { 162 | const [result] = parse(" MOVE.W D0,(A0)"); 163 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 164 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 165 | expect( 166 | (result.statement.operands[0] as EffectiveAddressNode).mode 167 | ).toEqual(AddressingModes.Dn); 168 | expect( 169 | (result.statement.operands[1] as EffectiveAddressNode).mode 170 | ).toEqual(AddressingModes.AnIndir); 171 | expect(result.timing).toBeTruthy(); 172 | expect(result.bytes).toBeTruthy(); 173 | }); 174 | 175 | test("space in parentheses", () => { 176 | const [result] = parse(" move.w d0,( a0 )"); 177 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 178 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 179 | expect( 180 | (result.statement.operands[0] as EffectiveAddressNode).mode 181 | ).toEqual(AddressingModes.Dn); 182 | expect( 183 | (result.statement.operands[1] as EffectiveAddressNode).mode 184 | ).toEqual(AddressingModes.AnIndir); 185 | expect(result.timing).toBeTruthy(); 186 | expect(result.bytes).toBeTruthy(); 187 | }); 188 | }); 189 | 190 | test("label no space", () => { 191 | const [result] = parse("a:MOVE.W D0,(A0)"); 192 | expect(result.statement.label.text).toEqual("a"); 193 | expect(result.statement.opcode.op.name).toEqual(Mnemonics.MOVE); 194 | expect(result.statement.opcode.qualifier.name).toEqual(Qualifiers.W); 195 | expect((result.statement.operands[0] as EffectiveAddressNode).mode).toEqual( 196 | AddressingModes.Dn 197 | ); 198 | expect((result.statement.operands[1] as EffectiveAddressNode).mode).toEqual( 199 | AddressingModes.AnIndir 200 | ); 201 | expect(result.timing).toBeTruthy(); 202 | expect(result.bytes).toBeTruthy(); 203 | }); 204 | 205 | describe("directives", () => { 206 | test("assignment EQU", () => { 207 | const [result] = parse("foo EQU 1"); 208 | expect(result.statement.label.text).toEqual("foo"); 209 | expect(result.statement.opcode.op.name).toEqual(Directives.EQU); 210 | expect( 211 | (result.statement.operands[0] as EffectiveAddressNode).text 212 | ).toEqual("1"); 213 | }); 214 | 215 | test("assignment =", () => { 216 | const [result] = parse("foo = 1"); 217 | expect(result.statement.label.text).toEqual("foo"); 218 | expect(result.statement.opcode.op.name).toEqual(Directives["="]); 219 | expect( 220 | (result.statement.operands[0] as EffectiveAddressNode).text 221 | ).toEqual("1"); 222 | }); 223 | 224 | test("assignment =", () => { 225 | const [result] = parse("foo=1"); 226 | expect(result.statement.label.text).toEqual("foo"); 227 | expect(result.statement.opcode.op.name).toEqual(Directives["="]); 228 | expect( 229 | (result.statement.operands[0] as EffectiveAddressNode).text 230 | ).toEqual("1"); 231 | }); 232 | 233 | test("memory", () => { 234 | const [result] = parse("a: dc.w 1,2,3"); 235 | expect(result.statement.label.text).toEqual("a"); 236 | expect(result.statement.opcode.op.name).toEqual(Directives.DC); 237 | expect( 238 | (result.statement.operands[0] as EffectiveAddressNode).text 239 | ).toEqual("1"); 240 | expect( 241 | (result.statement.operands[1] as EffectiveAddressNode).text 242 | ).toEqual("2"); 243 | expect(result.statement.operands[2].text).toEqual("3"); 244 | }); 245 | 246 | test("macro definition", () => { 247 | const [result] = parse("a: macro"); 248 | expect(result.statement.label.text).toEqual("a"); 249 | expect(result.statement.opcode.op.name).toEqual(Directives.MACRO); 250 | }); 251 | 252 | test("macro invocation: label", () => { 253 | const lines = parse(` 254 | a: macro 255 | move.w \\1,\\2 256 | endm 257 | a d0,(a0)`); 258 | expect(lines[4].macroLines).toHaveLength(1); 259 | expect(lines[4].bytes).toEqual(2); 260 | expect(lines[4].timing?.values).toEqual([[8, 1, 1]]); 261 | }); 262 | 263 | test("macro invocation: operand", () => { 264 | const lines = parse(` 265 | macro a 266 | move.w \\1,\\2 267 | endm 268 | a d0,(a0)`); 269 | expect(lines[4].macroLines).toHaveLength(1); 270 | expect(lines[4].bytes).toEqual(2); 271 | expect(lines[4].timing?.values).toEqual([[8, 1, 1]]); 272 | }); 273 | 274 | test("rept", () => { 275 | const lines = parse(` 276 | rept 4 277 | move.w d0,(a0) 278 | endr`); 279 | expect(lines[3].macroLines).toHaveLength(4); 280 | expect(lines[3].bytes).toEqual(2 * 4); 281 | expect(lines[3].timing.values).toEqual([[8 * 4, 4, 4]]); 282 | }); 283 | 284 | test("bss section", () => { 285 | const lines = parse(` 286 | a: ds.w 1 287 | BSS 288 | b: ds.w 1 289 | DATA 290 | c: ds.w 1 291 | SECTION "foo",BSS 292 | d: ds.w 1 293 | 294 | `); 295 | expect(lines[1].bss).toBe(false); 296 | expect(lines[3].bss).toBe(true); 297 | expect(lines[5].bss).toBe(false); 298 | expect(lines[7].bss).toBe(true); 299 | }); 300 | }); 301 | 302 | describe("parse assignments", () => { 303 | test("dynamic shift", () => { 304 | const code = ` 305 | foo=4 306 | lsl.w #foo,d0 307 | `; 308 | const lines = parse(code); 309 | const n = 4; 310 | expect(lines[2].timing.values).toEqual([[6 + 2 * n, 1, 0]]); 311 | }); 312 | 313 | test("forward ref", () => { 314 | const code = ` 315 | lsl.w #foo,d0 316 | foo=4 317 | `; 318 | const lines = parse(code); 319 | const n = 4; 320 | expect(lines[1].timing.values).toEqual([[6 + 2 * n, 1, 0]]); 321 | }); 322 | }); 323 | 324 | describe("no instruction", () => { 325 | test("empty line", () => { 326 | const [result] = parse(""); 327 | expect(result.statement.comment).toBeFalsy(); 328 | expect(result.statement.label).toBeFalsy(); 329 | expect(result.statement.opcode).toBeFalsy(); 330 | }); 331 | 332 | test("only whitespace", () => { 333 | const [result] = parse(" "); 334 | expect(result.statement.comment).toBeFalsy(); 335 | expect(result.statement.label).toBeFalsy(); 336 | expect(result.statement.opcode).toBeFalsy(); 337 | }); 338 | 339 | test("only comment", () => { 340 | const [result] = parse("; foo bar baz"); 341 | expect(result.statement.comment.text).toEqual("; foo bar baz"); 342 | expect(result.statement.opcode).toBeFalsy(); 343 | }); 344 | 345 | test("only comment - alt", () => { 346 | const [result] = parse("* foo bar baz"); 347 | expect(result.statement.comment.text).toEqual("* foo bar baz"); 348 | expect(result.statement.opcode).toBeFalsy(); 349 | }); 350 | 351 | test("only label", () => { 352 | const [result] = parse("foo:"); 353 | expect(result.statement.label.text).toEqual("foo"); 354 | expect(result.statement.opcode).toBeFalsy(); 355 | }); 356 | 357 | test("only label - local", () => { 358 | const [result] = parse(".foo:"); 359 | expect(result.statement.label.text).toEqual(".foo"); 360 | expect(result.statement.opcode).toBeFalsy(); 361 | }); 362 | 363 | test("only label - no colon", () => { 364 | const [result] = parse("foo"); 365 | expect(result.statement.label.text).toEqual("foo"); 366 | expect(result.statement.opcode).toBeFalsy(); 367 | }); 368 | }); 369 | }); 370 | -------------------------------------------------------------------------------- /test/examples/example.s: -------------------------------------------------------------------------------- 1 | INCDIR "include" 2 | INCLUDE "PhotonsMiniWrapper1.04!.i" 3 | 4 | ******************************************************************************** 5 | * Constants: 6 | ******************************************************************************** 7 | 8 | INCLUDE "defs.i" 9 | 10 | ; Flags: 11 | INTF = INTF_SETCLR|INTF_INTEN|INTF_VERTB 12 | DMAF = DMAF_SETCLR|DMAF_BLITHOG|DMAF_MASTER|DMAF_RASTER|DMAF_COPPER|DMAF_BLITTER 13 | 14 | ; Screen: 15 | BPLS = 3 ; Number of bitplanes 16 | H = 256 ; Lines (256 normal PAL, 272 overscan) 17 | W = 320 ; Pixels (multiple of 16, 320 normal, 352 overscan) 18 | DIW_V = $2c ; Hardware Vstart ($2c normal, $24 overscan) 19 | ENDLINE = DIW_V+H ; Safe to starting clearing after this scanline 20 | WWI = W/16 ; Word width 21 | BWI = WWI*2 ; Byte width 22 | BPL_B = BWI*H ; Bitplane bytes 23 | SCREEN_B = BPL_B*BPLS ; Screen bytes 24 | IMAGE_MOD = BWI*(BPLS-1) 25 | 26 | ******************************************************************************** 27 | * Macros: 28 | ******************************************************************************** 29 | 30 | WAITBLIT:macro 31 | tst.w (a6) ;for compatibility with A1000 32 | .wb\@: btst #6,2(a6) 33 | bne.s .wb\@ 34 | endm 35 | 36 | ******************************************************************************** 37 | * Main entry point: 38 | ******************************************************************************** 39 | 40 | ;------------------------------------------------------------------------------- 41 | ; a4=VBR, a6=Custom 42 | Demo: 43 | ; Init: 44 | move.l #VBint,$6c(a4) 45 | move.w #INTF,intena(a6) 46 | move.w #DMAF,dmacon(a6) ; dsf 47 | 48 | lea Arr,a0 49 | move.w #8,d0 50 | BLO Sort 51 | 52 | lea Image,a0 53 | lea Screen1,a1 54 | bsr CopyImage 55 | lea Screen2,a1 56 | bsr CopyImage 57 | 58 | lea Copper1, 59 | a0 60 | bsr CopyCopper 61 | lea Copper2,a0 62 | bsr CopyCopper 63 | 64 | lea Screen1,a0 65 | lea Copper1,a1 66 | bsr PokePtrs 67 | lea Screen2,a0 68 | lea Copper2,a1 69 | bsr PokePtrs 70 | 71 | ; move.w #10,d0 72 | ; move.w #48,d1 73 | ; move.w #6,d2 74 | ; lea Screen2,a0 75 | ; bsr ShiftRight 76 | 77 | lea Screen2,a0 78 | 79 | ; move.w #$a1,d0 ; 161 80 | ; move.w #$bf,d1 ; 191 81 | ; move.w #1,d2 82 | ; bsr ShiftLeft 83 | 84 | ; move.w #$c0,d0 85 | ; move.w #$dc,d1 86 | ; move.w #2,d2 87 | ; bsr ShiftLeft 88 | 89 | ; move.w #$de,d0 90 | ; move.w #$108,d1 91 | ; move.w #3,d2 92 | ; bsr ShiftLeft 93 | 94 | move.w #$10a,d0 95 | move.w #$122,d1 96 | move.w #4,d2 97 | bsr ShiftLeft 98 | 99 | move.w #$124,d0 100 | move.w #$140,d1 101 | move.w #5,d2 102 | bsr ShiftLeft 103 | 104 | .mainLoop: 105 | move.w #ENDLINE,d0 106 | bsr WaitRaster 107 | bsr SwapBuffer 108 | move.l ViewCopper,cop1lc(a6) 109 | 110 | move.w ScaleWidth(pc),d0 ; d0.w = target width 111 | add.w ScaleInc(pc),d0 112 | 113 | cmp.w #160,d0 114 | bgt .notMin 115 | neg.w ScaleInc 116 | .notMin 117 | cmp.w #320,d0 118 | blt .notMax 119 | neg.w ScaleInc 120 | .notMax 121 | 122 | move.w d0,ScaleWidth 123 | ; bsr ScaleH 124 | ; move.w #$000,$180(a6) 125 | 126 | move.l #H<<6,d1 ; Scale height proportional to width 127 | divu #W,d1 128 | ext.l d1 129 | mulu d0,d1 130 | lsr.l #6,d1 131 | ; move.w d1,ScaleHeight 132 | 133 | ; move.w #$ff0,$180(a6) 134 | bsr ScaleV 135 | 136 | ; move.w #$000,$180(a6) ; Show rastertime left down to $12c 137 | btst #6,$bfe001 ; Left mouse button not pressed? 138 | bne.w .mainLoop ; then loop 139 | .exit rts 140 | 141 | ******************************************************************************** 142 | * Routines: 143 | ******************************************************************************** 144 | 145 | ;------------------------------------------------------------------------------- 146 | ; Sort array using Selection Sort argorithm 147 | ; a0.l = array 148 | ; d0.w = length 149 | Sort: 150 | movem.l d0-d3/a0-a2,-(sp) 151 | subq.w #2,d0 152 | blt .end 153 | .l0: 154 | move.w d0,d1 155 | move.l a0,a1 156 | move.w (a1)+,d2 ; d2 = lowest value 157 | .l1: 158 | move.w (a1),d3 159 | cmp.w d2,d3 160 | bge .next1 161 | move.w d3,d2 162 | move.l a1,a2 ; a2 = ptr to lowest 163 | .next1 addq.l #2,a1 164 | dbf d1,.l1 165 | ; Swap lowest with first item if not equal 166 | cmp.w (a0),d2 167 | beq .next0 168 | move.w (a0),(a2) 169 | move.w d2,(a0) 170 | .next0 addq.l #2,a0 171 | dbf d0,.l0 172 | .end movem.l (sp)+,d0-d3/a0-a2 173 | rts 174 | 175 | ;------------------------------------------------------------------------------- 176 | ; d1.w = target width 177 | ScaleH: 178 | movem.l d0-d4/a0-a3,-(sp) 179 | move.l DrawScreen,a0 180 | move.l DrawWidth(pc),a1 181 | move.w (a1),d1 ; d1.w = current width 182 | move.w d0,(a1) ; Update current width 183 | 184 | move.w #W,d7 ; d7.w = left offset 185 | sub.w d1,d7 186 | lsr.w d7 187 | 188 | lea ColPositionsE(pc),a2 189 | move.w d1,d4 190 | add.w d4,d4 191 | ext.l d4 192 | sub.l d4,a2 ; Offset a2 to current step 193 | 194 | move.w d0,d3 195 | sub.w d1,d3 ; d3.w = delta 196 | blt .scaleDown 197 | ; ; Scale up: 198 | ; lea Image,a1 199 | ; lea SrcColsE(pc),a3 200 | ; sub.l d4,a3 ; Offset a3 to current step 201 | ; move.w d1,d2 202 | ; bra .endL0 203 | ; .l0 move.w -(a2),d0 204 | ; move.w d2,d1 205 | ; bsr InsertCol 206 | ; move.w -(a3),d1 207 | ; bsr CopyCol 208 | ; addq.w #1,d2 209 | ; .endL0 dbf d3,.l0 210 | ; bra .exit 211 | .scaleDown: 212 | neg.w d3 213 | move.w d3,d0 ; d0.w = array length 214 | clr.w d5 ; d5.w = Left hand cols count 215 | lea Cols,a0 216 | bra .endL1 217 | .l1 move.w (a2)+,d2 218 | add.w d7,d2 ; Add offset 219 | move.w d2,(a0)+ ; Add columns to array for sorting 220 | cmp.w #W/2,d2 ; Count left hand columns 221 | bge .endL1 222 | addq.w #1,d5 223 | .endL1 dbf d3,.l1 224 | 225 | lea Cols,a0 ; Sort cols array 226 | bsr Sort 227 | 228 | move.w d0,d4 ; d4.w = Right hand cols count 229 | sub.w d5,d4 230 | move.l DrawScreen,a0 231 | 232 | ; LHS: 233 | ; Move continuous sections right in increasing increments - rtl 234 | ; | |x| | | |x| | | |x| | x = to remove 235 | ; --1-> + = space 236 | ; | |x| | | |x|+| | | | | 237 | ; --2-> 238 | ; | |x|+|+| | | | | | | | 239 | ; 3-> 240 | ; |+|+|+| | | | | | | | | 241 | lea Cols,a1 242 | ext.l d5 ; Move to middle of array - iterate first half in reverse 243 | add.l d5,a1 244 | add.l d5,a1 245 | move.l a1,a2 246 | moveq #1,d2 ; d2.w = shift amount 247 | bra .endL3 248 | .l3 move.w -(a1),d1 ; d1.w = shift 'to' column (col to remove - 1) 249 | subq.w #1,d1 250 | tst.w d5 ; Is there a 'next' value? 251 | bne .getNext0 252 | move.w d7,d0 ; d0.w = shift 'from' column (image start) 253 | bra .apply0 254 | .getNext0 255 | move.w -2(a1),d0 ; d0.w = shift 'from' column (next col to remove + 1) 256 | addq.w #1,d0 257 | .apply0 258 | bsr ShiftRight 259 | addq.w #1,d2 ; Increment shift amount 260 | .endL3 dbf d5,.l3 261 | 262 | ; RHS: 263 | ; Move continuous sections left in increasing increments - ltr 264 | ; | |x| | | |x| | | |x| | x = to remove 265 | ; <-1-- + = space 266 | ; | | | | |+|x| | | |x| | 267 | ; <-2-- 268 | ; | | | | | | | |+|+|x| | 269 | ; <3 270 | ; | | | | | | | | |+|+|+| 271 | move.l a2,a1 272 | moveq #1,d2 ; d2.w = shift amount 273 | bra .endL2 274 | .l2 move.w (a1)+,d0 ; d0.w = shift 'from' column (col to remove + 1) 275 | addq.w #1,d0 276 | tst.w d4 ; Is there a 'next' value? 277 | bne .getNext1 278 | move.w #W,d1 ; d1.w = shift 'to' column (image end) 279 | sub.w d7,d1 280 | bra .apply1 281 | .getNext1 282 | move.w (a1),d1 ; d1.w = shift 'to' column (next col to remove - 1) 283 | subq.w #1,d1 284 | .apply1 285 | bsr ShiftLeft 286 | addq.w #1,d2 ; Increment shift amount 287 | .endL2 dbf d4,.l2 288 | 289 | .exit movem.l (sp)+,d0-d4/a0-a3 290 | rts 291 | 292 | ; a0 293 | ; a1,dc,1 294 | ; dd 295 | ; de,108,2 296 | ; 109 297 | ; 10a,140,3 298 | 299 | ; a0 300 | ; a1,be,1 301 | ; bf 302 | ; c0,dc,2 303 | ; dd 304 | ; de,108,3 305 | ; 109 306 | ; 10a,122,4 307 | ; 123 308 | ; 124,140,5 309 | 310 | ; a9 311 | ; b0,c0,1 312 | ; c1 313 | ; c2,f0,2 314 | ; f1 315 | ; f2,111,3 316 | ; 112 317 | ; 113,124,4 318 | ; 125 319 | ; 126,13e,5 320 | 321 | ; b2 322 | ; b3,e3,1 323 | ; ... 324 | 325 | ;------------------------------------------------------------------------------- 326 | ; Copy one column of image data 327 | ; d0.w = dest column index 328 | ; d1.w = source column index 329 | ; a0.l = dest buffer 330 | ; a1.l = source image 331 | CopyCol: 332 | movem.l d0-d4/a0-a1,-(sp) 333 | move.w d1,d2 334 | move.w d0,d3 335 | lsr.w #4,d2 ; d2.w = source word offset 336 | lsr.w #4,d3 ; d3.w = dest word offset 337 | and.w #$f,d1 ; d1.w = source bits remainder 338 | and.w #$f,d0 ; d0.w = dest bits remainder 339 | 340 | ext.l d2 ; Move pointers to blit start: 341 | ext.l d3 342 | add.l d2,a1 343 | add.l d2,a1 344 | add.l d3,a0 345 | add.l d3,a0 346 | 347 | clr.w d3 ; d3.w = bltafwm 348 | moveq #15,d4 349 | sub.w d0,d4 350 | bset d4,d3 351 | 352 | clr.w d4 ; d4.w = bltcon1 353 | sub.w d1,d0 ; d0.w = B Shift 354 | bge .pos 355 | neg.w d0 ; Reverse for left shift 356 | moveq #BLITREVERSE,d4 357 | add.l #BWI*BPLS*H-BWI,a1 358 | add.l #BWI*BPLS*H-BWI,a0 359 | .pos 360 | lsl.w #6,d0 ; Add B Shift to bltcon1 361 | lsl.w #6,d0 362 | add.w d0,d4 363 | 364 | WAITBLIT 365 | move.w #SRCB!SRCC!DEST!$ca,bltcon0(a6) 366 | move.w d4,bltcon1(a6) 367 | move.l a1,bltbpt(a6) 368 | move.l a0,bltcpt(a6) 369 | move.l a0,bltdpt(a6) 370 | move.w #BWI-2,bltbmod(a6) 371 | move.w #BWI-2,bltcmod(a6) 372 | move.w #BWI-2,bltdmod(a6) 373 | move.w #$ffff,bltalwm(a6) 374 | move.w d3,bltafwm(a6) 375 | move.w #$ffff,bltadat(a6) 376 | move.w #H*BPLS*64+1,bltsize(a6) 377 | movem.l (sp)+,d0-d4/a0-a1 378 | rts 379 | 380 | ;------------------------------------------------------------------------------- 381 | ; d0.w = start column 382 | ; d1.w = end column 383 | ; d2.w = shift amount 384 | ; a0.l = draw screen 385 | ShiftRight: 386 | movem.l d0-a6,-(sp) 387 | addq.w #1,d1 ; make end col inclusive 388 | add.w d2,d1 389 | 390 | move.w d0,d6 ; d6.w = start word 391 | lsr.w #4,d6 392 | 393 | ext.l d6 ; Move ptr to start of blit 394 | add.l d6,a0 395 | add.l d6,a0 396 | 397 | move.w d1,d5 398 | lsr.w #4,d5 399 | sub.w d6,d5 400 | addq.w #1,d5 ; d5.w = word width 401 | 402 | and.w #$f,d0 ; d0.w = start bits 403 | cmp.w d2,d0 404 | bge .ok 405 | move.w d2,d0 406 | .ok 407 | and.w #$f,d1 ; d1.w = end bits 408 | move.w #$ffff,d3 ; d3.w = fwm 409 | move.w #$ffff,d4 ; d4.w = lwm 410 | lsr.w d0,d3 411 | lsr.w d1,d4 412 | not.w d4 413 | 414 | move.w #BWI,d6 ; d6.w = modulo 415 | sub.w d5,d6 416 | sub.w d5,d6 417 | 418 | lsl.w #8,d2 ; d2.w = bltcon1 419 | lsl.w #4,d2 ; !shift << 12 420 | 421 | add.w #H*BPLS*64,d5 ; d5.w = blit size 422 | 423 | WAITBLIT 424 | move.w #SRCB!SRCC!DEST!$ca,bltcon0(a6) 425 | move.w d2,bltcon1(a6) 426 | move.l a0,bltbpt(a6) ; Source for shifted data 427 | move.l a0,bltcpt(a6) ; Original data for masked areas 428 | move.l a0,bltdpt(a6) ; Destination 429 | move.w d6,bltbmod(a6) 430 | move.w d6,bltcmod(a6) 431 | move.w d6,bltdmod(a6) 432 | move.w #$ffff,bltadat(a6) 433 | move.w d3,bltafwm(a6) 434 | move.w d4,bltalwm(a6) 435 | move.w d5,bltsize(a6) 436 | 437 | movem.l (sp)+,d0-a6 438 | rts 439 | 440 | ;------------------------------------------------------------------------------- 441 | ; d0.w = start column 442 | ; d1.w = end column 443 | ; d2.w = shift amount 444 | ; a0.l = draw screen 445 | ShiftLeft: 446 | movem.l d0-a6,-(sp) 447 | subq.w #2,d0 ; ??? 448 | addq.w #1,d1 ; make end col inclusive 449 | add.w d2,d0 450 | 451 | move.w d0,d6 ; d6.w = start word 452 | lsr.w #4,d6 453 | 454 | ext.l d6 ; Move ptr to start of blit 455 | add.l d6,a0 456 | add.l d6,a0 457 | 458 | move.w d1,d5 459 | lsr.w #4,d5 460 | sub.w d6,d5 461 | addq.w #1,d5 ; d5.w = word width 462 | 463 | and.w #$f,d0 ; d0.w = start bits 464 | and.w #$f,d1 ; d1.w = end bits 465 | cmp.w d2,d1 466 | bge .ok 467 | move.w d2,d1 ; ??? 468 | .ok 469 | move.w #$ffff,d3 ; d3.w = lwm 470 | move.w #$ffff,d4 ; d4.w = fwm 471 | lsr.w d0,d3 472 | lsr.w d1,d4 473 | not.w d4 474 | 475 | move.w #BWI,d6 ; d6.w = modulo 476 | sub.w d5,d6 477 | sub.w d5,d6 478 | 479 | lsl.w #8,d2 ; d2.w = bltcon1 480 | lsl.w #4,d2 ; !shift << 12 481 | add.w #BLITREVERSE,d2 482 | 483 | add.l #SCREEN_B-BWI-1,a0 ; Move ptr to end of blit 484 | ext.l d5 485 | add.l d5,a0 486 | add.l d5,a0 487 | 488 | add.w #H*BPLS*64,d5 ; d5.w = blit size 489 | 490 | WAITBLIT 491 | move.w #SRCB!SRCC!DEST!$ca,bltcon0(a6) 492 | move.w d2,bltcon1(a6) 493 | move.l a0,bltbpt(a6) ; Source for shifted data 494 | move.l a0,bltcpt(a6) ; Original data for masked areas 495 | move.l a0,bltdpt(a6) ; Destination 496 | move.w d6,bltbmod(a6) 497 | move.w d6,bltcmod(a6) 498 | move.w d6,bltdmod(a6) 499 | move.w #$ffff,bltadat(a6) 500 | move.w d3,bltalwm(a6) 501 | move.w d4,bltafwm(a6) 502 | move.w d5,bltsize(a6) 503 | 504 | movem.l (sp)+,d0-a6 505 | rts 506 | 507 | 508 | ;------------------------------------------------------------------------------- 509 | ; Insert add additional column into image by shifting from center 510 | ; d0.w = column to delete 511 | ; d1.w = current width 512 | ; a0.l = draw screen 513 | InsertCol: 514 | movem.l d0-a6,-(sp) 515 | moveq #1,d2 ; shift by 1 516 | 517 | move.w #W,d3 ; calculate start column of image 518 | sub.w d1,d3 ; screen width - current width / 2 519 | lsr.w d3 520 | 521 | cmp.w #W/2,d0 522 | bge .rhs 523 | 524 | move.w d0,d1 525 | move.w d3,d0 526 | subq.w #2,d0 527 | move #0,d0 528 | bsr ShiftLeft 529 | bra .end 530 | .rhs 531 | move.w #W-1,d1 532 | ; sub.w d3,d1 533 | bsr ShiftRight 534 | .end movem.l (sp)+,d0-a6 535 | rts 536 | 537 | ;------------------------------------------------------------------------------- 538 | ; a0=image source 539 | ; a1=screen destination address to clear 540 | CopyImage: 541 | bsr WaitBlitter 542 | clr.w bltamod(a6) 543 | clr.w bltdmod(a6) 544 | move.w #BLTEN_AD+BLT_A,bltcon0(a6) 545 | move.l a0,bltapt(a6) 546 | move.l a1,bltdpt(a6) 547 | move.w #$ffff,bltafwm(a6) 548 | move.w #$ffff,bltalwm(a6) 549 | move.w #H*BPLS*64+BWI/2,bltsize(a6) 550 | rts 551 | 552 | ;------------------------------------------------------------------------------- 553 | ; Double buffering of copper and screen 554 | SwapBuffer: 555 | movem.l DrawCopper(pc),a2-a3 556 | exg a2,a3 557 | movem.l a2-a3,DrawCopper 558 | movem.l DrawScreen(pc),a2-a3 559 | exg a2,a3 560 | movem.l a2-a3,DrawScreen 561 | movem.l DrawWidth(pc),a2-a3 562 | exg a2,a3 563 | movem.l a2-a3,DrawWidth 564 | rts 565 | 566 | ;------------------------------------------------------------------------------- 567 | ; Vertical blank interupt 568 | VBint: 569 | movem.l d0/a6,-(sp) ;Save used registers 570 | lea custom,a6 571 | btst #5,intreqr+1(a6) ;check if it's our vertb int. 572 | beq.s .notvb 573 | 574 | move.l Frame(pc),d0 ; Increment frame 575 | add.l #1,d0 576 | move.l d0,Frame 577 | 578 | moveq #$20,d0 ;poll irq bit 579 | move.w d0,intreq(a6) 580 | move.w d0,intreq(a6) 581 | .notvb: movem.l (sp)+,d0/a6 ;restore 582 | rte 583 | 584 | ;------------------------------------------------------------------------------- 585 | ; Copy initial copper data to buffer 586 | ; a0 = dest 587 | CopyCopper: 588 | lea Copper(pc),a1 589 | move.w #(CopperE-Copper)/4-1,d0 590 | .l0 move.l (a1)+,(a0)+ 591 | dbf d0,.l0 592 | rts 593 | 594 | ;------------------------------------------------------------------------------- 595 | ; Generic, poke ptrs into copper list 596 | ; a0 = screen 597 | ; a1 = copper address 598 | PokePtrs: 599 | moveq #BPLS-1,d1 600 | add.l #BplPtrs+2-Copper,a1 601 | .bpll: move.l a0,d2 602 | swap d2 603 | move.w d2,(a1) ;high word of address 604 | move.w a0,4(a1) ;low word of address 605 | addq.w #8,a1 ;skip two copper instructions 606 | add.l #BWI,a0 ;next ptr 607 | dbf d1,.bpll 608 | rts 609 | 610 | ;------------------------------------------------------------------------------- 611 | ; Scale effect with current image 612 | ScaleV: 613 | movem.l d0-a6,-(sp) 614 | move.l DrawCopper,a2 615 | move.w ScaleHeight,d1 ; d1.w = height 616 | ; Adjust for image shorter than screen: 617 | move.w #H,d2 618 | sub.w d1,d2 ; d2 = screen to image height diff / 2 619 | asr.w d2 620 | move.b #DIW_V,d3 ; Calc diwstrt 621 | add.b d2,d3 622 | lsl.w #8,d3 623 | move.b #$81,d3 624 | move.w d3,2(a2) 625 | move.b #DIW_V-1,d3 ; Calc diwstop 626 | sub.b d2,d3 627 | lsl.w #8,d3 628 | move.b #$c1,d3 629 | move.w d3,6(a2) 630 | 631 | add.l #Lines-Copper+2,a2 632 | ; mulu.w #12,d2 633 | ; (x<<2 - x)<<2 634 | move.w d2,d3 635 | lsl.w #2,d2 636 | sub.w d3,d2 637 | lsl.w #2,d2 638 | ext.l d2 639 | add.l d2,a2 640 | move.l #H<<7,d0 ; d0.l = scale fraction FP 641 | divu d1,d0 642 | ext.l d0 643 | clr.w d2 ; d2.w = last image line 644 | clr.l d3 ; d3.l = scaled line FP 645 | .line: 646 | ; Calculate modulo for line delta: 647 | move.l d3,d6 ; d6.w = scaled line < FP 648 | lsr.l #7,d6 649 | move.w d6,d5 ; d5.w = line delta (currentLine - prevLine) 650 | sub.w d2,d5 651 | move.w d6,d2 ; update last line 652 | ; mulu.w #BWI*BPLS,d5 ; d5.w = modulo 653 | ; Optimise: Avoid mulu in inner loop 654 | ; x*120 655 | ; x*128 - x*8 656 | ; (x*16-x)*8 657 | ; (x<<4-x)<<3 658 | move.w d5,d6 659 | lsl.w #4,d5 660 | sub.w d6,d5 661 | lsl.w #3,d5 662 | 663 | sub.w #BWI,d5 664 | move.w d5,(a2) 665 | move.w d5,4(a2) 666 | ; Next line 667 | add.l d0,d3 668 | lea 12(a2),a2 669 | dbf.w d1,.line 670 | 671 | movem.l (sp)+,d0-a6 672 | rts 673 | 674 | ******************************************************************************** 675 | * Fastmem data: 676 | ******************************************************************************** 677 | 678 | Arr: 679 | dc.w $10,$30,$15,$3,$40,$17,$1,$39 680 | 681 | Frame dc.l 0 682 | DrawScreen dc.l Screen2 683 | ViewScreen dc.l Screen1 684 | DrawCopper dc.l Copper2 685 | ViewCopper dc.l Copper1 686 | ScaleStep dc.w 0 687 | ScaleHeight dc.w H 688 | ScaleWidth dc.w W 689 | ScaleInc dc.w -5 690 | DrawWidth dc.l Width2 691 | ViewWidth dc.l Width1 692 | Width1 dc.w W 693 | Width2 dc.w W 694 | 695 | Copper: 696 | dc.w diwstrt,$2c81 697 | dc.w diwstop,$2cc1 698 | dc.w ddfstrt,$38 699 | dc.w ddfstop,$d0 700 | dc.w fmode,0 701 | dc.w bplcon0,BPLS<<12+$200 702 | dc.w bplcon1,$0c00 703 | dc.w bplcon3,0 704 | dc.w bpl1mod,IMAGE_MOD 705 | dc.w bpl2mod,IMAGE_MOD 706 | Palette: 707 | ; incbin "abstract.pal" 708 | incbin "test.pal" 709 | BplPtrs: 710 | dc.w bpl0pt,0 711 | dc.w bpl0ptl,0 712 | dc.w bpl1pt,0 713 | dc.w bpl1ptl,0 714 | dc.w bpl2pt,0 715 | dc.w bpl2ptl,0 716 | Lines: 717 | .l SET $2bdf 718 | REPT 256-$2b 719 | dc.w bpl1mod,IMAGE_MOD 720 | dc.w bpl2mod,IMAGE_MOD 721 | dc.w .l,$fffe 722 | .l SET .l+$100 723 | ENDR 724 | .l SET $df 725 | REPT $2b 726 | dc.w bpl1mod,IMAGE_MOD 727 | dc.w bpl2mod,IMAGE_MOD 728 | dc.w .l,$fffe 729 | .l SET .l+$100 730 | ENDR 731 | dc.l $fffffffe 732 | CopperE: 733 | 734 | ColPositions: 735 | dc.w 160,80,265,40,221,131,291,98 736 | dc.w 191,16,239,142,272,55,173,105 737 | dc.w 223,26,294,83,193,116,239,7 738 | dc.w 160,63,266,141,196,44,248,86 739 | dc.w 170,30,273,123,203,56,148,102 740 | dc.w 228,10,210,91,272,45,172,122 741 | dc.w 186,19,235,70,154,106,244,31 742 | dc.w 197,61,144,128,219,3,170,74 743 | dc.w 242,95,199,36,151,56,223,106 744 | dc.w 127,15,195,82,172,45,234,113 745 | dc.w 152,22,196,64,137,83,219,7 746 | dc.w 168,94,150,39,197,55,120,25 747 | dc.w 222,106,169,68,149,10,185,83 748 | dc.w 120,44,195,29,131,93,163,59 749 | dc.w 153,16,197,73,105,39,163,91 750 | dc.w 133,1,177,63,117,31,170,78 751 | dc.w 133,19,186,47,102,93,140,11 752 | dc.w 151,55,111,40,167,68,114,22 753 | dc.w 139,74,88,3,162,45,121,57 754 | dc.w 141,25,95,77,145,13,111,37 755 | dc.w 119,59,157,28,87,68,123,6 756 | dc.w 95,44,136,31,97,71,125,14 757 | dc.w 76,46,136,19,101,55,81,3 758 | dc.w 113,35,103,55,89,22,120,40 759 | dc.w 69,9,109,57,87,25,118,42 760 | dc.w 72,6,92,28,73,56,102,13 761 | dc.w 58,31,89,42,79,9,100,20 762 | dc.w 60,44,86,1,65,33,74,23 763 | dc.w 94,43,52,13,64,27,79,10 764 | dc.w 54,32,79,14,61,39,42,3 765 | dc.w 66,21,61,31,50,14,71,5 766 | dc.w 41,25,60,15,46,29,61,6 767 | dc.w 38,20,47,9,48,29,31,2 768 | dc.w 55,16,35,20,46,11,35,6 769 | dc.w 27,20,39,11,33,2,39,19 770 | dc.w 24,7,30,12,18,3,31,12 771 | dc.w 22,8,25,12,18,1,25,6 772 | dc.w 14,11,16,3,17,6,10,3 773 | dc.w 12,6,13,1,8,4,7,2 774 | dc.w 7,3,3,1,3,1,1,0 775 | ColPositionsE: 776 | 777 | SrcCols: 778 | dc.w 160,80,267,40,224,133,297,100 779 | dc.w 196,16,247,147,283,57,181,110 780 | dc.w 235,27,312,88,206,124,257,7 781 | dc.w 173,68,290,154,215,48,274,95 782 | dc.w 189,33,306,138,229,63,168,116 783 | dc.w 261,11,242,105,316,52,201,143 784 | dc.w 219,22,279,83,184,127,294,37 785 | dc.w 239,74,176,157,270,3,211,92 786 | dc.w 303,119,251,45,192,71,286,136 787 | dc.w 164,19,254,107,226,59,310,150 788 | dc.w 203,29,264,86,186,113,300,9 789 | dc.w 232,130,209,54,277,77,170,35 790 | dc.w 318,152,244,98,217,14,272,122 791 | dc.w 178,65,292,43,198,141,249,90 792 | dc.w 236,24,307,114,165,61,259,145 793 | dc.w 213,1,287,102,191,50,281,129 794 | dc.w 222,31,314,79,174,159,241,18 795 | dc.w 263,96,195,70,298,121,205,39 796 | dc.w 253,135,162,5,302,84,228,108 797 | dc.w 269,47,183,149,284,25,220,73 798 | dc.w 238,118,319,56,179,140,256,12 799 | dc.w 200,93,291,66,210,155,275,30 800 | dc.w 169,103,308,42,231,126,188,6 801 | dc.w 266,82,246,132,216,53,296,99 802 | dc.w 172,21,278,146,225,64,311,111 803 | dc.w 193,15,250,76,202,156,288,36 804 | dc.w 166,89,260,123,234,26,304,60 805 | dc.w 185,137,271,2,208,106,243,75 806 | dc.w 315,144,177,44,223,94,282,34 807 | dc.w 197,117,295,51,233,151,163,10 808 | dc.w 265,85,252,128,212,58,309,20 809 | dc.w 182,112,276,69,218,139,299,28 810 | dc.w 190,101,245,46,258,158,171,8 811 | dc.w 317,91,207,120,285,67,227,38 812 | dc.w 180,134,273,78,240,13,301,148 813 | dc.w 194,55,255,104,161,23,293,115 814 | dc.w 221,81,268,131,204,4,313,72 815 | dc.w 187,153,237,41,280,97,175,49 816 | dc.w 248,125,305,17,214,109,230,62 817 | dc.w 289,142,167,32,262,87,199,0 818 | SrcColsE: 819 | 820 | ******************************************************************************* 821 | SECTION FastBuffers,BSS 822 | ******************************************************************************* 823 | 824 | Cols: ds.w 16 825 | 826 | ******************************************************************************* 827 | SECTION ChipData,DATA_C 828 | ******************************************************************************* 829 | Image: 830 | incbin "test.raw" 831 | ; incbin "abstract.raw" 832 | 833 | ******************************************************************************* 834 | SECTION ChipBuffers,BSS_C 835 | ******************************************************************************* 836 | 837 | Screen1: ds.b SCREEN_B*2 838 | Screen2: ds.b SCREEN_B*2 839 | Copper1: ds.l CopperE-Copper 840 | Copper2: ds.l CopperE-Copper 841 | 842 | END 843 | -------------------------------------------------------------------------------- /src/timings/tables.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mnemonicGroups, 3 | Mnemonics as M, 4 | AddressingModes as O, 5 | addressingModeGroups as OG, 6 | Qualifiers, 7 | Mnemonic, 8 | Qualifier, 9 | AddressingMode, 10 | } from "../syntax"; 11 | import { Timing } from "."; 12 | 13 | const { BCC, DBCC, SCC, SHIFT } = mnemonicGroups; 14 | const { B, W, L } = Qualifiers; 15 | const RegList = [O.RegList, O.Dn, O.An]; 16 | 17 | export type TimingTable = [ 18 | Mnemonic[], 19 | (Qualifier | null)[], 20 | (AddressingMode | AddressingMode[])[], 21 | Timing[], 22 | Timing? 23 | ][]; 24 | 25 | // Effective Address Calculation Times: 26 | 27 | // prettier-ignore 28 | export const lookupTimes: Record = { 29 | // B/W L 30 | // Register: 31 | [O.Dn]: [[0, 0, 0], [0, 0, 0]], 32 | [O.An]: [[0, 0, 0], [0, 0, 0]], 33 | // Memory: 34 | [O.AnIndir]: [[4, 1, 0], [8, 2, 0]], 35 | [O.AnPostInc]: [[4, 1, 0], [8, 2, 0]], 36 | [O.AnPreDec]: [[6, 1, 0], [10, 2, 0]], 37 | [O.AnDisp]: [[8, 2, 0], [12, 3, 0]], 38 | [O.AnDispIx]: [[10, 2, 0], [14, 3, 0]], 39 | [O.AbsW]: [[8, 2, 0], [12, 3, 0]], 40 | [O.AbsL]: [[12, 3, 0], [16, 4, 0]], 41 | [O.PcDisp]: [[8, 2, 0], [12, 3, 0]], 42 | [O.PcDispIx]: [[10, 2, 0], [14, 3, 0]], 43 | // Immediate: 44 | [O.Imm]: [[4, 1, 0], [8, 2, 0]], 45 | }; 46 | 47 | // prettier-ignore 48 | export const baseTimes: TimingTable = [ 49 | // Mnemonics: Sizes: Operands: Timings: N Multipliers: 50 | //---------------------------------------------------------------------------------------------------------------- 51 | // MOVE INSTRUCTION EXECUTION TIMES: 52 | // Move Byte and Word Instruction Execution Times: 53 | //---------------------------------------------------------------------------------------------------------------- 54 | [ [M.MOVE], [B, W], [O.Dn, O.Dn], [[4, 1, 0]] ], 55 | [ [M.MOVE, M.MOVEA], [B, W], [O.Dn, O.An], [[4, 1, 0]] ], 56 | [ [M.MOVE], [B, W], [O.Dn, O.AnIndir], [[8, 1, 1]] ], 57 | [ [M.MOVE], [B, W], [O.Dn, O.AnPostInc], [[8, 1, 1]] ], 58 | [ [M.MOVE], [B, W], [O.Dn, O.AnPreDec], [[8, 1, 1]] ], 59 | [ [M.MOVE], [B, W], [O.Dn, O.AnDisp], [[12, 2, 1]] ], 60 | [ [M.MOVE], [B, W], [O.Dn, O.AnDispIx], [[14, 2, 1]] ], 61 | [ [M.MOVE], [B, W], [O.Dn, O.AbsW], [[12, 2, 1]] ], 62 | [ [M.MOVE], [B, W], [O.Dn, O.AbsL], [[16, 3, 1]] ], 63 | //---------------------------------------------------------------------------------------------------------------- 64 | [ [M.MOVE], [B, W], [O.An, O.Dn], [[4, 1, 0]] ], 65 | [ [M.MOVE, M.MOVEA], [B, W], [O.An, O.An], [[4, 1, 0]] ], 66 | [ [M.MOVE], [B, W], [O.An, O.AnIndir], [[8, 1, 1]] ], 67 | [ [M.MOVE], [B, W], [O.An, O.AnPostInc], [[8, 1, 1]] ], 68 | [ [M.MOVE], [B, W], [O.An, O.AnPreDec], [[8, 1, 1]] ], 69 | [ [M.MOVE], [B, W], [O.An, O.AnDisp], [[12, 2, 1]] ], 70 | [ [M.MOVE], [B, W], [O.An, O.AnDispIx], [[14, 2, 1]] ], 71 | [ [M.MOVE], [B, W], [O.An, O.AbsW], [[12, 2, 1]] ], 72 | [ [M.MOVE], [B, W], [O.An, O.AbsL], [[16, 3, 1]] ], 73 | //---------------------------------------------------------------------------------------------------------------- 74 | [ [M.MOVE], [B, W], [O.AnIndir, O.Dn], [[8, 2, 0]] ], 75 | [ [M.MOVE, M.MOVEA], [B, W], [O.AnIndir, O.An], [[8, 2, 0]] ], 76 | [ [M.MOVE], [B, W], [O.AnIndir, O.AnIndir], [[12, 2, 1]] ], 77 | [ [M.MOVE], [B, W], [O.AnIndir, O.AnPostInc], [[12, 2, 1]] ], 78 | [ [M.MOVE], [B, W], [O.AnIndir, O.AnPreDec], [[12, 2, 1]] ], 79 | [ [M.MOVE], [B, W], [O.AnIndir, O.AnDisp], [[16, 3, 1]] ], 80 | [ [M.MOVE], [B, W], [O.AnIndir, O.AnDispIx], [[18, 3, 1]] ], 81 | [ [M.MOVE], [B, W], [O.AnIndir, O.AbsW], [[16, 3, 1]] ], 82 | [ [M.MOVE], [B, W], [O.AnIndir, O.AbsL], [[20, 4, 1]] ], 83 | //---------------------------------------------------------------------------------------------------------------- 84 | [ [M.MOVE], [B, W], [O.AnPostInc, O.Dn], [[8, 2, 0]] ], 85 | [ [M.MOVE, M.MOVEA], [B, W], [O.AnPostInc, O.An], [[8, 2, 0]] ], 86 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AnIndir], [[12, 2, 1]] ], 87 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AnPostInc], [[12, 2, 1]] ], 88 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AnPreDec], [[12, 2, 1]] ], 89 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AnDisp], [[16, 3, 1]] ], 90 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AnDispIx], [[18, 3, 1]] ], 91 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AbsW], [[16, 3, 1]] ], 92 | [ [M.MOVE], [B, W], [O.AnPostInc, O.AbsL], [[20, 4, 1]] ], 93 | //---------------------------------------------------------------------------------------------------------------- 94 | [ [M.MOVE], [B, W], [O.AnPreDec, O.Dn], [[10, 2, 0]] ], 95 | [ [M.MOVE, M.MOVEA], [B, W], [O.AnPreDec, O.An], [[10, 2, 0]] ], 96 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AnIndir], [[14, 2, 1]] ], 97 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AnPostInc], [[14, 2, 1]] ], 98 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AnPreDec], [[14, 2, 1]] ], 99 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AnDisp], [[18, 3, 1]] ], 100 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AnDispIx], [[20, 3, 1]] ], 101 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AbsW], [[18, 3, 1]] ], 102 | [ [M.MOVE], [B, W], [O.AnPreDec, O.AbsL], [[22, 4, 1]] ], 103 | //---------------------------------------------------------------------------------------------------------------- 104 | [ [M.MOVE], [B, W], [O.AnDisp, O.Dn], [[12, 3, 0]] ], 105 | [ [M.MOVE, M.MOVEA], [B, W], [O.AnDisp, O.An], [[12, 3, 0]] ], 106 | [ [M.MOVE], [B, W], [O.AnDisp, O.AnIndir], [[16, 3, 1]] ], 107 | [ [M.MOVE], [B, W], [O.AnDisp, O.AnPostInc], [[16, 3, 1]] ], 108 | [ [M.MOVE], [B, W], [O.AnDisp, O.AnPreDec], [[16, 3, 1]] ], 109 | [ [M.MOVE], [B, W], [O.AnDisp, O.AnDisp], [[20, 4, 1]] ], 110 | [ [M.MOVE], [B, W], [O.AnDisp, O.AnDispIx], [[22, 4, 1]] ], 111 | [ [M.MOVE], [B, W], [O.AnDisp, O.AbsW], [[20, 4, 1]] ], 112 | [ [M.MOVE], [B, W], [O.AnDisp, O.AbsL], [[24, 5, 1]] ], 113 | //---------------------------------------------------------------------------------------------------------------- 114 | [ [M.MOVE], [B, W], [O.AnDispIx, O.Dn], [[14, 3, 0]] ], 115 | [ [M.MOVE, M.MOVEA], [B, W], [O.AnDispIx, O.An], [[14, 3, 0]] ], 116 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AnIndir], [[18, 3, 1]] ], 117 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AnPostInc], [[18, 3, 1]] ], 118 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AnPreDec], [[18, 3, 1]] ], 119 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AnDisp], [[22, 4, 1]] ], 120 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AnDispIx], [[24, 4, 1]] ], 121 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AbsW], [[22, 4, 1]] ], 122 | [ [M.MOVE], [B, W], [O.AnDispIx, O.AbsL], [[26, 5, 1]] ], 123 | //---------------------------------------------------------------------------------------------------------------- 124 | [ [M.MOVE], [B, W], [O.AbsW, O.Dn], [[12, 3, 0]] ], 125 | [ [M.MOVE, M.MOVEA], [B, W], [O.AbsW, O.An], [[12, 3, 0]] ], 126 | [ [M.MOVE], [B, W], [O.AbsW, O.AnIndir], [[16, 3, 1]] ], 127 | [ [M.MOVE], [B, W], [O.AbsW, O.AnPostInc], [[16, 3, 1]] ], 128 | [ [M.MOVE], [B, W], [O.AbsW, O.AnPreDec], [[16, 3, 1]] ], 129 | [ [M.MOVE], [B, W], [O.AbsW, O.AnDisp], [[20, 4, 1]] ], 130 | [ [M.MOVE], [B, W], [O.AbsW, O.AnDispIx], [[22, 4, 1]] ], 131 | [ [M.MOVE], [B, W], [O.AbsW, O.AbsW], [[20, 4, 1]] ], 132 | [ [M.MOVE], [B, W], [O.AbsW, O.AbsL], [[24, 5, 1]] ], 133 | //---------------------------------------------------------------------------------------------------------------- 134 | [ [M.MOVE], [B, W], [O.AbsL, O.Dn], [[16, 4, 0]] ], 135 | [ [M.MOVE, M.MOVEA], [B, W], [O.AbsL, O.An], [[16, 4, 0]] ], 136 | [ [M.MOVE], [B, W], [O.AbsL, O.AnIndir], [[20, 4, 1]] ], 137 | [ [M.MOVE], [B, W], [O.AbsL, O.AnPostInc], [[20, 4, 1]] ], 138 | [ [M.MOVE], [B, W], [O.AbsL, O.AnPreDec], [[20, 4, 1]] ], 139 | [ [M.MOVE], [B, W], [O.AbsL, O.AnDisp], [[24, 5, 1]] ], 140 | [ [M.MOVE], [B, W], [O.AbsL, O.AnDispIx], [[26, 5, 1]] ], 141 | [ [M.MOVE], [B, W], [O.AbsL, O.AbsW], [[24, 5, 1]] ], 142 | [ [M.MOVE], [B, W], [O.AbsL, O.AbsL], [[28, 6, 1]] ], 143 | //---------------------------------------------------------------------------------------------------------------- 144 | [ [M.MOVE], [B, W], [O.PcDisp, O.Dn], [[12, 3, 0]] ], 145 | [ [M.MOVE, M.MOVEA], [B, W], [O.PcDisp, O.An], [[12, 3, 0]] ], 146 | [ [M.MOVE], [B, W], [O.PcDisp, O.AnIndir], [[16, 3, 1]] ], 147 | [ [M.MOVE], [B, W], [O.PcDisp, O.AnPostInc], [[16, 3, 1]] ], 148 | [ [M.MOVE], [B, W], [O.PcDisp, O.AnPreDec], [[16, 3, 1]] ], 149 | [ [M.MOVE], [B, W], [O.PcDisp, O.AnDisp], [[20, 4, 1]] ], 150 | [ [M.MOVE], [B, W], [O.PcDisp, O.AnDispIx], [[22, 4, 1]] ], 151 | [ [M.MOVE], [B, W], [O.PcDisp, O.AbsW], [[20, 4, 1]] ], 152 | [ [M.MOVE], [B, W], [O.PcDisp, O.AbsL], [[24, 5, 1]] ], 153 | //---------------------------------------------------------------------------------------------------------------- 154 | [ [M.MOVE], [B, W], [O.PcDispIx, O.Dn], [[14, 3, 0]] ], 155 | [ [M.MOVE, M.MOVEA], [B, W], [O.PcDispIx, O.An], [[14, 3, 0]] ], 156 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AnIndir], [[18, 3, 1]] ], 157 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AnPostInc], [[18, 3, 1]] ], 158 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AnPreDec], [[18, 3, 1]] ], 159 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AnDisp], [[22, 4, 1]] ], 160 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AnDispIx], [[24, 4, 1]] ], 161 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AbsW], [[22, 4, 1]] ], 162 | [ [M.MOVE], [B, W], [O.PcDispIx, O.AbsL], [[26, 5, 1]] ], 163 | //---------------------------------------------------------------------------------------------------------------- 164 | [ [M.MOVE], [B, W], [O.Imm, O.Dn], [[8, 2, 0]] ], 165 | [ [M.MOVE, M.MOVEA], [B, W], [O.Imm, O.An], [[8, 2, 0]] ], 166 | [ [M.MOVE], [B, W], [O.Imm, O.AnIndir], [[12, 2, 1]] ], 167 | [ [M.MOVE], [B, W], [O.Imm, O.AnPostInc], [[12, 2, 1]] ], 168 | [ [M.MOVE], [B, W], [O.Imm, O.AnPreDec], [[12, 2, 1]] ], 169 | [ [M.MOVE], [B, W], [O.Imm, O.AnDisp], [[16, 3, 1]] ], 170 | [ [M.MOVE], [B, W], [O.Imm, O.AnDispIx], [[18, 3, 1]] ], 171 | [ [M.MOVE], [B, W], [O.Imm, O.AbsW], [[16, 3, 1]] ], 172 | [ [M.MOVE], [B, W], [O.Imm, O.AbsL], [[20, 4, 1]] ], 173 | //---------------------------------------------------------------------------------------------------------------- 174 | // Move Long Instruction Execution Times: 175 | //---------------------------------------------------------------------------------------------------------------- 176 | [ [M.MOVE], [L], [O.Dn, O.Dn], [[4, 1, 0]] ], 177 | [ [M.MOVE, M.MOVEA], [L], [O.Dn, O.An], [[4, 1, 0]] ], 178 | [ [M.MOVE], [L], [O.Dn, O.AnIndir], [[12, 1, 2]] ], 179 | [ [M.MOVE], [L], [O.Dn, O.AnPostInc], [[12, 1, 2]] ], 180 | [ [M.MOVE], [L], [O.Dn, O.AnPreDec], [[12, 1, 2]] ], 181 | [ [M.MOVE], [L], [O.Dn, O.AnDisp], [[16, 2, 2]] ], 182 | [ [M.MOVE], [L], [O.Dn, O.AnDispIx], [[18, 2, 2]] ], 183 | [ [M.MOVE], [L], [O.Dn, O.AbsW], [[16, 2, 2]] ], 184 | [ [M.MOVE], [L], [O.Dn, O.AbsL], [[20, 3, 2]] ], 185 | //---------------------------------------------------------------------------------------------------------------- 186 | [ [M.MOVE], [L], [O.An, O.Dn], [[4, 1, 0]] ], 187 | [ [M.MOVE, M.MOVEA], [L], [O.An, O.An], [[4, 1, 0]] ], 188 | [ [M.MOVE], [L], [O.An, O.AnIndir], [[12, 1, 2]] ], 189 | [ [M.MOVE], [L], [O.An, O.AnPostInc], [[12, 1, 2]] ], 190 | [ [M.MOVE], [L], [O.An, O.AnPreDec], [[12, 1, 2]] ], 191 | [ [M.MOVE], [L], [O.An, O.AnDisp], [[16, 2, 2]] ], 192 | [ [M.MOVE], [L], [O.An, O.AnDispIx], [[18, 2, 2]] ], 193 | [ [M.MOVE], [L], [O.An, O.AbsW], [[16, 2, 2]] ], 194 | [ [M.MOVE], [L], [O.An, O.AbsL], [[20, 3, 2]] ], 195 | //---------------------------------------------------------------------------------------------------------------- 196 | [ [M.MOVE], [L], [O.AnIndir, O.Dn], [[12, 3, 0]] ], 197 | [ [M.MOVE, M.MOVEA], [L], [O.AnIndir, O.An], [[12, 3, 0]] ], 198 | [ [M.MOVE], [L], [O.AnIndir, O.AnIndir], [[20, 3, 2]] ], 199 | [ [M.MOVE], [L], [O.AnIndir, O.AnPostInc], [[20, 3, 2]] ], 200 | [ [M.MOVE], [L], [O.AnIndir, O.AnPreDec], [[20, 3, 2]] ], 201 | [ [M.MOVE], [L], [O.AnIndir, O.AnDisp], [[24, 4, 2]] ], 202 | [ [M.MOVE], [L], [O.AnIndir, O.AnDispIx], [[26, 4, 2]] ], 203 | [ [M.MOVE], [L], [O.AnIndir, O.AbsW], [[24, 4, 2]] ], 204 | [ [M.MOVE], [L], [O.AnIndir, O.AbsL], [[28, 5, 2]] ], 205 | //---------------------------------------------------------------------------------------------------------------- 206 | [ [M.MOVE], [L], [O.AnPostInc, O.Dn], [[12, 3, 0]] ], 207 | [ [M.MOVE, M.MOVEA], [L], [O.AnPostInc, O.An], [[12, 3, 0]] ], 208 | [ [M.MOVE], [L], [O.AnPostInc, O.AnIndir], [[20, 3, 2]] ], 209 | [ [M.MOVE], [L], [O.AnPostInc, O.AnPostInc], [[20, 3, 2]] ], 210 | [ [M.MOVE], [L], [O.AnPostInc, O.AnPreDec], [[20, 3, 2]] ], 211 | [ [M.MOVE], [L], [O.AnPostInc, O.AnDisp], [[24, 4, 2]] ], 212 | [ [M.MOVE], [L], [O.AnPostInc, O.AnDispIx], [[26, 4, 2]] ], 213 | [ [M.MOVE], [L], [O.AnPostInc, O.AbsW], [[24, 4, 2]] ], 214 | [ [M.MOVE], [L], [O.AnPostInc, O.AbsL], [[28, 5, 2]] ], 215 | //---------------------------------------------------------------------------------------------------------------- 216 | [ [M.MOVE], [L], [O.AnPreDec, O.Dn], [[14, 3, 0]] ], 217 | [ [M.MOVE, M.MOVEA], [L], [O.AnPreDec, O.An], [[14, 3, 0]] ], 218 | [ [M.MOVE], [L], [O.AnPreDec, O.AnIndir], [[22, 3, 2]] ], 219 | [ [M.MOVE], [L], [O.AnPreDec, O.AnPostInc], [[22, 3, 2]] ], 220 | [ [M.MOVE], [L], [O.AnPreDec, O.AnPreDec], [[22, 3, 2]] ], 221 | [ [M.MOVE], [L], [O.AnPreDec, O.AnDisp], [[26, 4, 2]] ], 222 | [ [M.MOVE], [L], [O.AnPreDec, O.AnDispIx], [[28, 4, 2]] ], 223 | [ [M.MOVE], [L], [O.AnPreDec, O.AbsW], [[26, 4, 2]] ], 224 | [ [M.MOVE], [L], [O.AnPreDec, O.AbsL], [[30, 5, 2]] ], 225 | //---------------------------------------------------------------------------------------------------------------- 226 | [ [M.MOVE], [L], [O.AnDisp, O.Dn], [[16, 4, 0]] ], 227 | [ [M.MOVE, M.MOVEA], [L], [O.AnDisp, O.An], [[16, 4, 0]] ], 228 | [ [M.MOVE], [L], [O.AnDisp, O.AnIndir], [[24, 4, 2]] ], 229 | [ [M.MOVE], [L], [O.AnDisp, O.AnPostInc], [[24, 4, 2]] ], 230 | [ [M.MOVE], [L], [O.AnDisp, O.AnPreDec], [[24, 4, 2]] ], 231 | [ [M.MOVE], [L], [O.AnDisp, O.AnDisp], [[28, 5, 2]] ], 232 | [ [M.MOVE], [L], [O.AnDisp, O.AnDispIx], [[30, 5, 2]] ], 233 | [ [M.MOVE], [L], [O.AnDisp, O.AbsW], [[28, 5, 2]] ], 234 | [ [M.MOVE], [L], [O.AnDisp, O.AbsL], [[32, 6, 2]] ], 235 | //---------------------------------------------------------------------------------------------------------------- 236 | [ [M.MOVE], [L], [O.AnDispIx, O.Dn], [[18, 4, 0]] ], 237 | [ [M.MOVE, M.MOVEA], [L], [O.AnDispIx, O.An], [[18, 4, 0]] ], 238 | [ [M.MOVE], [L], [O.AnDispIx, O.AnIndir], [[26, 4, 2]] ], 239 | [ [M.MOVE], [L], [O.AnDispIx, O.AnPostInc], [[26, 4, 2]] ], 240 | [ [M.MOVE], [L], [O.AnDispIx, O.AnPreDec], [[26, 4, 2]] ], 241 | [ [M.MOVE], [L], [O.AnDispIx, O.AnDisp], [[30, 5, 2]] ], 242 | [ [M.MOVE], [L], [O.AnDispIx, O.AnDispIx], [[32, 5, 2]] ], 243 | [ [M.MOVE], [L], [O.AnDispIx, O.AbsW], [[30, 5, 2]] ], 244 | [ [M.MOVE], [L], [O.AnDispIx, O.AbsL], [[34, 6, 2]] ], 245 | //---------------------------------------------------------------------------------------------------------------- 246 | [ [M.MOVE], [L], [O.AbsW, O.Dn], [[16, 4, 0]] ], 247 | [ [M.MOVE, M.MOVEA], [L], [O.AbsW, O.An], [[16, 4, 0]] ], 248 | [ [M.MOVE], [L], [O.AbsW, O.AnIndir], [[24, 4, 2]] ], 249 | [ [M.MOVE], [L], [O.AbsW, O.AnPostInc], [[24, 4, 2]] ], 250 | [ [M.MOVE], [L], [O.AbsW, O.AnPreDec], [[24, 4, 2]] ], 251 | [ [M.MOVE], [L], [O.AbsW, O.AnDisp], [[28, 5, 2]] ], 252 | [ [M.MOVE], [L], [O.AbsW, O.AnDispIx], [[30, 5, 2]] ], 253 | [ [M.MOVE], [L], [O.AbsW, O.AbsW], [[28, 5, 2]] ], 254 | [ [M.MOVE], [L], [O.AbsW, O.AbsL], [[32, 6, 2]] ], 255 | //---------------------------------------------------------------------------------------------------------------- 256 | [ [M.MOVE], [L], [O.AbsL, O.Dn], [[20, 5, 0]] ], 257 | [ [M.MOVE, M.MOVEA], [L], [O.AbsL, O.An], [[20, 5, 0]] ], 258 | [ [M.MOVE], [L], [O.AbsL, O.AnIndir], [[28, 5, 2]] ], 259 | [ [M.MOVE], [L], [O.AbsL, O.AnPostInc], [[28, 5, 2]] ], 260 | [ [M.MOVE], [L], [O.AbsL, O.AnPreDec], [[28, 5, 2]] ], 261 | [ [M.MOVE], [L], [O.AbsL, O.AnDisp], [[32, 6, 2]] ], 262 | [ [M.MOVE], [L], [O.AbsL, O.AnDispIx], [[34, 6, 2]] ], 263 | [ [M.MOVE], [L], [O.AbsL, O.AbsW], [[32, 6, 2]] ], 264 | [ [M.MOVE], [L], [O.AbsL, O.AbsL], [[36, 7, 2]] ], 265 | //---------------------------------------------------------------------------------------------------------------- 266 | [ [M.MOVE], [L], [O.PcDisp, O.Dn], [[16, 4, 0]] ], 267 | [ [M.MOVE, M.MOVEA], [L], [O.PcDisp, O.An], [[16, 4, 0]] ], 268 | [ [M.MOVE], [L], [O.PcDisp, O.AnIndir], [[24, 4, 2]] ], 269 | [ [M.MOVE], [L], [O.PcDisp, O.AnPostInc], [[24, 4, 2]] ], 270 | [ [M.MOVE], [L], [O.PcDisp, O.AnPreDec], [[24, 4, 2]] ], 271 | [ [M.MOVE], [L], [O.PcDisp, O.AnDisp], [[28, 5, 2]] ], 272 | [ [M.MOVE], [L], [O.PcDisp, O.AnDispIx], [[30, 5, 2]] ], 273 | [ [M.MOVE], [L], [O.PcDisp, O.AbsW], [[28, 5, 2]] ], 274 | [ [M.MOVE], [L], [O.PcDisp, O.AbsL], [[32, 6, 2]] ], 275 | //---------------------------------------------------------------------------------------------------------------- 276 | [ [M.MOVE], [L], [O.PcDispIx, O.Dn], [[18, 4, 0]] ], 277 | [ [M.MOVE, M.MOVEA], [L], [O.PcDispIx, O.An], [[18, 4, 0]] ], 278 | [ [M.MOVE], [L], [O.PcDispIx, O.AnIndir], [[26, 4, 2]] ], 279 | [ [M.MOVE], [L], [O.PcDispIx, O.AnPostInc], [[26, 4, 2]] ], 280 | [ [M.MOVE], [L], [O.PcDispIx, O.AnPreDec], [[26, 4, 2]] ], 281 | [ [M.MOVE], [L], [O.PcDispIx, O.AnDisp], [[30, 5, 2]] ], 282 | [ [M.MOVE], [L], [O.PcDispIx, O.AnDispIx], [[32, 5, 2]] ], 283 | [ [M.MOVE], [L], [O.PcDispIx, O.AbsW], [[30, 5, 2]] ], 284 | [ [M.MOVE], [L], [O.PcDispIx, O.AbsL], [[34, 6, 2]] ], 285 | //---------------------------------------------------------------------------------------------------------------- 286 | [ [M.MOVE], [L], [O.Imm, O.Dn], [[12, 3, 0]] ], 287 | [ [M.MOVE, M.MOVEA], [L], [O.Imm, O.An], [[12, 3, 0]] ], 288 | [ [M.MOVE], [L], [O.Imm, O.AnIndir], [[20, 3, 2]] ], 289 | [ [M.MOVE], [L], [O.Imm, O.AnPostInc], [[20, 3, 2]] ], 290 | [ [M.MOVE], [L], [O.Imm, O.AnPreDec], [[20, 3, 2]] ], 291 | [ [M.MOVE], [L], [O.Imm, O.AnDisp], [[24, 4, 2]] ], 292 | [ [M.MOVE], [L], [O.Imm, O.AnDispIx], [[26, 4, 2]] ], 293 | [ [M.MOVE], [L], [O.Imm, O.AbsW], [[24, 4, 2]] ], 294 | [ [M.MOVE], [L], [O.Imm, O.AbsL], [[28, 5, 2]] ], 295 | 296 | //---------------------------------------------------------------------------------------------------------------- 297 | // STANDARD INSTRUCTION EXECUTION TIMES: 298 | //---------------------------------------------------------------------------------------------------------------- 299 | [ [M.ADD, M.SUB], [B, W], [OG.EA, O.An], [[8, 1, 0]] ], 300 | [ [M.ADDA, M.SUBA], [W], [OG.EA, O.An], [[8, 1, 0]] ], 301 | [ [M.ADD, M.SUB], [B, W], [OG.EA, O.Dn], [[4, 1, 0]] ], 302 | [ [M.ADD, M.SUB], [B, W], [O.Dn, OG.M], [[8, 1, 1]] ], 303 | [ [M.ADD, M.ADDA, M.SUB, M.SUBA], [L], [OG.M, O.An], [[6, 1, 0]] ], 304 | [ [M.ADD, M.SUB], [L], [OG.M, O.Dn], [[6, 1, 0]] ], 305 | [ [M.ADD, M.ADDA, M.SUB, M.SUBA], [L], [OG.DI, O.An], [[8, 1, 0]] ], 306 | [ [M.ADD, M.SUB], [L], [OG.DI, O.Dn], [[8, 1, 0]] ], 307 | [ [M.ADD, M.SUB], [L], [O.Dn, OG.M], [[12, 1, 2]] ], 308 | //---------------------------------------------------------------------------------------------------------------- 309 | [ [M.AND, M.OR], [B, W], [OG.EA, O.Dn], [[4, 1, 0]] ], 310 | [ [M.AND, M.OR], [B, W], [O.Dn, OG.M], [[8, 1, 1]] ], 311 | [ [M.AND, M.OR], [L], [OG.M, O.Dn], [[6, 1, 0]] ], 312 | [ [M.AND, M.OR], [L], [OG.DI, O.Dn], [[8, 1, 0]] ], 313 | [ [M.AND, M.OR], [L], [O.Dn, OG.M], [[12, 1, 2]] ], 314 | //---------------------------------------------------------------------------------------------------------------- 315 | [ [M.EOR], [B, W], [O.Dn, O.Dn], [[4, 1, 0]] ], 316 | [ [M.EOR], [B, W], [O.Dn, OG.M], [[8, 1, 1]] ], 317 | [ [M.EOR], [L], [O.Dn, O.Dn], [[8, 1, 0]] ], 318 | [ [M.EOR], [L], [O.Dn, OG.M], [[12, 1, 2]] ], 319 | //---------------------------------------------------------------------------------------------------------------- 320 | [ [M.CMP], [B, W], [OG.EA, O.An], [[6, 1, 0]] ], 321 | [ [M.CMPA], [W], [OG.EA, O.An], [[6, 1, 0]] ], 322 | [ [M.CMP, M.CMPA], [L], [OG.EA, O.An], [[6, 1, 0]] ], 323 | [ [M.CMP], [L], [OG.EA, O.Dn], [[6, 1, 0]] ], 324 | [ [M.CMP], [B, W], [OG.EA, O.Dn], [[4, 1, 0]] ], 325 | //---------------------------------------------------------------------------------------------------------------- 326 | [ [M.DIVS], [W], [OG.EA, O.Dn], [[158, 1, 0]] ], 327 | [ [M.DIVU], [W], [OG.EA, O.Dn], [[140, 1, 0]] ], 328 | [ [M.MULS, M.MULU], [W], [OG.EA, O.Dn], [[38, 1, 0]], [2, 0, 0]], 329 | //---------------------------------------------------------------------------------------------------------------- 330 | // Immediate Instruction Execution Times: 331 | //---------------------------------------------------------------------------------------------------------------- 332 | [ [M.ADD, M.ADDI, M.SUB, M.SUBI], [B, W], [O.Imm, O.Dn], [[8, 2, 0]] ], 333 | [ [M.ADD, M.ADDI, M.SUB, M.SUBI], [B, W], [O.Imm, OG.M], [[12, 2, 1]] ], 334 | [ [M.ADD, M.ADDI, M.SUB, M.SUBI], [L], [O.Imm, O.Dn], [[16, 3, 0]] ], 335 | [ [M.ADD, M.ADDI, M.SUB, M.SUBI], [L], [O.Imm, OG.M], [[20, 3, 2]] ], 336 | //---------------------------------------------------------------------------------------------------------------- 337 | [ [M.ADDQ, M.SUBQ], [B, W], [O.Imm, O.Dn], [[4, 1, 0]] ], 338 | [ [M.ADDQ, M.SUBQ], [B, W], [O.Imm, O.An], [[8, 1, 0]] ], 339 | [ [M.ADDQ, M.SUBQ], [B, W], [O.Imm, OG.M], [[8, 1, 1]] ], 340 | [ [M.ADDQ, M.SUBQ], [L], [O.Imm, O.Dn], [[8, 1, 0]] ], 341 | [ [M.ADDQ, M.SUBQ], [L], [O.Imm, O.An], [[8, 1, 0]] ], 342 | [ [M.ADDQ, M.SUBQ], [L], [O.Imm, OG.M], [[12, 1, 2]] ], 343 | //---------------------------------------------------------------------------------------------------------------- 344 | [ [M.AND, M.ANDI, M.OR, M.ORI], [B, W], [O.Imm, O.Dn], [[8, 2, 0]] ], 345 | [ [M.AND, M.ANDI, M.OR, M.ORI], [B, W], [O.Imm, OG.M], [[12, 2, 1]] ], 346 | [ [M.AND, M.ANDI, M.OR, M.ORI], [L], [O.Imm, O.Dn], [[16, 3, 0]] ], 347 | [ [M.AND, M.ANDI, M.OR, M.ORI], [L], [O.Imm, OG.M], [[20, 3, 2]] ], 348 | //---------------------------------------------------------------------------------------------------------------- 349 | [ [M.CMP, M.CMPI], [B, W], [O.Imm, O.Dn], [[8, 2, 0]] ], 350 | [ [M.CMP, M.CMPI], [B, W], [O.Imm, OG.M], [[8, 2, 0]] ], 351 | [ [M.CMP, M.CMPI], [L], [O.Imm, O.Dn], [[14, 3, 0]] ], 352 | [ [M.CMP, M.CMPI], [L], [O.Imm, OG.M], [[12, 3, 0]] ], 353 | //---------------------------------------------------------------------------------------------------------------- 354 | [ [M.EOR, M.EORI], [B, W], [O.Imm, O.Dn], [[8, 2, 0]] ], 355 | [ [M.EOR, M.EORI], [B, W], [O.Imm, OG.M], [[12, 2, 1]] ], 356 | [ [M.EOR, M.EORI], [L], [O.Imm, O.Dn], [[16, 3, 0]] ], 357 | [ [M.EOR, M.EORI], [L], [O.Imm, OG.M], [[20, 3, 2]] ], 358 | //---------------------------------------------------------------------------------------------------------------- 359 | [ [M.MOVEQ], [L], [O.Imm, O.Dn], [[4, 1, 0]] ], 360 | 361 | //---------------------------------------------------------------------------------------------------------------- 362 | // SINGLE OPERAND INSTRUCTION EXECUTION TIMES: Taken Not taken: 363 | //---------------------------------------------------------------------------------------------------------------- 364 | [ [M.CLR, M.NOT, M.NEG], [B, W], [O.Dn], [[4, 1, 0]] ], 365 | [ [M.CLR, M.NOT, M.NEG], [B, W], [OG.M], [[8, 1, 1]] ], 366 | [ [M.CLR, M.NOT, M.NEG], [L], [O.Dn], [[6, 1, 0]] ], 367 | [ [M.CLR, M.NOT, M.NEG], [L], [OG.M], [[12, 1, 2]] ], 368 | //---------------------------------------------------------------------------------------------------------------- 369 | [ [M.NBCD], [B], [O.Dn], [[6, 1, 0]] ], 370 | [ [M.NBCD], [B], [OG.M], [[8, 1, 1]] ], 371 | //---------------------------------------------------------------------------------------------------------------- 372 | [ [M.NEGX], [B, W], [O.Dn], [[4, 1, 0]] ], 373 | [ [M.NEGX], [B, W], [OG.M], [[8, 1, 1]] ], 374 | [ [M.NEGX], [L], [O.Dn], [[6, 1, 0]] ], 375 | [ [M.NEGX], [L], [OG.M], [[12, 1, 2]] ], 376 | //---------------------------------------------------------------------------------------------------------------- 377 | [ SCC, [B], [O.Dn], [[4, 1, 0], [6, 1, 0]] ], 378 | [ SCC, [B], [OG.M], [[8, 1, 1], [8, 1, 1]] ], 379 | //---------------------------------------------------------------------------------------------------------------- 380 | [ [M.TAS], [B], [O.Dn], [[4, 1, 0]] ], 381 | [ [M.TAS], [B], [OG.M], [[10, 1, 1]] ], 382 | //---------------------------------------------------------------------------------------------------------------- 383 | [ [M.TST], [B, W, L], [O.Dn], [[4, 1, 0]] ], 384 | [ [M.TST], [B, W, L], [OG.M], [[4, 1, 0]] ], 385 | 386 | //---------------------------------------------------------------------------------------------------------------- 387 | // SHIFT/ROTATE INSTRUCTION EXECUTION TIMES: 388 | //---------------------------------------------------------------------------------------------------------------- 389 | [ SHIFT, [B, W], [O.Imm, O.Dn], [[6, 1, 0]], [2, 0, 0]], 390 | [ SHIFT, [B, W], [O.Imm, OG.M], [[8, 1, 1]], [2, 0, 0]], 391 | [ SHIFT, [B, W], [O.Dn, O.Dn], [[6, 1, 0]], [2, 0, 0]], 392 | [ SHIFT, [B, W], [O.Dn, OG.M], [[8, 1, 1]], [2, 0, 0]], 393 | [ SHIFT, [B, W], [O.Dn], [[8, 1, 0]], ], 394 | [ SHIFT, [B, W], [OG.M], [[8, 1, 1]], ], 395 | [ SHIFT, [L], [O.Imm, O.Dn], [[8, 1, 0]], [2, 0, 0]], 396 | [ SHIFT, [L], [O.Dn], [[10, 1, 0]], ], 397 | [ SHIFT, [L], [O.Dn, O.Dn], [[8, 1, 0]], [2, 0, 0]], 398 | 399 | //---------------------------------------------------------------------------------------------------------------- 400 | // BIT MANIPULATION INSTRUCTION EXECUTION TIMES: 401 | //---------------------------------------------------------------------------------------------------------------- 402 | [ [M.BCHG, M.BSET, M.BCLR], [B], [O.Dn, OG.M], [[8, 1, 1]] ], 403 | [ [M.BCHG, M.BSET, M.BCLR], [B], [O.Imm, OG.M], [[12, 2, 1]] ], 404 | [ [M.BCHG, M.BSET], [L], [O.Dn, O.Dn], [[8, 1, 0]] ], 405 | [ [M.BCHG, M.BSET], [L], [O.Imm, O.Dn], [[12, 2, 0]] ], 406 | [ [M.BCLR], [L], [O.Dn, O.Dn], [[10, 1, 0]] ], 407 | [ [M.BCLR], [L], [O.Imm, O.Dn], [[14, 2, 0]] ], 408 | //---------------------------------------------------------------------------------------------------------------- 409 | [ [M.BTST], [B], [O.Dn, OG.M], [[4, 1, 0]] ], 410 | [ [M.BTST], [B], [O.Imm, OG.M], [[8, 2, 0]] ], 411 | [ [M.BTST], [B], [O.Dn, O.Imm], [[10, 2, 0]] ], 412 | [ [M.BTST], [L], [O.Dn, O.Dn], [[6, 1, 0]] ], 413 | [ [M.BTST], [L], [O.Imm, O.Dn], [[10, 2, 0]] ], 414 | 415 | //---------------------------------------------------------------------------------------------------------------- 416 | // CONDITIONAL INSTRUCTION EXECUTION TIMES: Taken: Not taken: Expired: 417 | //---------------------------------------------------------------------------------------------------------------- 418 | [ BCC, [B], [O.AbsL], [[10, 2, 0], [8, 1, 0]] ], 419 | [ BCC, [W], [O.AbsL], [[10, 2, 0], [12, 2, 0]] ], 420 | //---------------------------------------------------------------------------------------------------------------- 421 | [ [M.BRA], [B, W], [O.AbsL], [[10, 2, 0]] ], 422 | [ [M.BSR], [B, W], [O.AbsL], [[18, 2, 2]] ], 423 | //---------------------------------------------------------------------------------------------------------------- 424 | [ DBCC, [W], [O.Dn, O.AbsL], [[10, 2, 0], [12, 2, 0], [14, 3, 0]] ], 425 | 426 | //---------------------------------------------------------------------------------------------------------------- 427 | // JMP, JSR, LEA, PEA, AND MOVEM INSTRUCTION EXECUTION TIMES: 428 | //---------------------------------------------------------------------------------------------------------------- 429 | [ [M.JMP], [null], [O.AnIndir], [[8, 2, 0]] ], 430 | [ [M.JMP], [null], [O.AnDisp], [[10, 2, 0]] ], 431 | [ [M.JMP], [null], [O.AnDispIx], [[14, 2, 0]] ], 432 | [ [M.JMP], [null], [O.AbsW], [[10, 2, 0]] ], 433 | [ [M.JMP], [null], [O.AbsL], [[12, 3, 0]] ], 434 | [ [M.JMP], [null], [O.PcDisp], [[10, 2, 0]] ], 435 | [ [M.JMP], [null], [O.PcDispIx], [[14, 2, 0]] ], 436 | //---------------------------------------------------------------------------------------------------------------- 437 | [ [M.JSR], [null], [O.AnIndir], [[16, 2, 2]] ], 438 | [ [M.JSR], [null], [O.AnDisp], [[18, 2, 2]] ], 439 | [ [M.JSR], [null], [O.AnDispIx], [[22, 2, 2]] ], 440 | [ [M.JSR], [null], [O.AbsW], [[18, 2, 2]] ], 441 | [ [M.JSR], [null], [O.AbsL], [[20, 3, 2]] ], 442 | [ [M.JSR], [null], [O.PcDisp], [[18, 2, 2]] ], 443 | [ [M.JSR], [null], [O.PcDispIx], [[22, 2, 2]] ], 444 | //---------------------------------------------------------------------------------------------------------------- 445 | [ [M.LEA], [L], [O.AnIndir, O.An], [[4, 1, 0]] ], 446 | [ [M.LEA], [L], [O.AnDisp, O.An], [[8, 2, 0]] ], 447 | [ [M.LEA], [L], [O.AnDispIx, O.An], [[12, 2, 0]] ], 448 | [ [M.LEA], [L], [O.AbsW, O.An], [[8, 2, 0]] ], 449 | [ [M.LEA], [L], [O.AbsL, O.An], [[12, 3, 0]] ], 450 | [ [M.LEA], [L], [O.PcDisp, O.An], [[8, 2, 0]] ], 451 | [ [M.LEA], [L], [O.PcDispIx, O.An], [[12, 2, 0]] ], 452 | //---------------------------------------------------------------------------------------------------------------- 453 | [ [M.PEA], [L], [O.AnIndir], [[12, 1, 2]] ], 454 | [ [M.PEA], [L], [O.AnDisp], [[16, 2, 2]] ], 455 | [ [M.PEA], [L], [O.AnDispIx], [[20, 2, 2]] ], 456 | [ [M.PEA], [L], [O.AbsW], [[16, 2, 2]] ], 457 | [ [M.PEA], [L], [O.AbsL], [[20, 3, 2]] ], 458 | [ [M.PEA], [L], [O.PcDisp], [[16, 2, 2]] ], 459 | [ [M.PEA], [L], [O.PcDispIx], [[20, 2, 2]] ], 460 | // M => R -------------------------------------------------------------------------------------------------------- 461 | [ [M.MOVEM], [W], [O.AnIndir, RegList], [[12, 3, 0]], [4, 1, 0]], 462 | [ [M.MOVEM], [W], [O.AnPostInc, RegList], [[12, 3, 0]], [4, 1, 0]], 463 | [ [M.MOVEM], [W], [O.AnDisp, RegList], [[16, 4, 0]], [4, 1, 0]], 464 | [ [M.MOVEM], [W], [O.AnDispIx, RegList], [[18, 4, 0]], [4, 1, 0]], 465 | [ [M.MOVEM], [W], [O.AbsW, RegList], [[16, 4, 0]], [4, 1, 0]], 466 | [ [M.MOVEM], [W], [O.AbsL, RegList], [[20, 5, 0]], [4, 1, 0]], 467 | [ [M.MOVEM], [W], [O.PcDisp, RegList], [[16, 4, 0]], [4, 1, 0]], 468 | [ [M.MOVEM], [W], [O.PcDispIx, RegList], [[18, 4, 0]], [4, 1, 0]], 469 | [ [M.MOVEM], [L], [O.AnIndir, RegList], [[12, 3, 0]], [8, 2, 0]], 470 | [ [M.MOVEM], [L], [O.AnPostInc, RegList], [[12, 3, 0]], [8, 2, 0]], 471 | [ [M.MOVEM], [L], [O.AnDisp, RegList], [[16, 4, 0]], [8, 2, 0]], 472 | [ [M.MOVEM], [L], [O.AnDispIx, RegList], [[18, 4, 0]], [8, 2, 0]], 473 | [ [M.MOVEM], [L], [O.AbsW, RegList], [[16, 4, 0]], [8, 2, 0]], 474 | [ [M.MOVEM], [L], [O.AbsL, RegList], [[20, 5, 0]], [8, 2, 0]], 475 | [ [M.MOVEM], [L], [O.PcDisp, RegList], [[16, 4, 0]], [8, 2, 0]], 476 | [ [M.MOVEM], [L], [O.PcDispIx, RegList], [[18, 4, 0]], [8, 2, 0]], 477 | // R => M -------------------------------------------------------------------------------------------------------- 478 | [ [M.MOVEM], [W], [RegList, O.AnIndir], [[8, 2, 0]], [4, 0, 1]], 479 | [ [M.MOVEM], [W], [RegList, O.AnPreDec], [[8, 2, 0]], [4, 0, 1]], 480 | [ [M.MOVEM], [W], [RegList, O.AnDisp], [[12, 3, 0]], [4, 0, 1]], 481 | [ [M.MOVEM], [W], [RegList, O.AnDispIx], [[14, 3, 0]], [4, 0, 1]], 482 | [ [M.MOVEM], [W], [RegList, O.AbsW], [[12, 3, 0]], [4, 0, 1]], 483 | [ [M.MOVEM], [W], [RegList, O.AbsL], [[16, 4, 0]], [4, 0, 1]], 484 | [ [M.MOVEM], [L], [RegList, O.AnIndir], [[8, 2, 0]], [8, 0, 2]], 485 | [ [M.MOVEM], [L], [RegList, O.AnPreDec], [[8, 2, 0]], [8, 0, 2]], 486 | [ [M.MOVEM], [L], [RegList, O.AnDisp], [[12, 3, 0]], [8, 0, 2]], 487 | [ [M.MOVEM], [L], [RegList, O.AnDispIx], [[14, 3, 0]], [8, 0, 2]], 488 | [ [M.MOVEM], [L], [RegList, O.AbsW], [[12, 3, 0]], [8, 0, 2]], 489 | [ [M.MOVEM], [L], [RegList, O.AbsL], [[16, 4, 0]], [8, 0, 2]], 490 | 491 | //---------------------------------------------------------------------------------------------------------------- 492 | // MULTIPRECISION INSTRUCTION EXECUTION TIMES: 493 | //---------------------------------------------------------------------------------------------------------------- 494 | [ [M.ADDX, M.SUBX], [B, W], [O.Dn, O.Dn], [[4, 1, 0]] ], 495 | [ [M.ADDX, M.SUBX], [B, W], [O.AnPreDec, O.AnPreDec], [[18, 3, 1]] ], 496 | [ [M.ADDX, M.SUBX], [L], [O.Dn, O.Dn], [[8, 1, 0]] ], 497 | [ [M.ADDX, M.SUBX], [L], [O.AnPreDec, O.AnPreDec], [[30, 5, 2]] ], 498 | //---------------------------------------------------------------------------------------------------------------- 499 | [ [M.CMPM], [B, W], [O.AnPostInc, O.AnPostInc], [[12, 3, 0]] ], 500 | [ [M.CMPM], [L], [O.AnPostInc, O.AnPostInc], [[20, 5, 0]] ], 501 | //---------------------------------------------------------------------------------------------------------------- 502 | [ [M.ABCD, M.SBCD], [B], [O.Dn, O.Dn], [[6, 1, 0]] ], 503 | [ [M.ABCD, M.SBCD], [B], [O.AnPreDec, O.AnPreDec], [[18, 3, 1]] ], 504 | 505 | //---------------------------------------------------------------------------------------------------------------- 506 | // MISCELLANEOUS INSTRUCTION EXECUTION TIMES: No trap: Trap >: Trap <: 507 | //---------------------------------------------------------------------------------------------------------------- 508 | [ [M.CHK], [W,L], [OG.EA, O.Dn], [[10, 1, 0], [38, 5, 3], [40, 5, 3]] ], 509 | 510 | [ [M.EXG], [L], [O.Dn,O.Dn], [[6, 1, 0]] ], 511 | [ [M.EXG], [L], [O.Dn, O.An], [[6, 1, 0]] ], 512 | [ [M.EXG], [L], [O.An,O.Dn], [[6, 1, 0]] ], 513 | [ [M.EXG], [L], [O.An, O.An], [[6, 1, 0]] ], 514 | [ [M.EXT], [W, L], [O.Dn], [[4, 1, 0]] ], 515 | [ [M.LINK], [W, L], [O.An,O.Imm], [[16, 2, 2]] ], 516 | [ [M.NOP], [null], [], [[4, 1, 0]] ], 517 | [ [M.RESET], [null], [], [[132, 1, 0]] ], 518 | [ [M.RTE], [null], [], [[20, 5, 0]] ], 519 | [ [M.RTR], [null], [], [[20, 5, 0]] ], 520 | [ [M.RTS], [null], [], [[16, 4, 0]] ], 521 | [ [M.STOP], [null], [O.Imm], [[4, 1, 0]] ], 522 | [ [M.SWAP], [W], [O.Dn], [[4, 1, 0]] ], 523 | [ [M.TRAP], [null], [O.Imm], [[34, 4, 3]] ], 524 | [ [M.TRAPV], [null], [], [[4, 1, 0], [34, 5, 3]] ], 525 | [ [M.UNLK], [null], [O.An], [[12, 3, 0]] ], 526 | [ [M.ILLEGAL], [null], [], [[34, 4, 3]] ], 527 | 528 | //---------------------------------------------------------------------------------------------------------------- 529 | // CCR/SR 530 | //---------------------------------------------------------------------------------------------------------------- 531 | [ [M.AND, M.ANDI, M.EOR, M.EORI], [B, W], [O.Imm, O.CCR], [[20, 3, 0]] ], 532 | [ [M.OR, M.ORI], [B, W], [O.Imm, O.CCR], [[20, 3, 0]] ], 533 | [ [M.AND, M.ANDI, M.EOR, M.EORI], [B, W], [O.Imm, O.SR], [[20, 3, 0]] ], 534 | [ [M.OR, M.ORI], [B, W], [O.Imm, O.SR], [[20, 3, 0]] ], 535 | [ [M.MOVE], [W], [O.SR, OG.EA], [[6, 1, 0]] ], 536 | [ [M.MOVE], [W], [OG.EA, O.CCR], [[12, 1, 0]] ], 537 | [ [M.MOVE], [W], [OG.EA, O.SR], [[12, 1, 0]] ], 538 | [ [M.MOVE], [L], [O.An, O.USP], [[4, 1, 0]] ], 539 | [ [M.MOVE], [L], [O.USP, O.An], [[4, 1, 0]] ], 540 | 541 | //---------------------------------------------------------------------------------------------------------------- 542 | // Move Peripheral Instruction Execution Times 543 | //---------------------------------------------------------------------------------------------------------------- 544 | [ [M.MOVEP], [W], [O.Dn, O.AnDisp], [[16, 2, 2]] ], 545 | [ [M.MOVEP], [W], [O.AnDisp, O.Dn], [[16, 4, 0]] ], 546 | [ [M.MOVEP], [L], [O.Dn, O.AnDisp], [[24, 2, 4]] ], 547 | [ [M.MOVEP], [L], [O.AnDisp, O.Dn], [[24, 6, 0]] ], 548 | ]; 549 | --------------------------------------------------------------------------------