A RISC-V fantasy console — written in Mere
Two Mere programs, all the way down. The cartridge is a
Mere program compiled by mere -rv straight to a flat
RV32IM binary — no assembler, no linker. The
console is the Mere-written RISC-V emulator (the same
RV32IM core as memu) compiled
to WebAssembly. So this page is a Mere-authored CPU, running in the
browser, executing a Mere-authored game.
Use the arrow keys to move the cyan block around the
field (the yellow dots are just scenery). Every frame, the emulator
refreshes a memory-mapped input register from your held keys, runs the
RV32IM cartridge until it calls present, then blits the
64×32 framebuffer to the <canvas>.
How the two halves meet
The cartridge and console share a tiny hardware contract — three
operations the mere -rv backend lowers to bare machine code
and memory-mapped I/O:
fb_set x y v— store shadev(0..7) at framebuffer pixel(x, y): a byte write to0x7F8000 + y*64 + x.key n— read the held state of buttonn(0=right, 1=left, 2=up, 3=down): a byte read from the input register at0x7F9000 + n, which the host refreshes each frame fromdom_key_held.present ()— end the frame and yield to the host via anecall(a7=100). The CPU resumes on the next instruction next frame, so the cartridge's main loop is a coroutine driven byrequestAnimationFrame— the player position lives on the RISC-V call stack, not in any global.
What's happening
mere -rv game.mere > game.binlowers the cartridge to a ~3 KB flat RV32IM binary.mere -w -I . rvconsole.merelowers the RV32IM emulator to WebAssembly.- The page
fetchesgame.binand hands it to the glue (setRom); the module reads it a byte at a time throughdom_rom_size/dom_rom_byteinto its emulated RAM at address 0. - Each
requestAnimationFramepolls the keys, runs the CPU to the nextpresent, and repaints the framebuffer viadom_canvas_fill_rect.