Files
WebMetal/examples/retro-8/retro-8.webmetal.json
T
2026-07-19 18:05:04 +02:00

861 lines
20 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 instructions 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 MEMs 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"
}
]
}