velxio/backend/app/api/routes/compile_rom.py

57 lines
1.7 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
"""POST /api/compile-rom — assemble or convert a chip-program source to ROM bytes.
Used by Velxio's "Compile" button when the active file is a chip-program file
(`.s` / `.asm` / `.hex` / `.bin`). The compiled ROM is base64 bytes that the
frontend stashes in the chip's `romBytes` property; the chip reads them on
chip_setup via the new `vx_rom_size` / `vx_rom_read` SDK calls.
Request body:
source: str chip-program source (asm text, hex text, or bin-as-hex)
target: str "8080" | "z80" | "8086" | "4004"
format: str "asm" | "hex" | "bin"
Response (mirrors compile_chip.py shape):
success: bool
rom_base64: str | null
byte_size: int
stderr: str
error: str | null
"""
from __future__ import annotations
import logging
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from app.services.rom_compile import compile_rom
logger = logging.getLogger(__name__)
router = APIRouter()
class RomCompileRequest(BaseModel):
source: str
target: str = "8080"
format: str = "asm"
class RomCompileResponse(BaseModel):
success: bool
rom_base64: str | None = None
byte_size: int = 0
stderr: str = ""
error: str | None = None
@router.post("/", response_model=RomCompileResponse)
async def compile_rom_endpoint(request: RomCompileRequest):
if not request.source.strip() and request.format != "bin":
raise HTTPException(status_code=422, detail="`source` cannot be empty.")
try:
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
result = await compile_rom(request.source, request.target, request.format) # type: ignore[arg-type]
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
except Exception as e: # noqa: BLE001
logger.exception("ROM compile failed")
raise HTTPException(status_code=500, detail=str(e))
return RomCompileResponse(**result)