mirror of
https://github.com/ApfelTeeSaft/WebMetal.git
synced 2026-08-26 19:43:24 +00:00
861 lines
20 KiB
JSON
861 lines
20 KiB
JSON
{
|
||
"formatVersion": 3,
|
||
"metadata": {
|
||
"name": "RETRO-8",
|
||
"author": "WebMetal examples",
|
||
"description": "An original 8-bit accumulator machine in the spirit of late-70s home-computer CPUs: Z/N/C flags, immediate, absolute, and X-indexed addressing.",
|
||
"createdAt": "2026-07-17T00:00:00.000Z",
|
||
"generator": {
|
||
"app": "WebMetal",
|
||
"version": "0.12.0"
|
||
}
|
||
},
|
||
"graph": {
|
||
"nodes": [
|
||
{
|
||
"id": "note-title",
|
||
"type": "comment",
|
||
"position": {
|
||
"x": 40,
|
||
"y": -190
|
||
},
|
||
"data": {
|
||
"name": "NOTE1",
|
||
"doc": "",
|
||
"params": {
|
||
"text": "RETRO-8 - an original 8-bit accumulator machine with three addressing modes.\n\nImmediate (LDI #5), absolute (LDA 96), and X-indexed (STAX 96 stores to 96 + X). The X register plus INX turn straight-line code into loops over arrays - the demo fills five memory cells that way."
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "note-indexed",
|
||
"type": "comment",
|
||
"position": {
|
||
"x": 940,
|
||
"y": 110
|
||
},
|
||
"data": {
|
||
"name": "NOTE2",
|
||
"doc": "",
|
||
"params": {
|
||
"text": "Indexed addressing: STAX adds the X register to the instruction’s address operand inside the ALU before the store - the classic base + index trick."
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "acc",
|
||
"type": "register",
|
||
"position": {
|
||
"x": 40,
|
||
"y": 20
|
||
},
|
||
"data": {
|
||
"name": "ACC",
|
||
"doc": "The accumulator: source and destination of all arithmetic, and the value stored by STA/STAX.",
|
||
"params": {
|
||
"width": 8
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "x",
|
||
"type": "register",
|
||
"position": {
|
||
"x": 40,
|
||
"y": 200
|
||
},
|
||
"data": {
|
||
"name": "X",
|
||
"doc": "The index register. STAX adds it to the address operand; INX steps it through an array.",
|
||
"params": {
|
||
"width": 8
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "alu",
|
||
"type": "alu",
|
||
"position": {
|
||
"x": 340,
|
||
"y": 110
|
||
},
|
||
"data": {
|
||
"name": "ALU",
|
||
"doc": "Adds/subtracts the accumulator with an immediate or a memory operand, and computes indexed addresses for STAX.",
|
||
"params": {
|
||
"width": 8
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "flags",
|
||
"type": "flags",
|
||
"position": {
|
||
"x": 640,
|
||
"y": 110
|
||
},
|
||
"data": {
|
||
"name": "FLAGS",
|
||
"doc": "Z (zero), N (negative), C (carry / no-borrow). CMP subtracts without writing ACC, just to set these for BEQ/BNE.",
|
||
"params": {
|
||
"flags": "Z,N,C"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "pc",
|
||
"type": "pc",
|
||
"position": {
|
||
"x": 40,
|
||
"y": 440
|
||
},
|
||
"data": {
|
||
"name": "PC",
|
||
"doc": "8-bit program counter addressing MEM’s 256 words. Branches and JMP load it with a target address.",
|
||
"params": {
|
||
"width": 8
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "mem",
|
||
"type": "memory",
|
||
"position": {
|
||
"x": 340,
|
||
"y": 440
|
||
},
|
||
"data": {
|
||
"name": "MEM",
|
||
"doc": "Unified program + data memory: 256 × 8-bit words. The demo writes its output table at addresses 96-100.",
|
||
"params": {
|
||
"size": 256,
|
||
"width": 8
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"id": "dec",
|
||
"type": "decoder",
|
||
"position": {
|
||
"x": 640,
|
||
"y": 440
|
||
},
|
||
"data": {
|
||
"name": "DEC",
|
||
"doc": "Splits fetched words into the opcode and the immediate/address operand.",
|
||
"params": {}
|
||
}
|
||
},
|
||
{
|
||
"id": "ctl",
|
||
"type": "control",
|
||
"position": {
|
||
"x": 940,
|
||
"y": 440
|
||
},
|
||
"data": {
|
||
"name": "CTL",
|
||
"doc": "Turns decoded instructions and flag state into register loads, memory read/write strobes, and PC updates.",
|
||
"params": {}
|
||
}
|
||
}
|
||
],
|
||
"edges": [
|
||
{
|
||
"id": "pc.out->mem.addr",
|
||
"source": "pc",
|
||
"sourceHandle": "out",
|
||
"target": "mem",
|
||
"targetHandle": "addr",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "mem.dout->dec.instr",
|
||
"source": "mem",
|
||
"sourceHandle": "dout",
|
||
"target": "dec",
|
||
"targetHandle": "instr",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "acc.out->alu.a",
|
||
"source": "acc",
|
||
"sourceHandle": "out",
|
||
"target": "alu",
|
||
"targetHandle": "a",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "x.out->alu.a",
|
||
"source": "x",
|
||
"sourceHandle": "out",
|
||
"target": "alu",
|
||
"targetHandle": "a",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "mem.dout->alu.b",
|
||
"source": "mem",
|
||
"sourceHandle": "dout",
|
||
"target": "alu",
|
||
"targetHandle": "b",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "dec.imm->alu.b",
|
||
"source": "dec",
|
||
"sourceHandle": "imm",
|
||
"target": "alu",
|
||
"targetHandle": "b",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "alu.result->acc.in",
|
||
"source": "alu",
|
||
"sourceHandle": "result",
|
||
"target": "acc",
|
||
"targetHandle": "in",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "alu.result->x.in",
|
||
"source": "alu",
|
||
"sourceHandle": "result",
|
||
"target": "x",
|
||
"targetHandle": "in",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "alu.flags->flags.in",
|
||
"source": "alu",
|
||
"sourceHandle": "flags",
|
||
"target": "flags",
|
||
"targetHandle": "in",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "acc.out->mem.din",
|
||
"source": "acc",
|
||
"sourceHandle": "out",
|
||
"target": "mem",
|
||
"targetHandle": "din",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "dec.imm->pc.next",
|
||
"source": "dec",
|
||
"sourceHandle": "imm",
|
||
"target": "pc",
|
||
"targetHandle": "next",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "flags.out->ctl.flags",
|
||
"source": "flags",
|
||
"sourceHandle": "out",
|
||
"target": "ctl",
|
||
"targetHandle": "flags",
|
||
"kind": "data"
|
||
},
|
||
{
|
||
"id": "dec.ctl->ctl.decoded",
|
||
"source": "dec",
|
||
"sourceHandle": "ctl",
|
||
"target": "ctl",
|
||
"targetHandle": "decoded",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "dec.ctl->alu.op",
|
||
"source": "dec",
|
||
"sourceHandle": "ctl",
|
||
"target": "alu",
|
||
"targetHandle": "op",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->acc.load",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "acc",
|
||
"targetHandle": "load",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->x.load",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "x",
|
||
"targetHandle": "load",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->pc.inc",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "pc",
|
||
"targetHandle": "inc",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->pc.load",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "pc",
|
||
"targetHandle": "load",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->flags.latch",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "flags",
|
||
"targetHandle": "latch",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->mem.write",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "mem",
|
||
"targetHandle": "write",
|
||
"kind": "control"
|
||
},
|
||
{
|
||
"id": "ctl.signals->mem.read",
|
||
"source": "ctl",
|
||
"sourceHandle": "signals",
|
||
"target": "mem",
|
||
"targetHandle": "read",
|
||
"kind": "control"
|
||
}
|
||
],
|
||
"viewport": {
|
||
"x": 30,
|
||
"y": 215,
|
||
"zoom": 0.75
|
||
}
|
||
},
|
||
"isa": {
|
||
"description": "RETRO-8: accumulator-implicit instructions with immediate (#n), absolute (n), and X-indexed (STAX) addressing. Opcode in word 0, operand in word 1.",
|
||
"opcodeField": {
|
||
"offset": 0,
|
||
"width": 8
|
||
},
|
||
"instructions": [
|
||
{
|
||
"id": "r8-hlt",
|
||
"mnemonic": "HLT",
|
||
"opcode": 0,
|
||
"words": 1,
|
||
"operands": [],
|
||
"flagsAffected": [],
|
||
"doc": "Stop the machine.",
|
||
"microOps": [
|
||
{
|
||
"op": "halt"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-ldi",
|
||
"mnemonic": "LDI",
|
||
"opcode": 1,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "value",
|
||
"kind": "immediate",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Load an immediate into the accumulator.",
|
||
"microOps": [
|
||
{
|
||
"op": "move",
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"src": {
|
||
"kind": "operand",
|
||
"field": "value"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-ldx",
|
||
"mnemonic": "LDX",
|
||
"opcode": 2,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "value",
|
||
"kind": "immediate",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Load an immediate into the X index register.",
|
||
"microOps": [
|
||
{
|
||
"op": "move",
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "X",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"src": {
|
||
"kind": "operand",
|
||
"field": "value"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-lda",
|
||
"mnemonic": "LDA",
|
||
"opcode": 3,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "source",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Load the accumulator from a memory address (absolute addressing).",
|
||
"microOps": [
|
||
{
|
||
"op": "load",
|
||
"memory": "MEM",
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"addr": {
|
||
"kind": "operand",
|
||
"field": "source"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-sta",
|
||
"mnemonic": "STA",
|
||
"opcode": 4,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "target",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Store the accumulator to a memory address (absolute addressing).",
|
||
"microOps": [
|
||
{
|
||
"op": "store",
|
||
"memory": "MEM",
|
||
"addr": {
|
||
"kind": "operand",
|
||
"field": "target"
|
||
},
|
||
"src": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-stax",
|
||
"mnemonic": "STAX",
|
||
"opcode": 5,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "base",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Store the accumulator to base + X (indexed addressing).",
|
||
"microOps": [
|
||
{
|
||
"op": "alu",
|
||
"fn": "add",
|
||
"width": 8,
|
||
"dst": {
|
||
"kind": "temp",
|
||
"index": 0
|
||
},
|
||
"a": {
|
||
"kind": "operand",
|
||
"field": "base"
|
||
},
|
||
"b": {
|
||
"kind": "reg",
|
||
"bank": "X",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"setFlags": false
|
||
},
|
||
{
|
||
"op": "store",
|
||
"memory": "MEM",
|
||
"addr": {
|
||
"kind": "temp",
|
||
"index": 0
|
||
},
|
||
"src": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-addi",
|
||
"mnemonic": "ADDI",
|
||
"opcode": 6,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "value",
|
||
"kind": "immediate",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [
|
||
"Z",
|
||
"N",
|
||
"C"
|
||
],
|
||
"doc": "Add an immediate to the accumulator.",
|
||
"microOps": [
|
||
{
|
||
"op": "alu",
|
||
"fn": "add",
|
||
"width": 8,
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"a": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"b": {
|
||
"kind": "operand",
|
||
"field": "value"
|
||
},
|
||
"setFlags": true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-add",
|
||
"mnemonic": "ADD",
|
||
"opcode": 7,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "source",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [
|
||
"Z",
|
||
"N",
|
||
"C"
|
||
],
|
||
"doc": "Add the value at a memory address to the accumulator.",
|
||
"microOps": [
|
||
{
|
||
"op": "load",
|
||
"memory": "MEM",
|
||
"dst": {
|
||
"kind": "temp",
|
||
"index": 0
|
||
},
|
||
"addr": {
|
||
"kind": "operand",
|
||
"field": "source"
|
||
}
|
||
},
|
||
{
|
||
"op": "alu",
|
||
"fn": "add",
|
||
"width": 8,
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"a": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"b": {
|
||
"kind": "temp",
|
||
"index": 0
|
||
},
|
||
"setFlags": true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-cmp",
|
||
"mnemonic": "CMP",
|
||
"opcode": 8,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "value",
|
||
"kind": "immediate",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [
|
||
"Z",
|
||
"N",
|
||
"C"
|
||
],
|
||
"doc": "Compare: set flags from ACC - value without changing the accumulator.",
|
||
"microOps": [
|
||
{
|
||
"op": "alu",
|
||
"fn": "sub",
|
||
"width": 8,
|
||
"dst": {
|
||
"kind": "temp",
|
||
"index": 1
|
||
},
|
||
"a": {
|
||
"kind": "reg",
|
||
"bank": "ACC",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"b": {
|
||
"kind": "operand",
|
||
"field": "value"
|
||
},
|
||
"setFlags": true
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-beq",
|
||
"mnemonic": "BEQ",
|
||
"opcode": 9,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "target",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Branch when the Z flag is set (last result was zero / compare matched).",
|
||
"microOps": [
|
||
{
|
||
"op": "branch",
|
||
"flag": "Z",
|
||
"ifSet": true,
|
||
"target": {
|
||
"kind": "operand",
|
||
"field": "target"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-bne",
|
||
"mnemonic": "BNE",
|
||
"opcode": 10,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "target",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Branch when the Z flag is clear.",
|
||
"microOps": [
|
||
{
|
||
"op": "branch",
|
||
"flag": "Z",
|
||
"ifSet": false,
|
||
"target": {
|
||
"kind": "operand",
|
||
"field": "target"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-jmp",
|
||
"mnemonic": "JMP",
|
||
"opcode": 11,
|
||
"words": 2,
|
||
"operands": [
|
||
{
|
||
"name": "target",
|
||
"kind": "address",
|
||
"field": {
|
||
"word": 1,
|
||
"offset": 0,
|
||
"width": 8
|
||
}
|
||
}
|
||
],
|
||
"flagsAffected": [],
|
||
"doc": "Jump unconditionally.",
|
||
"microOps": [
|
||
{
|
||
"op": "jump",
|
||
"target": {
|
||
"kind": "operand",
|
||
"field": "target"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "r8-inx",
|
||
"mnemonic": "INX",
|
||
"opcode": 12,
|
||
"words": 1,
|
||
"operands": [],
|
||
"flagsAffected": [],
|
||
"doc": "Increment the X index register.",
|
||
"microOps": [
|
||
{
|
||
"op": "alu",
|
||
"fn": "add",
|
||
"width": 8,
|
||
"dst": {
|
||
"kind": "reg",
|
||
"bank": "X",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"a": {
|
||
"kind": "reg",
|
||
"bank": "X",
|
||
"index": {
|
||
"kind": "literal",
|
||
"value": 0
|
||
}
|
||
},
|
||
"b": {
|
||
"kind": "const",
|
||
"value": 1
|
||
},
|
||
"setFlags": false
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
"programs": [
|
||
{
|
||
"id": "r8-demo",
|
||
"name": "times table",
|
||
"source": "; RETRO-8 demo - fill MEM[96..100] with 10,20,30,40,50\n; using X-indexed stores.\n\n LDX #0 ; X = table index\n LDI #0 ; ACC = running value\nloop:\n ADDI #10 ; next multiple of ten\n STAX 96 ; MEM[96 + X] = ACC\n INX\n CMP #50 ; reached the last entry?\n BNE loop\n HLT ; MEM[96..100] = 10,20,30,40,50\n"
|
||
}
|
||
]
|
||
}
|