Back to browse
GitHub Repository

RISC-V emulator that runs DOOM

30 starsC++

I built a RISC-V emulator that runs DOOM

by Flex247A·May 3, 2026·50 points·4 comments

AI Analysis

●●●BangerWizardryBig Brain

Running DOOM on a homegrown RISC-V core is a rite of passage executed well.

Strengths
  • Implements RV32IM ISA with ELF loading and newlib syscall stubs from scratch.
  • Successfully ports doomgeneric to a custom memory-mapped VRAM interface.
  • Clear milestone progression from assembly hello-world to playable C game.
Weaknesses
  • FENCE and EBREAK instructions are currently NOPs, limiting robustness.
  • Only supports single PT_LOAD segments, restricting complex binary loading.
Category
Target Audience

Systems programmers, emulator hobbyists, retro computing enthusiasts

Similar To

rv8 · Spike · QEMU

Post Description

Hi HN,

I built a RISC-V emulator that implements the RV32IM instruction set and a minimal syscall interface to run DOOM. A few weeks ago, I got my first output with a simple hello world assembly program.

Since then I have been working tirelessly to get DOOM to run.

I needed to figure out how to run C programs first, and came across newlib, which allows the underlying environment to implement the syscall stubs one by one until the programs run.

I have also added ELF loading, but currently only a single `PT_LOAD` segment is supported.

To port DOOM, I used doomgeneric, which was quite convenient to get working once the required stubs were in place.

DOOM renders to a fixed area in memory (0x705FDD = VRAM_START):

0x7FFFFF +-------------------------------------+ | | | QUEUE_SIZE (32 bytes) | | | 0x7FFFDF +-------------------------------------+ <-- QUEUE_START 0x7FFFDE | QUEUE_READ_IDX | 0x7FFFDD | QUEUE_WRITE_IDX | +-------------------------------------+ | | | | | VRAM (1,024,000 bytes) | | | | | 0x705FDD +-------------------------------------+ <-- STACK_START | Stack | | | | | v | | | | ^ | | | | | Program data + Heap | | | 0x000000 +-------------------------------------+

I made a small linker script so that the entry point of a C program is at _start and virtual address is always 0. That kept the ELF loader code simple.

Inputs are written to the queue by rvcore which are then intercepted by DOOM running inside it.

Demo link: https://www.youtube.com/watch?v=f5uygzEmdLw

Similar Projects