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; const REG_MAP = { 'A': '000', 'B': '001', 'C': '010', 'D': '011', 'E': '100', 'F': '101', 'X': '110', 'Y': '111', }; function parseRegister(registerString: string): string { if (!(registerString in REG_MAP)) { throw new Error(`'${registerString}' is not a valid register`); } return REG_MAP[registerString]; } 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 = Object.keys(REG_MAP).join('|'); 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, false, false)); 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, literalSigned: boolean, labelRelative: boolean): ParsedLinePart { if (isNumber(numberOrLabelText)) { return toBitString(toNumber(numberOrLabelText), bits, literalSigned); } else { return { addressRelative: labelRelative, sourceAddress: this.currentAddress, bitCount: 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 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 indirect = bindablePattern(VALUE_RE, '\\(', '\\)'); const registerAndA = bindablePattern(REGISTER_RE, '', `${so},${so}A`); const valueAndA = bindablePattern(VALUE_RE, '', `${so},${so}A`); match('hlt', () => i('00000000')); match('nop', () => i('00000001')); match('ret', () => i('00001010')); match('rti', () => i('00001011')); match('sec', () => i('00001100')); match('clc', () => i('00001101')); match('eni', () => i('00001110')); match('dsi', () => i('00001111')); match('jmp', s, indirect(a), ({ a }) => { i2('00000011', this.value(a, 8, false, false)) }); match('jsr', s, indirect(a), ({ a }) => { i2('00001001', this.value(a, 8, false, false)) }); match('jmp', s, token(a), ({ a }) => { i2('00000010', this.value(a, 8, false, false)) }); match('jz', s, token(a), ({ a }) => { i2('00000100', this.value(a, 8, false, false)) }); match('jnz', s, token(a), ({ a }) => { i2('00000101', this.value(a, 8, false, false)) }); match('jc', s, token(a), ({ a }) => { i2('00000110', this.value(a, 8, false, false)) }); match('jnc', s, token(a), ({ a }) => { i2('00000111', this.value(a, 8, false, false)) }); match('jsr', s, token(a), ({ a }) => { i2('00001000', this.value(a, 8, false, false)) }); match('adc', s, registerAndA(r), ({ r }) => i('00010', parseRegister(r))); match('sbc', s, registerAndA(r), ({ r }) => i('00100', parseRegister(r))); match('xor', s, registerAndA(r), ({ r }) => i('00111', parseRegister(r))); match('and', s, registerAndA(r), ({ r }) => i('01000', parseRegister(r))); match('or', s, registerAndA(r), ({ r }) => i('01001', parseRegister(r))); match('cmp', s, registerAndA(r), ({ r }) => i('01110', parseRegister(r))); match('inc', s, register(r), ({ r }) => i('00011', parseRegister(r))); match('dec', s, register(r), ({ r }) => i('00101', parseRegister(r))); match('not', s, register(r), ({ r }) => i('00110', parseRegister(r))); match('slc', s, register(r), ({ r }) => i('01010', parseRegister(r))); match('src', s, register(r), ({ r }) => i('01011', parseRegister(r))); match('rol', s, register(r), ({ r }) => i('01100', parseRegister(r))); match('ror', s, register(r), ({ r }) => i('01101', parseRegister(r))); match('ld', register(a), sep, register(b), ({ a, b }) => i('11', parseRegister(a), parseRegister(b))); match('ld', register(a), sep, valueAndA(b), ({ a, b }) => { i2('10011', parseRegister(a), this.value(b, 8, false, false)); }); match('st', register(a), sep, valueAndA(b), ({ a, b }) => { i2('10010', parseRegister(a), this.value(b, 8, false, false)); }); match('ld', register(a), sep, indirect(b), ({ a, b }) => { i2('10101', parseRegister(a), this.value(b, 8, false, false)); }); match('st', register(a), sep, indirect(b), ({ a, b }) => { i2('10100', parseRegister(a), this.value(b, 8, false, false)); }); match('ld', register(a), sep, immediate(b), ({ a, b }) => { i2('01111', parseRegister(a), this.value(b, 8, false, false)); }); match('ld', register(a), sep, token(b), ({ a, b }) => { i2('10001', parseRegister(a), this.value(b, 8, false, false)); }); match('st', register(a), sep, token(b), ({ a, b }) => { i2('10000', parseRegister(a), this.value(b, 8, false, false)); }); match('push', s, token(r), ({ r }) => i('10110', parseRegister(r))); match('pull', s, token(r), ({ r }) => i('10111', parseRegister(r))); // 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): string { if (typeof part === 'string') { return part; } else if (isNumber(part.value)) { return toBitString(toNumber(part.value), part.bitCount, part.addressRelative); } 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); } 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, bitCount: 8, value: base, }), 2); const offsetValue = parseInt(this.resolveParsedLinePart({ ...part, bitCount: 8, addressRelative: false, value: offset, }), 2) * (operator === '+' ? 1 : -1); return toBitString(baseValue + offsetValue, part.bitCount, part.addressRelative); } if (this.constants.has(part.value)) { const value = this.constants.get(part.value); return toBitString(value, part.bitCount, false); } 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() { const instruction = this.memory[this.pc] ?? 0; const immediate = this.memory[this.pc + 1] ?? 0; const bits = instruction.toString(2).padStart(8, '0'); const operand = bits.slice(5, 8); if (bits === '00000000') { // hlt } else if (bits === '00000001') { // nop this.pc += 1; } else if (bits === '00000010') { // jmp imm this.pc = immediate; } else if (bits === '00000011') { // jmp abs this.pc = this.memory[immediate] ?? 0; } else if (bits === '00000100') { // jz imm this.pc = this.zeroFlag ? immediate : this.pc + 2; } else if (bits === '00000101') { // jnz imm this.pc = !this.zeroFlag ? immediate : this.pc + 2; } else if (bits === '00000110') { // jc imm this.pc = this.carryFlag ? immediate : this.pc + 2; } else if (bits === '00000111') { // jnc imm this.pc = !this.carryFlag ? immediate : this.pc + 2; } else if (bits === '00001000') { // jsr imm this.push(this.pc + 2); this.pc = immediate; } else if (bits === '00001001') { // jsr abs this.push(this.pc + 2); this.pc = this.memory[immediate] ?? 0; } else if (bits === '00001010') { // ret this.pc = this.pop(); } else if (bits === '00001011') { // rti this.pc = this.pop(); this.interruptsEnabled = true; } else if (bits === '00001100') { // sec this.carryFlag = true; this.pc += 1; } else if (bits === '00001101') { // clc this.carryFlag = false; this.pc += 1; } else if (bits === '00001110') { // eni this.interruptsEnabled = true; this.pc += 1; } else if (bits === '00001111') { // dsi this.interruptsEnabled = false; this.pc += 1; } else if (bits.startsWith('00010')) { // adc this.aluOp(operand, (x, a) => x + a + (this.carryFlag ? 1 : 0)); } else if (bits.startsWith('00011')) { // inc this.aluOp(operand, (x, a) => x + 1, false); } else if (bits.startsWith('00100')) { // sbc this.aluOp(operand, (x, a) => x - a - (this.carryFlag ? 1 : 0)); } else if (bits.startsWith('00101')) { // dec this.aluOp(operand, (x, a) => x - 1, false); } else if (bits.startsWith('00110')) { // not this.aluOp(operand, (x, a) => (~x) & 0xff, false); } else if (bits.startsWith('00111')) { // xor this.aluOp(operand, (x, a) => x ^ a); } else if (bits.startsWith('01000')) { // and this.aluOp(operand, (x, a) => x & a); } else if (bits.startsWith('01001')) { // or this.aluOp(operand, (x, a) => x | a); } else if (bits.startsWith('01010')) { // slc this.aluOp(operand, (x, a) => (x << 1) + (this.carryFlag ? 1 : 0)); } else if (bits.startsWith('01011')) { // src const operandValue = this.getRegister(operand); const aValue = this.registers[0]; const result = operandValue >>> 1 + (this.carryFlag ? 0x80 : 0); this.zeroFlag = result === 0; this.carryFlag = (operandValue & 0x01) === 1; this.registers[0] = result & 0xff; this.pc += 1; } else if (bits.startsWith('01100')) { // rol this.aluOp(operand, (x, a) => (x << 1) + (x >>> 7), false); } else if (bits.startsWith('01101')) { // ror const initialValue = this.getRegister(operand); this.aluOp(operand, (x, a) => (x >>> 1) + ((x & 1) << 7), false); this.carryFlag = (initialValue & 1) === 1; } else if (bits.startsWith('01110')) { // cmp const operandValue = this.getRegister(operand); const aValue = this.registers[0]; const result = aValue - operandValue; this.zeroFlag = result === 0; this.carryFlag = result > 0; this.pc += 1; } else if (bits.startsWith('01111')) { // ldR imm this.setRegister(operand, immediate); this.pc += 2; } else if (bits.startsWith('10000')) { // stR abs this.memory[immediate] = this.getRegister(operand); this.pc += 2; } else if (bits.startsWith('10001')) { // ldR abs this.setRegister(operand, this.memory[immediate] ?? 0); this.pc += 2; } else if (bits.startsWith('10010')) { // stR aai this.memory[immediate + this.registers[0]] = this.getRegister(operand); this.pc += 2; } else if (bits.startsWith('10011')) { // ldR aai this.setRegister(operand, this.memory[immediate + this.registers[0]] ?? 0); this.pc += 2; } else if (bits.startsWith('10100')) { // stR ind this.memory[this.memory[immediate] ?? 0] = this.getRegister(operand); this.pc += 2; } else if (bits.startsWith('10101')) { this.setRegister(operand, this.memory[this.memory[immediate] ?? 0] ?? 0); this.pc += 2; } else if (bits.startsWith('10110')) { // push this.push(this.getRegister(operand)); this.pc += 1; } else if (bits.startsWith('10111')) { // pull this.setRegister(operand, this.pop()); this.pc += 1; } else if (bits.startsWith('11')) { // ldR R this.setRegister(bits.slice(2, 5), this.getRegister(operand)); this.pc += 1; } } aluOp(register: string, fn: (x: number, a: number) => number, destinationIsA = true) { const operandValue = this.getRegister(register); const aValue = this.registers[0]; const result = fn(operandValue, aValue); this.zeroFlag = result === 0; this.carryFlag = (result & 0xff) !== result; this.setRegister(destinationIsA ? '0' : register, result & 0xff); this.pc += 1; } push(value: number) { this.memory[this.memory[STACK_POINTER_ADDRESS]--] = value; } pop(): number { return this.memory[++this.memory[STACK_POINTER_ADDRESS]]; } getRegisterA(): number { return this.registers[0]; } getRegister(register: string): number { const index = parseInt(register, 2); return this.registers[index]; } setRegister(register: string, value: number) { const index = parseInt(register, 2); this.registers[index] = value; } printState(): string { const registerNames = ['A', 'B', 'C', 'D', 'E', 'F', 'X', 'Y']; const registersString = this.registers.map((value, index) => `${registerNames[index]}=${value.toString(16).padStart(2, '0')}`).join(' '); let memoryString = ' '; for (let i = 0; i < 16; i++) { memoryString += `x${i.toString(16)} `; } for (let i = 0; i < 16; i++) { memoryString += `\n${i.toString(16)}x `; for (let j = 0; j < 16; j++) { const address = i * 16 + j; const value = this.memory[address] ?? 0; let style = ''; if (address === this.memory[STACK_POINTER_ADDRESS]) { style += 'background-color: cyan;'; } if (address === this.pc) { style += 'background-color: #fcb55b;'; } memoryString += ``; memoryString += `${value.toString(16).padStart(2, '0')}`; memoryString += ''; memoryString += ' '; } } const controlRegistersString = `pc: ${this.pc.toString(16).padStart(2, '0')}, ` + `stack pointer: ${this.memory[STACK_POINTER_ADDRESS].toString(16).padStart(2, '0')}, ` + `carry flag: ${this.carryFlag ? 1 : 0}, ` + `zero flag: ${this.zeroFlag ? 1 : 0}`; return `
${controlRegistersString}
${registersString}
memory:\n${memoryString}
`; } } 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[a-fA-FxXyY]\b/, instruction: /^[a-zA-Z0-9\.]+/, other: /\S/, }); const archSpecification: ArchSpecification = { documentation: '', syntaxHighlighting, assemble, maxWordsPerInstruction: 2, wordSize: 8, emulator: new V8Emulator(), }; export default archSpecification; archSpecification.documentation = ` \`\`\` V V 888 V V 8 8 V V 8 8 V V 888 V V 8 8 V V 8 8 V 888 \`\`\` The V8 is an 8-bit RISC processor with an 8-bit address bus, 8 registers and 2 flags developed in LBP2 by Neon. It was optimized for efficiency rather than speed (WHAT DOES THAT MEAN???!?!? (this means that efficiency (component count) was favoured over speed in the past (keyword: was))) and can be clocked up to 7.5 Hz. ## OPCODE SHEET \`\`\` +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ |OPCODES| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 0 | HLT | NOP |JMP imm|JMP abs|JZ imm |JNZ imm|JC imm |JNC imm|JSR imm|JSR abs| RET | RTI | SEC | CLC | ENI | DSI | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 1 |ADC A,A|ADC B,A|ADC C,A|ADC D,A|ADC E,A|ADC F,A|ADC X,A|ADC Y,A| INC A | INC B | INC C | INC D | INC E | INC F | INC X | INC Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 2 |SBC A,A|SBC B,A|SBC C,A|SBC D,A|SBC E,A|SBC F,A|SBC X,A|SBC Y,A| DEC A | DEC B | DEC C | DEC D | DEC E | DEC F | DEC X | DEC Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 3 | NOT A | NOT B | NOT C | NOT D | NOT E | NOT F | NOT X | NOT Y |XOR A,A|XOR B,A|XOR C,A|XOR D,A|XOR E,A|XOR F,A|XOR X,A|XOR Y,A| +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 4 |AND A,A|AND B,A|AND C,A|AND D,A|AND E,A|AND F,A|AND X,A|AND Y,A|OR A,A |OR B,A |OR C,A |OR D,A |OR E,A |OR F,A |OR X,A |OR Y,A | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 5 | SLC A | SLC B | SLC C | SLC D | SLC E | SLC F | SLC X | SRC Y | SRC A | SRC B | SRC C | SRC D | SRC E | SRC F | SRC X | SRC Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 6 | ROL A | ROL B | ROL C | ROL D | ROL E | ROL F | ROL X | ROL Y | ROR A | ROR B | ROR C | ROR D | ROR E | ROR F | ROR X | ROR Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 7 |CMP A,A|CMP B,A|CMP C,A|CMP D,A|CMP E,A|CMP F,A|CMP X,A|CMP Y,A|LDA imm|LDB imm|LDC imm|LDD imm|LDE imm|LDF imm|LDX imm|LDY imm| +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 8 |STA abs|STB abs|STC abs|STD abs|STE abs|STF abs|STX abs|STY abs|LDA abs|LDB abs|LDC abs|LDD abs|LDE abs|LDF abs|LDX abs|LDY abs| +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 9 |STA aai|STB aai|STC aai|STD aai|STE aai|STF aai|STX aai|STY aai|LDA aai|LDB aai|LDC aai|LDD aai|LDE aai|LDF aai|LDX aai|LDY aai| +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | A |STA ind|STB ind|STC ind|STD ind|STE ind|STF ind|STX ind|STY ind|LDA ind|LDB ind|LDC ind|LDD ind|LDE ind|LDF ind|LDX ind|LDY ind| +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | B |PUSH A |PUSH B |PUSH C |PUSH D |PUSH E |PUSH F |PUSH X |PUSH Y |PULL A |PULL B |PULL C |PULL D |PULL E |PULL F |PULL X |PULL Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | C | LDA A | LDA B | LDA C | LDA D | LDA E | LDA F | LDA X | LDA Y | LDB A | LDB B | LDB C | LDB D | LDB E | LDB F | LDB X | LDB Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | D | LDC A | LDC B | LDC C | LDC D | LDC E | LDC F | LDC X | LDC Y | LDD A | LDD B | LDD C | LDD D | LDD E | LDD F | LDD X | LDD Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | E | LDE A | LDE B | LDE C | LDE D | LDE E | LDE F | LDE X | LDE Y | LDF A | LDF B | LDF C | LDF D | LDF E | LDF F | LDF X | LDF Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | F | LDX A | LDX B | LDX C | LDX D | LDX E | LDX F | LDX X | LDX Y | LDY A | LDY B | LDY C | LDY D | LDY E | LDY F | LDY X | LDY Y | +-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ \`\`\` ## REGISTERS A (Accumulator): acts like a normal register, but certain operations will overwrite it's contents B,C,D,E,F,X,Y: general purpose registers ## ADDRESSING MODES Here is an overview of the addressing modes used in the opcode sheet and in the instruction summary, as well as how they would look in an assembler using LDA as an example. "*" represents the byte immediately after the instruction. (R) (Register): (R) is used as a placeholder and represents any of the 8 registers. assembler: LDA r imm (IMMediate): the data used is * itself. assembler: LDA #* abs (ABSolute): * points to the memory address to interact with. assembler: LDA * aai (Absolute, A Indexed): *+A points to the memory address to interact with. assembler: LDA *,A ind (INDirect): * points to a memory address, and the byte at that address points to the address to interact with. assembler: LDA (*) ## FLAGS The V8 has 2 flags: Zero (Z) and Carry (C). zero is set whenever the last read data=$00 and Carry is naturally set whenever the ALU outputs a carry-out of sorts, but can also be manually set or reset. ## INTERRUPTS interrupts are triggered by hardware and cause the processor to stop what it's doing and jump to the address pointed to by the interrupt vector located at memory address $FE, saving a return address to the stack. Once an interrupt occurs, no further interrupts can occur until interrupts are re-enabled. Interrupts can only be handled while CLK is high, interrupts are enabled and the processor isn't already executing an instruction. If an interrupt is triggered while these conditions aren't met, it will be handled once the conditions for an interrupt to occur are met. Furthermore, the interrupt input must be brought low and then high again for another interrupt to occur. Once triggered, it takes 4 clock cycles for the processor to jump to where the interrupt vector points. ## RESETS Bringing the reset input high will cause the processor to initialize itself. Resets take priority over interrupts and are handled when CLK is high, but can be triggered at any time, similar to interrupts. Resets take 2 clock cycles to be handled, during which all 8 registers are set to $00, the stack pointer is set to $7E, interrupts are disabled and the processor jumps to where the reset vector, located at memory address $FF, points to. ## THE STACK The stack is a region of memory used as a large LIFO (Last In First Out) buffer. The stack pointer is located at memory address $7F and points to the next free layer. The stack pointer initially points to memory address $7E by default, but can be relocated elsewhere if needed. The stack cascades downwards, meaning pushing something onto the stack will decrement the stack pointer and pulling something from it will increment the pointer. ## INSTRUCTIONS This is a basic overview of the V8's 31 instructions along with how many clock cycles (CCs) they take and which flags they affect. -ADC (R),A: ADd A to (R) with Carry input, sum is written to A CCs: 1 flags affected: C,Z -AND r,A: bitwise AND between (R) and A, result is written to A CCs: 1 flags affected: Z -CLC: CLears the Carry flag CCs: 1 flags affected: C -CMP (R),A: CoMPare (R) to A and set the flags accordingly, functionally the same as subtracting (R) from A and discarding the difference CCs: 1 flags affected: C,Z -DEC (R): DECrement (R) by 1 CCs: 1 flags affected: C,Z -DSI: DiSable Interrupts CCs: 1 flags affected: none -ENI: ENable Interrupts CCs: 1 flags affected: none -HLT: enable interrupts and HaLT the processor until an interrupt or reset occurs CCs: N/A flags affected: none -INC (R): INCrement (R) by 1 CCs: 1 flags affected: C,Z -JC: Jump if the Carry flag is set CCs: 1 or 2* flags affected: none -JMP: JuMP to a new address CCs: JMP imm: 2 JMP abs: 3 flags affected: none -JNC (Jump on No Carry) jump if the carry flag is Not set CCs: 1 or 2* flags affected: none -JNZ (jump on No Zero) jump if the zero flag isn't set CCs: 1 or 2* flags affected: none -JSR (Jump to SubRoutine): push a return address onto the stack and jump to a new address CCs: JSR imm: 4 JSR abs: 5 flags affected: none -JZ: jump if the zero flag is set CCs: 1 or 2* flags affected: none -LDr: LoaD data into r CCs: LD(R) (R): 1 LD(R) imm: 2 LD(R) abs: 3 LD(R) aii: 3 LD(R) ind: 4 flags affected: Z -NOP: NO OPeration is performed CCs: 1 flags affected: none -NOT r: bitwise NOT on r CCs: 1 flags affected: Z -OR (R),A: bitwise OR between (R) and A, result is written to A CCs: 1 flags affected: Z -PULL (R): PULL the last stack entry into (R) CCs: 3 flags affected: none -PUSH (R): PUSH (R) onto the stack CCs: 3 flags affected: none -RET (RETurn from subroutine): pull the last saved return address from the stack and jump to it CCs: 3 flags affected: none -ROL (R): ROtate (R) one bit to the Left CCs: 1 flags affected: C,Z -ROR (R): ROtate (R) one bit to the Right CCs: 1 flags affected: C,Z -RTI (ReTurn from Interrupt): pull the last saved return address from the stack and jump to it and enable interrupts CCs: 3 flags affected: none -SEC: SEt the Carry flag CCs: 1 flags affected: C -SLC (R): Shifts (R) one bit to the Left with Carry input CCs: 1 flags affected: C,Z -SRC (R): Shifts (R) one bit to the Right with Carry input CCs: 1 flags affected: C,Z -ST(R): STore (R) into memory CCs: ST(R) abs: 3 ST(R) aii: 3 ST(R) ind: 4 flags affected: none -SBC (R),A: SuBtract A from (R) with Carry input, difference is written to A CCs: 1 flags affected: C,Z -XOR (R),A: bitwise XOR between (R) and A, result is written to A CCs: 1 flags affected: Z *: Conditional jumps take 1 clock cycle if the condition is false and 2 if it's true ## PINOUT Here is a description of what each input/output does and where they are located on the processor: \`\`\` __ __ | \\_/ |- AD7 | |- AD6 | |- AD5 CLK -| |- AD4 INT -| |- AD3 RST -| |- AD2 DI7 -| |- AD1 DI6 -| |- AD0 DI5 -| |- DO7 DI4 -| |- DO6 DI3 -| |- DO5 DI2 -| |- DO4 DI1 -| |- DO3 DI0 -| |- DO2 | |- DO1 | |- DO0 | |- R |_______|- W CLK: CLocK input INT: INTerrupt RST: ReSeT DI7-DI0: Data Input bus AD7-AD0: ADdress bus DO7-DO0: Data Output bus R: Read W: Write \`\`\` `;