velxio/backend/app/services/rom_compile.py

200 lines
6.4 KiB
Python
Raw Normal View History

feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
"""ROM-compile service — turns a chip-program source file into raw ROM bytes.
Backs the `POST /api/compile-rom` endpoint that the frontend's "Compile" button
uses when the active file is a chip-program file (.s / .asm / .hex / .bin).
The output is base64-encoded bytes the frontend can stash in the chip's
`romBytes` property; the chip then reads them at chip_setup via
`vx_rom_size` / `vx_rom_read`.
Supported targets and formats:
target=8080 format=asm in-tree two-pass Intel 8080 assembler (asm8080.py)
target=* format=hex Intel HEX parser
target=* format=bin raw byte passthrough (already-compiled ROM)
Future: z80/8086/4004 assemblers and SDCC for C sources.
"""
from __future__ import annotations
import base64
import logging
from importlib import import_module
from typing import Literal
logger = logging.getLogger(__name__)
# Lazy-load the asm8080 module from this services dir so the import stays
# explicit (no implicit sys.path manipulation).
_ASM_MODULE = None
_ASM_Z80_MODULE = None
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
def _asm8080():
global _ASM_MODULE
if _ASM_MODULE is None:
_ASM_MODULE = import_module("app.services.asm8080")
return _ASM_MODULE
def _asmz80():
global _ASM_Z80_MODULE
if _ASM_Z80_MODULE is None:
_ASM_Z80_MODULE = import_module("app.services.asmz80")
return _ASM_Z80_MODULE
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
Target = Literal["8080", "z80", "8086", "4004"]
Format = Literal["asm", "hex", "bin"]
def parse_intel_hex(text: str) -> bytes:
"""Parse Intel HEX records into a flat byte buffer. Unknown record types
are skipped; data records (type 0x00) are placed at their declared address.
"""
out = bytearray()
for raw in text.splitlines():
line = raw.strip()
if not line.startswith(":"):
continue
try:
length = int(line[1:3], 16)
addr = int(line[3:7], 16)
rtype = int(line[7:9], 16)
except ValueError:
continue
if rtype == 0x01: # EOF record
break
if rtype != 0x00: # ignore extended-segment, start-address, etc.
continue
data_hex = line[9 : 9 + length * 2]
try:
data = bytes.fromhex(data_hex)
except ValueError:
continue
end = addr + len(data)
if end > len(out):
out.extend(b"\x00" * (end - len(out)))
out[addr:end] = data
return bytes(out)
def assemble_8080(source: str) -> bytes:
"""Two-pass Intel 8080 assembler. Returns raw ROM bytes."""
return _asm8080().assemble(source)
def assemble_z80(source: str) -> bytes:
"""Two-pass Zilog Z80 assembler. Returns raw ROM bytes."""
return _asmz80().assemble(source)
feat(chips): C-to-Z80 compile via SDCC + LED chaser example Adds a third format to /api/compile-rom: `c` (C source compiled by SDCC to Z80 bytes). Same chip-program flow as 8080/Z80 asm — write C in a project file, click Compile, click Run. Backend: - backend/app/services/c_compile.py — async SDCC wrapper. Locates the sdcc binary on PATH (or via SDCC env var, or common Windows install paths) and shells out with target=mz80 + --code-loc 0x100 --data-loc 0x8000. Parses the resulting Intel HEX into raw ROM bytes. Pure 8080 is rejected with a clear error (SDCC has no 8080 backend; Z80 ROMs also run on the i8080-cpu chip if you avoid Z80-only ops). - rom_compile.py: compile_rom is now async; the new c branch delegates to c_compile. compile_rom_endpoint awaits it. Frontend: - romCompileService: RomFormat gains 'c'; formatForFile maps .c/.cpp to 'c'. isChipProgramFile intentionally still excludes .c — disambiguation happens at the EditorToolbar level. - EditorToolbar: the chip-program path also fires when a custom-chip has programFile === activeFile.name (regardless of extension). That lets .c files route to /api/compile-rom (SDCC) when bound to a CPU chip, while .c files NOT bound to any chip continue to route to arduino-cli as before. Docker: - Dockerfile.standalone adds `sdcc` to the apt-get install list, so the prod image ships with SDCC out of the box. Example: - /examples/z80-led-chaser-c — z80-cpu chip + chaser.c (a Larson scanner written in C with __at() MMIO definitions). Compiles cleanly with SDCC's --code-loc 0x100 default crt0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:31:10 +07:00
async def compile_rom(source: str, target: Target, fmt: Format) -> dict:
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
"""Compile a chip-program source to ROM bytes.
Returns a dict shaped like:
{ success, rom_base64, byte_size, stderr, error }
"""
fmt_l = fmt.lower()
tgt_l = target.lower()
if fmt_l == "bin":
# Source may arrive as a hex string (frontend pre-encodes binary)
# or as raw text. Try hex-encoded first.
clean = "".join(source.split())
try:
data = bytes.fromhex(clean)
except ValueError:
data = source.encode("latin1")
return {
"success": True,
"rom_base64": base64.b64encode(data).decode("ascii"),
"byte_size": len(data),
"stderr": "",
"error": None,
}
if fmt_l == "hex":
try:
data = parse_intel_hex(source)
except Exception as e: # noqa: BLE001
return {
"success": False,
"rom_base64": None,
"byte_size": 0,
"stderr": "",
"error": f"Intel HEX parse failed: {e}",
}
return {
"success": True,
"rom_base64": base64.b64encode(data).decode("ascii"),
"byte_size": len(data),
"stderr": "",
"error": None,
}
if fmt_l == "asm":
if tgt_l == "8080":
try:
data = assemble_8080(source)
except Exception as e: # noqa: BLE001
return {
"success": False, "rom_base64": None, "byte_size": 0,
"stderr": "", "error": f"asm8080: {e}",
}
elif tgt_l == "z80":
try:
data = assemble_z80(source)
except Exception as e: # noqa: BLE001
return {
"success": False, "rom_base64": None, "byte_size": 0,
"stderr": "", "error": f"asm-z80: {e}",
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
}
else:
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
return {
"success": False, "rom_base64": None, "byte_size": 0,
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
"stderr": "",
"error": (
f"No assembler for target {target!r} yet. Supported: "
"8080, z80. Try uploading a .hex or .bin compiled with "
"your own toolchain."
),
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
}
return {
"success": True,
"rom_base64": base64.b64encode(data).decode("ascii"),
"byte_size": len(data),
"stderr": "", "error": None,
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
}
feat(chips): C-to-Z80 compile via SDCC + LED chaser example Adds a third format to /api/compile-rom: `c` (C source compiled by SDCC to Z80 bytes). Same chip-program flow as 8080/Z80 asm — write C in a project file, click Compile, click Run. Backend: - backend/app/services/c_compile.py — async SDCC wrapper. Locates the sdcc binary on PATH (or via SDCC env var, or common Windows install paths) and shells out with target=mz80 + --code-loc 0x100 --data-loc 0x8000. Parses the resulting Intel HEX into raw ROM bytes. Pure 8080 is rejected with a clear error (SDCC has no 8080 backend; Z80 ROMs also run on the i8080-cpu chip if you avoid Z80-only ops). - rom_compile.py: compile_rom is now async; the new c branch delegates to c_compile. compile_rom_endpoint awaits it. Frontend: - romCompileService: RomFormat gains 'c'; formatForFile maps .c/.cpp to 'c'. isChipProgramFile intentionally still excludes .c — disambiguation happens at the EditorToolbar level. - EditorToolbar: the chip-program path also fires when a custom-chip has programFile === activeFile.name (regardless of extension). That lets .c files route to /api/compile-rom (SDCC) when bound to a CPU chip, while .c files NOT bound to any chip continue to route to arduino-cli as before. Docker: - Dockerfile.standalone adds `sdcc` to the apt-get install list, so the prod image ships with SDCC out of the box. Example: - /examples/z80-led-chaser-c — z80-cpu chip + chaser.c (a Larson scanner written in C with __at() MMIO definitions). Compiles cleanly with SDCC's --code-loc 0x100 default crt0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:31:10 +07:00
if fmt_l == "c":
# C source via SDCC (Z80 only — pure 8080 has no SDCC backend; Z80
# is binary-compat with 8080 so the same .c can target both chips).
from app.services.c_compile import compile_c # lazy — keeps the
# import out of the
# asm/hex/bin path.
result = await compile_c(source, tgt_l if tgt_l in ("z80", "8080") else "z80")
rom = result.get("rom_bytes", b"")
if not result.get("success"):
return {
"success": False,
"rom_base64": None,
"byte_size": 0,
"stderr": result.get("stderr", ""),
"error": result.get("error", "C compile failed"),
}
return {
"success": True,
"rom_base64": base64.b64encode(rom).decode("ascii"),
"byte_size": len(rom),
"stderr": result.get("stderr", ""),
"error": None,
}
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
return {
"success": False,
"rom_base64": None,
"byte_size": 0,
"stderr": "",
feat(chips): C-to-Z80 compile via SDCC + LED chaser example Adds a third format to /api/compile-rom: `c` (C source compiled by SDCC to Z80 bytes). Same chip-program flow as 8080/Z80 asm — write C in a project file, click Compile, click Run. Backend: - backend/app/services/c_compile.py — async SDCC wrapper. Locates the sdcc binary on PATH (or via SDCC env var, or common Windows install paths) and shells out with target=mz80 + --code-loc 0x100 --data-loc 0x8000. Parses the resulting Intel HEX into raw ROM bytes. Pure 8080 is rejected with a clear error (SDCC has no 8080 backend; Z80 ROMs also run on the i8080-cpu chip if you avoid Z80-only ops). - rom_compile.py: compile_rom is now async; the new c branch delegates to c_compile. compile_rom_endpoint awaits it. Frontend: - romCompileService: RomFormat gains 'c'; formatForFile maps .c/.cpp to 'c'. isChipProgramFile intentionally still excludes .c — disambiguation happens at the EditorToolbar level. - EditorToolbar: the chip-program path also fires when a custom-chip has programFile === activeFile.name (regardless of extension). That lets .c files route to /api/compile-rom (SDCC) when bound to a CPU chip, while .c files NOT bound to any chip continue to route to arduino-cli as before. Docker: - Dockerfile.standalone adds `sdcc` to the apt-get install list, so the prod image ships with SDCC out of the box. Example: - /examples/z80-led-chaser-c — z80-cpu chip + chaser.c (a Larson scanner written in C with __at() MMIO definitions). Compiles cleanly with SDCC's --code-loc 0x100 default crt0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:31:10 +07:00
"error": f"Unknown format {fmt!r} — expected asm / hex / bin / c.",
feat(chips): programmable retro CPU chips with external ROM Adds a new way to use the retro CPU chips: write your program in a project file (.s / .asm / .hex / .bin), click Compile, click Run, and the same chip emulates whatever you wrote. Same chip + different ROMs = mini PC, calculator, LED demo, Kill-the-Bit game, etc. SDK: - velxio-chip.h gets two new host imports: uint32_t vx_rom_size(void); void vx_rom_read(uint32_t off, uint8_t* dst, uint32_t len); CPU-emulator chips call these in chip_setup to pull their program out of the host's romBytes property. Frontend runtime: - ChipRuntime accepts opts.romBytes (Uint8Array) and exposes the new imports, copying bytes into chip memory on vx_rom_read. - CustomChipPart pulls component.properties.romBytes (base64) and passes it through. - Component registry declares three new custom-chip properties: romBytes (base64), programFile (matching project filename), and programTarget (cpu name). New programmable bundled chip: - frontend/src/components/customChips/examples/intel/i8080-cpu.{c,chip.json} Same clean-room 8080 emulator as i8080-repl/i8080-counter, but ROM is loaded externally via vx_rom_*. Has 8 LEDs, 8 buttons, UART, 16 KB RAM, 32 KB of external ROM. Backend: - New /api/compile-rom endpoint and rom_compile service that turns chip-program source into ROM bytes. 8080 ASM is assembled by the in-tree two-pass assembler (moved to backend/app/services/asm8080.py). Intel HEX records are parsed; raw .bin is passed through. Future targets (z80, 8086, 4004) are scaffolded but not wired yet. EditorToolbar: - Compile button detects when the active file is .s/.asm/.hex/.bin and routes to compile-rom instead of arduino-cli. The compiled bytes are injected into every custom-chip on the canvas whose programFile property matches the active filename (or is empty). Example: - /examples/i8080-killbits loads Dean McDaniel's 1975 Kill-the-Bit on the programmable i8080-cpu chip. killbits.s is shipped as a project file alongside sketch.ino; the user clicks Compile then Run and the LED walks across 8 outputs, buttons kill it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 09:38:18 +07:00
}