import type { OutputLine, AssemblyInput, ArchSpecification, AssemblyOutput, InputError, LineSource, Emulator } from '../assembly'; import { toBitString } from '../util.js'; export type Reference = { sourceAddress: number; addressRelative: boolean; bitCount: number; value: string; }; type ParsedLabel = { tag: 'label'; source: LineSource, name: string; }; type ParsedInstruction = { tag: 'instruction'; source: LineSource, parts: Array; address: number; }; export type ParsedLinePart = string | Reference; type ParsedLine = ParsedInstruction | ParsedLabel; function isNumber(value: string): boolean { return toNumber(value) !== null; } function toNumber(value: string): number { let result = null; if (value.startsWith('$')) { result = parseInt(value.slice(1), 16); } else if (value.startsWith('%')) { result = parseInt(value.slice(1), 2); } else if (value.startsWith('@')) { result = parseInt(value.slice(1), 8); } else { result = parseInt(value, 10); } if (isNaN(result)) { return null; } return result; } type Binding = { tag: 'binding', name: string }; const NUMBER_RE = String.raw`%-?[01][01_]*|\$-?[\da-f][\da-f_]*|@-?[0-7][0-7_]*|-?\d[\d_]*`; const VALUE_ATOM_RE = String.raw`(?:${NUMBER_RE}|[a-z0-9_-]+)`; const VALUE_PAIR_RE = String.raw`\((${VALUE_ATOM_RE})\s*([\+\-])\s*(${VALUE_ATOM_RE})\)`; const VALUE_RE = String.raw`${VALUE_ATOM_RE}|${VALUE_PAIR_RE}`; const REGISTER_RE = 'x|y|z|X|Y|Z|YX|yx'; class Program { currentAddress: number; output: Array; labels: Map; constants: Map; outputEnabled: boolean | null; constructor() { this.currentAddress = 0; this.output = []; this.labels = new Map(); this.constants = new Map(); this.outputEnabled = null; } instruction(parts: Array, wordCount: number, source: LineSource, count: number) { if (this.outputEnabled === null || this.outputEnabled) { this.output.push({ tag: 'instruction', source, address: this.currentAddress, parts, }); } this.currentAddress += wordCount * count; } label(name: string, source: LineSource) { if (this.labels.has(name)) { throw new Error(`Label '${name}' already defined`); } this.labels.set(name, this.currentAddress); this.output.push({ tag: 'label', source, name, }); } address(value: number) { this.currentAddress = value; } align(value: number) { this.currentAddress = Math.ceil(this.currentAddress / value) * value; } enable() { if (this.outputEnabled === null) { this.output = []; } this.outputEnabled = true; } disable() { this.outputEnabled = false; } data(values: Array, source: LineSource) { const numbers: Array = values.map(n => this.value(n, 8, true)); for (const number of numbers) { this.instruction([number], 1, { ...source, realInstruction: '(data)' }, 1); } return this; } constant(name: string, value: number) { this.constants.set(name, value); } value(numberOrLabelText: string, bits: number, dynamic: boolean = false): ParsedLinePart { if (isNumber(numberOrLabelText)) { return toBitString(toNumber(numberOrLabelText), bits, false, 8); } else { return { addressRelative: false, sourceAddress: this.currentAddress, bitCount: dynamic ? 16 : bits, value: numberOrLabelText, }; } } literal(numberText: string, bits: number, signed: boolean): number { if (!isNumber(numberText)) { throw new Error(`Expected number, got ${numberText}`); } else { return toNumber(numberText); } } parseSourceLine(sourceLine: string, lineNumber: number) { // Remove comments beginning with ; and # let commentIndex = sourceLine.length; if (sourceLine.indexOf(';') !== -1) { commentIndex = Math.min(commentIndex, sourceLine.indexOf(';')); } if (sourceLine.indexOf('//') !== -1) { commentIndex = Math.min(commentIndex, sourceLine.indexOf('//')); } const uncommented = sourceLine.slice(0, commentIndex).trim(); this.parse({ lineNumber, realInstruction: uncommented, sourceInstruction: uncommented, sourceInstructionCommented: sourceLine, }); } parse(source: LineSource) { const line = source.realInstruction; if (line === '') { return; } let matchFound = false; const match = (...args: Array void)>) => { if (matchFound) { return; } const fn = args.pop() as any; const expression = new RegExp('^' + args.join('') + '$', 'i'); const result = line.match(expression); if (result === null) { return; } fn(result.groups); matchFound = true; } const i = (...parts: Array) => { this.instruction(parts, 1, source, 1); }; const i2 = (...parts: Array) => { this.instruction(parts, 1, source, 2); }; const i3 = (...parts: Array) => { this.instruction(parts, 1, source, 3); }; const i4 = (...parts: Array) => { this.instruction(parts, 1, source, 4); }; const pseudo = (realInstruction: string) => this.parse({ ...source, realInstruction }); const binding = (name: string): Binding => ({ tag: 'binding', name }); const r = binding('r'); const a = binding('a'); const b = binding('b'); const bindablePattern = (regex: string, prefix: string = '', suffix: string = '') => (binding: Binding) => `${prefix}(?<${binding.name}>${regex})${suffix}`; const token = bindablePattern(VALUE_RE); const register = bindablePattern(REGISTER_RE); const immediate = bindablePattern(VALUE_RE, '#'); const remainder = bindablePattern('.*'); window['numberRegex'] = NUMBER_RE; const s = String.raw`\s+`; const so = String.raw`\s*`; const sep = String.raw`\s*[,\s]\s*`; const hardSep = String.raw`\s*,\s*`; const addr = bindablePattern(VALUE_RE); const registerAndA = bindablePattern(REGISTER_RE, '', `${so},${so}A`); const valueAndA = bindablePattern(VALUE_RE, '', `${so},${so}A`); const x = '[xX]'; const y = '[yY]'; const z = '[zZ]'; const yx = '[yY][xX]'; match('hlt', () => i('00000000')); match('rti', () => i('00000010')); match('eni', () => i('00000011')); match('dsi', () => i('00000100')); match('nop', () => i('00000101')); match('rem', s, x, () => i('00001000')); match('rem', s, y, () => i('00001001')); match('rem', s, z, () => i('00001010')); match('clr', () => i('00001011')); match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) }); match('jmp', s, addr(a), sep, x, ({ a }) => { i3('00010001', this.value(a, 16)) }); match('jmp', s, addr(a), sep, y, ({ a }) => { i3('00010010', this.value(a, 16)) }); match('jmp', s, addr(a), sep, z, ({ a }) => { i3('00010011', this.value(a, 16)) }); match('jsr', s, addr(a), ({ a }) => { i3('00010100', this.value(a, 16)) }); match('jsr', s, addr(a), sep, x, ({ a }) => { i3('00010101', this.value(a, 16)) }); match('jsr', s, addr(a), sep, y, ({ a }) => { i3('00010110', this.value(a, 16)) }); match('jsr', s, addr(a), sep, z, ({ a }) => { i3('00010111', this.value(a, 16)) }); match('swp', s, x, sep, y, () => { i('00011000') }); match('swp', s, y, sep, x, () => pseudo('SWP X, Y')); match('swp', s, x, sep, z, () => { i('00011001') }); match('swp', s, z, sep, x, () => pseudo('SWP X, Z')); match('swp', s, y, sep, z, () => { i('00011011') }); match('swp', s, z, sep, y, () => pseudo('SWP Y, Z')); match('jmpez', s, x, sep, addr(a), ({ a }) => { i3('00100000', this.value(a, 16)) }); match('jmpez', s, z, sep, addr(a), ({ a }) => { i3('00100001', this.value(a, 16)) }); match('jmpez', s, register(r), sep, addr(a), () => { throw new Error('JMPEZ can only be used with registers X and Z') }); match('jmpgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100010', this.value(a, 16)) }); match('jmpgt', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JMPGT can only be used with registers X and Y') }); match('jmpeq', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100011', this.value(a, 16)) }); match('jmpeq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Y, ${a}`)); match('jmpeq', s, x, sep, z, sep, addr(a), ({ a }) => { i3('11110010', this.value(a, 16)) }); match('jmpeq', s, z, sep, x, sep, addr(a), ({ a }) => pseudo(`JMPEQ X, Z, ${a}`)); match('jmpeq', s, y, sep, z, sep, addr(a), ({ a }) => { i3('11110011', this.value(a, 16)) }); match('jmpeq', s, z, sep, y, sep, addr(a), ({ a }) => pseudo(`JMPEQ Y, Z, ${a}`)); match('jmprez', s, addr(a), ({ a }) => { i3('11110000', this.value(a, 16)) }); match('jmprnz', s, addr(a), ({ a }) => { i3('11110001', this.value(a, 16)) }); match('jsrez', s, x, sep, addr(a), ({ a }) => { i3('00100100', this.value(a, 16)) }); match('jsrez', s, z, sep, addr(a), ({ a }) => { i3('00100101', this.value(a, 16)) }); match('jsrez', s, register(r), sep, addr(a), () => { throw new Error('JSREZ can only be used with registers X and Z') }); match('jsrgt', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100110', this.value(a, 16)) }); match('jsrgt', s, register(a), sep, register(b), sep, () => { throw new Error('JSRGT can only be used with registers X and Y') }); match('jsreq', s, x, sep, y, sep, addr(a), ({ a }) => { i3('00100111', this.value(a, 16)) }); match('jsreq', s, y, sep, x, sep, addr(a), ({ a }) => pseudo(`JSREQ X, Y, ${a}`)); match('jsreq', s, register(a), sep, register(b), sep, addr(r), () => { throw new Error('JSREQ can only be used with registers X and Y') }); match('ret', () => i('00000001')); match('retrez', () => i('11110100')); match('retrnz', () => i('11110101')); match('djnz', s, x, sep, addr(a), ({ a }) => i3('11100000', this.value(a, 16))); match('djnz', s, y, sep, addr(a), ({ a }) => i3('11100001', this.value(a, 16))); match('djnz', s, z, sep, addr(a), ({ a }) => i3('11100010', this.value(a, 16))); match('lod', s, x, sep, immediate(a), ({ a }) => i2('01111100', this.value(a, 8))); match('lod', s, y, sep, immediate(a), ({ a }) => i2('01111101', this.value(a, 8))); match('lod', s, z, sep, immediate(a), ({ a }) => i2('01111110', this.value(a, 8))); match('lod', s, y, sep, x, () => i('00101000')); match('lod', s, z, sep, x, () => i('00101001')); match('lod', s, x, sep, y, () => i('00101010')); match('lod', s, z, sep, y, () => i('00101011')); match('lod', s, x, sep, z, () => i('00101100')); match('lod', s, y, sep, z, () => i('00101101')); match('lod', s, x, sep, addr(a), ({ a }) => { i3('10100000', this.value(a, 16)) }); match('lod', s, y, sep, addr(a), ({ a }) => { i3('10110000', this.value(a, 16)) }); match('lod', s, z, sep, addr(a), ({ a }) => { i3('11000000', this.value(a, 16)) }); match('lod', s, x, sep, addr(a), sep, y, ({ a }) => { i3('10100001', this.value(a, 16)) }); match('lod', s, x, sep, addr(a), sep, z, ({ a }) => { i3('10100010', this.value(a, 16)) }); match('lod', s, y, sep, addr(a), sep, x, ({ a }) => { i3('10110001', this.value(a, 16)) }); match('lod', s, y, sep, addr(a), sep, z, ({ a }) => { i3('10110010', this.value(a, 16)) }); match('lod', s, z, sep, addr(a), sep, x, ({ a }) => { i3('11000001', this.value(a, 16)) }); match('lod', s, z, sep, addr(a), sep, y, ({ a }) => { i3('11000010', this.value(a, 16)) }); match('str', s, x, sep, addr(a), ({ a }) => { i3('10100100', this.value(a, 16)) }); match('str', s, y, sep, addr(a), ({ a }) => { i3('10110100', this.value(a, 16)) }); match('str', s, z, sep, addr(a), ({ a }) => { i3('11000100', this.value(a, 16)) }); match('str', s, x, sep, addr(a), sep, y, ({ a }) => { i3('10100101', this.value(a, 16)) }); match('str', s, x, sep, addr(a), sep, z, ({ a }) => { i3('10100110', this.value(a, 16)) }); match('str', s, y, sep, addr(a), sep, x, ({ a }) => { i3('10110101', this.value(a, 16)) }); match('str', s, y, sep, addr(a), sep, z, ({ a }) => { i3('10110110', this.value(a, 16)) }); match('str', s, z, sep, addr(a), sep, x, ({ a }) => { i3('11000101', this.value(a, 16)) }); match('str', s, z, sep, addr(a), sep, y, ({ a }) => { i3('11000110', this.value(a, 16)) }); match('str', s, immediate(a), sep, addr(b), sep, x, ({ a, b }) => { i4('11010000', this.value(b, 16), this.value(a, 8)) }); match('str', s, immediate(a), sep, addr(b), sep, y, ({ a, b }) => { i4('11010001', this.value(b, 16), this.value(a, 8)) }); match('str', s, immediate(a), sep, addr(b), sep, z, ({ a, b }) => { i4('11010010', this.value(b, 16), this.value(a, 8)) }); match('str', s, immediate(a), sep, addr(b), ({ a, b }) => { i4('11010011', this.value(b, 16), this.value(a, 8)) }); match('str', s, immediate(a), sep, addr(b), sep, yx, ({ a, b }) => { i4('11010100', this.value(b, 16), this.value(a, 8)) }); match('lod', s, z, sep, addr(a), sep, yx, ({ a }) => { i3('11000011', this.value(a, 16)) }); match('str', s, z, sep, addr(a), sep, yx, ({ a }) => { i3('11000111', this.value(a, 16)) }); match('jmp', s, addr(a), ({ a }) => { i3('00010000', this.value(a, 16)) }); match('inc', s, x, () => i('00110000')); match('inc', s, y, () => i('00110001')); match('inc', s, z, () => i('00110010')); match('inc', s, addr(a), ({ a }) => i3('00110011', this.value(a, 16))); match('dec', s, x, () => i('00110100')); match('dec', s, y, () => i('00110101')); match('dec', s, z, () => i('00110110')); match('dec', s, addr(a), ({ a }) => i3('00110111', this.value(a, 16))); match('add', s, x, sep, immediate(a), ({ a }) => i2('01000000', this.value(a, 8))); match('add', s, y, sep, immediate(a), ({ a }) => i2('01010000', this.value(a, 8))); match('add', s, z, sep, immediate(a), ({ a }) => i2('01100000', this.value(a, 8))); // TODO: Check where result is stored and maybe remove the duplicate encodings if needed match('add', s, x, sep, y, () => i('01000100')); match('add', s, y, sep, x, () => i('01000100')); match('add', s, x, sep, z, () => i('01000101')); match('add', s, z, sep, x, () => i('01000101')); match('add', s, y, sep, z, () => i('01010101')); match('add', s, z, sep, y, () => i('01010101')); match('sub', s, x, sep, immediate(a), ({ a }) => i2('01000001', this.value(a, 8))); match('sub', s, y, sep, immediate(a), ({ a }) => i2('01010001', this.value(a, 8))); match('sub', s, z, sep, immediate(a), ({ a }) => i2('01100001', this.value(a, 8))); match('sub', s, x, sep, y, () => i('01000110')); match('sub', s, x, sep, z, () => i('01000111')); match('sub', s, y, sep, x, () => i('01010110')); match('sub', s, y, sep, z, () => i('01010111')); match('sub', s, z, sep, x, () => i('01100110')); match('sub', s, z, sep, y, () => i('01100111')); match('mul', s, x, sep, immediate(a), ({ a }) => i2('01000010', this.value(a, 8))); match('mul', s, y, sep, immediate(a), ({ a }) => i2('01010010', this.value(a, 8))); match('mul', s, z, sep, immediate(a), ({ a }) => i2('01100010', this.value(a, 8))); match('mul', s, x, sep, y, () => i('01001000')); match('mul', s, y, sep, x, () => i('01001000')); match('mul', s, x, sep, z, () => i('01001001')); match('mul', s, z, sep, x, () => i('01001001')); match('mul', s, y, sep, z, () => i('01011001')); match('mul', s, z, sep, y, () => i('01011001')); match('div', s, x, sep, immediate(a), ({ a }) => i2('01000011', this.value(a, 8))); match('div', s, y, sep, immediate(a), ({ a }) => i2('01010011', this.value(a, 8))); match('div', s, z, sep, immediate(a), ({ a }) => i2('01100011', this.value(a, 8))); match('div', s, x, sep, y, () => i('01001010')); match('div', s, x, sep, z, () => i('01001011')); match('div', s, y, sep, x, () => i('01011010')); match('div', s, y, sep, z, () => i('01011011')); match('div', s, z, sep, x, () => i('01101010')); match('div', s, z, sep, y, () => i('01101011')); match('mod', s, x, sep, immediate(a), ({ a }) => i2('10011100', this.value(a, 8))); match('mod', s, y, sep, immediate(a), ({ a }) => i2('10011101', this.value(a, 8))); match('mod', s, z, sep, immediate(a), ({ a }) => i2('10011110', this.value(a, 8))); match('mod', s, x, sep, y, () => i('01001100')); match('mod', s, x, sep, z, () => i('01001101')); match('mod', s, y, sep, x, () => i('01011100')); match('mod', s, y, sep, z, () => i('01011101')); match('mod', s, z, sep, x, () => i('01101100')); match('mod', s, z, sep, y, () => i('01101101')); match('lsl', s, x, () => i('01110000')); match('lsl', s, y, () => i('01110001')); match('lsl', s, z, () => i('01110010')); match('lsr', s, x, () => i('01110100')); match('lsr', s, y, () => i('01110101')); match('lsr', s, z, () => i('01110110')); match('not', s, x, () => i('01111000')); match('not', s, y, () => i('01111001')); match('not', s, z, () => i('01111010')); match('and', s, x, sep, immediate(a), ({ a }) => i2('10010000', this.value(a, 8))); match('and', s, y, sep, immediate(a), ({ a }) => i2('10010001', this.value(a, 8))); match('and', s, z, sep, immediate(a), ({ a }) => i2('10010010', this.value(a, 8))); match('and', s, x, sep, y, () => i('10000000')); match('and', s, y, sep, x, () => i('10000000')); match('and', s, x, sep, z, () => i('10000001')); match('and', s, z, sep, x, () => i('10000001')); match('and', s, y, sep, z, () => i('10000010')); match('and', s, z, sep, y, () => i('10000010')); match('xor', s, x, sep, immediate(a), ({ a }) => i2('10011000', this.value(a, 8))); match('xor', s, y, sep, immediate(a), ({ a }) => i2('10011001', this.value(a, 8))); match('xor', s, z, sep, immediate(a), ({ a }) => i2('10011010', this.value(a, 8))); match('xor', s, x, sep, y, () => i('10000100')); match('xor', s, y, sep, x, () => i('10000100')); match('xor', s, x, sep, z, () => i('10000101')); match('xor', s, z, sep, x, () => i('10000101')); match('xor', s, y, sep, z, () => i('10000110')); match('xor', s, z, sep, y, () => i('10000110')); match('or', s, x, sep, immediate(a), ({ a }) => i2('10010100', this.value(a, 8))); match('or', s, y, sep, immediate(a), ({ a }) => i2('10010101', this.value(a, 8))); match('or', s, z, sep, immediate(a), ({ a }) => i2('10010110', this.value(a, 8))); match('or', s, x, sep, y, () => i('10001000')); match('or', s, y, sep, x, () => i('10001000')); match('or', s, x, sep, z, () => i('10001001')); match('or', s, z, sep, x, () => i('10001001')); match('or', s, y, sep, z, () => i('10001010')); match('or', s, z, sep, y, () => i('10001010')); // Directives match(token(a), ':', ({ a }) => this.label(a, source)); match('.data', s, remainder(a), ({ a }) => this.data(a.split(new RegExp(hardSep)), { ...source, sourceInstruction: '.data' })); match('.repeat', s, token(a), sep, token(b), ({ a, b }) => this.data(new Array(this.literal(b, 8, false)).fill(a), { ...source, sourceInstruction: '.repeat' })); match('.eq', s, token(a), sep, token(b), ({ a, b }) => this.constant(a, this.literal(b, 8, false))); match('.address', s, token(a), ({ a }) => this.address(this.literal(a, 8, false))); match('.align', s, token(a), ({ a }) => this.align(this.literal(a, 8, false))); match('.start', () => this.enable()); match('.end', () => this.disable()); if (!matchFound) { throw new Error(`Unknown instruction: ${line}`); } } resolveParsedLine(instruction: ParsedLine): OutputLine { if (instruction.tag === 'label') { return { tag: 'label', name: instruction.name, }; } let bits = ''; for (const part of instruction.parts) { bits += this.resolveParsedLinePart(part); } if (bits.length % 8 !== 0) { throw new Error(`Instruction ${instruction.source.realInstruction} is ${bits.length} bits long, but should be a multiple of 8`); } return { tag: 'instruction', bits, address: instruction.address, source: instruction.source, }; } resolveParsedLinePart(part: ParsedLinePart, littleEndian: boolean = true): string { if (typeof part === 'string') { return part; } else if (isNumber(part.value)) { return toBitString(toNumber(part.value), part.bitCount, part.addressRelative, littleEndian ? 8 : null); } else if (this.labels.has(part.value)) { const targetLabelAddress = this.labels.get(part.value); const value = part.addressRelative ? targetLabelAddress - part.sourceAddress : targetLabelAddress; return toBitString(value, part.bitCount, part.addressRelative, littleEndian ? 8 : null); } const offsetMatch = part.value.match(new RegExp(VALUE_PAIR_RE, 'i')); if (offsetMatch) { const [base, operator, offset] = offsetMatch.slice(1); const baseValue = parseInt(this.resolveParsedLinePart({ ...part, value: base, }, false), 2,); const offsetValue = parseInt(this.resolveParsedLinePart({ ...part, addressRelative: false, value: offset, }, false), 2) * (operator === '+' ? 1 : -1); return toBitString(baseValue + offsetValue, part.bitCount, part.addressRelative, littleEndian ? 8 : null); } if (this.constants.has(part.value)) { const value = this.constants.get(part.value); return toBitString(value, part.bitCount, false, littleEndian ? 8 : null); } else { throw new Error(`Unknown label: ${part.value}`); } } } function assemble(input: AssemblyInput): AssemblyOutput { const program = new Program(); const errors: Array = []; for (const [lineNumber, line] of input.source.split('\n').entries()) { try { program.parseSourceLine(line, lineNumber); } catch (e) { errors.push({ line: lineNumber, message: e.message, }); } } const outputLines: Array = []; for (const instruction of program.output) { let resolved: OutputLine; try { resolved = program.resolveParsedLine(instruction); outputLines.push(resolved); } catch (e) { errors.push({ line: instruction.source.lineNumber, message: e.message, }); } } return { lines: outputLines, errors, message: '', }; } const STACK_POINTER_ADDRESS = 0x7f; const INTERRUPT_VECTOR_ADDRESS = 0xfe; const RESET_VECTOR_ADDRESS = 0xff; class V8Emulator implements Emulator { memory: Array = []; registers: Array = []; pc: number; cycle: number; carryFlag: boolean; zeroFlag: boolean; interruptsEnabled: boolean; constructor() { this.init([]); } init(memory: Array) { this.memory = memory; this.memory[STACK_POINTER_ADDRESS] = STACK_POINTER_ADDRESS - 1; this.registers = new Array(8).fill(0); this.pc = this.memory[RESET_VECTOR_ADDRESS] ?? 0; this.cycle = 0; this.carryFlag = false; this.zeroFlag = false; this.interruptsEnabled = true; } step() { } printState(): string { return 'Emulator not implemented'; } } const syntaxHighlighting = new window['Parser']({ whitespace: /\s+/, number: /#?(\$-?[\dA-Fa-f_]+|-?(\d+)|%-?[01_]+|@-?[0-7_]+)/, comment: /\/\/[^\r\n]*|;[^\r\n]*/, directive: /\.[a-zA-Z0-9_]+/, label: /[a-zA-Z0-9_]+:/, string: /"(\\.|[^"\r\n])*"|'(\\.|[^'\r\n])*'/, register: /\b([xyz]|yx)\b/i, instruction: /^[a-zA-Z0-9\.]+/, other: /\S/, }); const archSpecification: ArchSpecification = { documentation: '', syntaxHighlighting, assemble, maxWordsPerInstruction: 4, wordSize: 8, emulator: new V8Emulator(), }; export default archSpecification; archSpecification.documentation = ` # Bitzzy An 8-bit CPU by Mazzetip with 16-bit addressing. ## Registers There are three registers: X, Y, and Z. Z is the accumulator. ## Assembler syntax Instructions and registers are case-insensitive. For example, \`INC X\` and \`inc x\` are both valid. Labels and constants are case-sensitive. For example, \`myLabel:\` and \`mylabel:\` are different labels. Comments begin with a semicolon. For example, \`; this is a comment\`. Labels can be offset by a value by placing them in parentheses. For example, \`(myLabel + 2)\` is two bytes after \`myLabel\`. <reg> indicates a register, i.e. X, Y, or Z. For example, the instruction \`INC \` can be used as \`INC X\`, \`INC Y\`, or \`INC Z\`. When an instruction takes two registers, they must not be the same. E.g. \`SWP x, y\` is okay but \`SWP x, x\` is illegal. <imm> indicates an 8-bit immediate value. This must be prefixed with '#' and can be signed or unsigned. For example, \`LOD X, #\$ff\` loads the value -1 into register X. <addr> indicates a 16-bit address. For example, \`JMP \$fa08\` jumps to address 0xfa08. When registers X and Y are used together to form a 16-bit address, they are written as YX. Y forms the upper 8 bits while X forms the lower 8 bits. ## Instructions HLT: Halt the CPU. RTI: Return from interrupt. ENI: Enable interrupts. DSI: Disable interrupts. NOP: No operation. REM <reg>: Set register <reg> to the remainder (the carry/borrow flag). CLR: Clear remainder. JMP <addr>: Jump to the address <addr>. JMP <addr>, <reg>: Jump to the address <addr> + register <reg>. JMPEZ X, <addr>: Jump to the address <addr> if register X is zero. JMPEZ Z, <addr>: Jump to the address <addr> if register Z is zero. JMPGT X, Z, <addr>: Jump to the address <addr> if X is greater than Y. JMPEQ <reg-a>, <reg-b>, <addr>: Jump to the address <addr> if register <reg-a> is equal to register <reg-b>. JMPRNZ <addr>: Jump to the address <addr> if the remainder is not zero. JMPREZ <addr>: Jump to the address <addr> if the remainder is zero. JSR <addr>: Jump to the address <addr> and push the return address onto the stack. JSR <addr>, <reg>: Jump to the address <addr> + register <reg> and push the return address onto the stack. JSREZ X, <addr>: Jump to the address <addr> if register X is zero and push the return address onto the stack. JSREZ Z, <addr>: Jump to the address <addr> if register Z is zero and push the return address onto the stack. JSRGT X, Z, <addr>: Jump to the address <addr> if X is greater than Y and push the return address onto the stack. JSREQ <reg-a>, <reg-b>, <addr>: Jump to the address <addr> if register <reg-a> is equal to register <reg-b> and push the return address onto the stack. DJNZ <reg>, <addr>: Decrement register <reg> and jump to the address <addr> if it is now not zero. RET: Return from subroutine. RETRNZ: Return from subroutine if the remainder is not zero. RETREZ: Jump to the address <addr> if the remainder is zero. INC <reg>: Increment register <reg>. INC <addr>: Increment value at address <addr>. DEC <reg>: Decrement register <reg>. DEC <addr>: Decrement value at address <addr>. ADD <reg-a>, <reg-b>: Add register <reg-b> to register <reg-a> and store the result in Z. ADD <reg>, <imm>: Add the immediate value <imm> to register <reg> and store the result in register <reg>. SUB <reg-a>, <reg-b>: Subtract register <reg-b> from register <reg-a> and store the result in Z. SUB <reg>, <imm>: Subtract the immediate value <imm> from register <reg> and store the result in register <reg>. LSL <reg>: Logical shift left register <reg> by one bit. LSR <reg>: Logical shift right register <reg> by one bit. AND <reg-a>, <reg-b>: Bitwise AND register <reg-a> with register <reg-b> and store the result in Z. AND <reg>, <imm>: Bitwise AND register <reg> with the immediate value <imm> and store the result in register <reg>. OR <reg-a>, <reg-b>: Bitwise OR register <reg-a> with register <reg-b> and store the result in Z. OR <reg>, <imm>: Bitwise OR register <reg> with the immediate value <imm> and store the result in register <reg>. XOR <reg-a>, <reg-b>: Bitwise XOR register <reg-a> with register <reg-b> and store the result in Z. XOR <reg>, <imm>: Bitwise XOR register <reg> with the immediate value <imm> and store the result in register <reg>. LOD <reg>, <imm>: Load the immediate value <imm> into register <reg>. LOD <reg-a>, <reg-b>: Load the value in register <reg-b> into register <reg-a>. LOD <reg>, <addr>: Load the value at address <addr> into register <reg>. LOD <reg-a>, <addr>, <reg-b>: Load the value at address <addr> + register <reg-b> into register <reg-a>. LOD Z, <addr>, YX: Load the value at address <addr> + the combined value of registers X and Y ($YYXX) into register Z. SWP <reg-a>, <reg-b>: Swap the values in registers <reg-a> and <reg-b>. STR <reg>, <addr>: Store the value in register <reg> at address <addr>. STR <reg-a>, <addr>, <reg-b>: Store the value in register <reg-a> at address <addr> + register <reg-b>. STR <imm>, <addr>: Store the immediate value <imm> to address <addr>. STR <imm>, <addr>, <reg>: Store the immediate value <imm> to address <addr> + register <reg>. STR Z, <addr>, YX: Store the value in register Z at address <addr> + the combined value of registers X and Y ($YYXX). MUL <reg-a>, <reg-b>: Multiply register <reg-a> by register <reg-b> and store the result in in Z. MUL <reg>, <imm>: Multiply register <reg> by the immediate value <imm> and store the result in register <reg>. DIV <reg-a>, <reg-b>: Divide register <reg-a> by register <reg-b> and store the result in in Z. DIV <reg>, <imm>: Divide register <reg> by the immediate value <imm> and store the result in register <reg>. MOD <reg-a>, <reg-b>: Divide register <reg-a> by register <reg-b> and store the remainder in in Z. MOD <reg>, <imm>: Divide register <reg> by the immediate value <imm> and store the remainder in register <reg>. ## Assembler directives \`:\` : Define a label. \`.data , , , ...\`: Store 8-bit values in memory. You can also use labels, in which case the 16-bit address of the label will be stored. \`.repeat , \`: Repeat in memory multiple times. \`.address \`: Set the current address to . \`.align \`: Align the current address to the next multiple of . \`.end\`: Ignore all following instructions. \`.start\`: Resume assembling instructions. \`.eq\ , \`: Define a constant. `;