Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Can It Run Doom? — Doom on egos-2k+ (RISC-V 32-bit)

Porting Doom to a tiny teaching operating system running on an emulated 32-bit RISC-V CPU.

Doom running on egos-2k+ — click to watch the full demo

Watch the full demo on YouTube

egos-2k+ is a ~2,000-line microkernel-style OS (earth / grass / apps layers) built for a graduate Operating Systems course. Out of the box it boots to a shell over a serial console — no graphics, no input beyond the UART, no sound, and a read-only filesystem. This project adds the OS-level machinery a general-purpose platform needs and uses Doom as the end-to-end proof that it all works together: a QEMU ramfb framebuffer driver, a virtio-input keyboard, a virtio-sound audio pipeline, a demand-paged heap, a read/write filesystem with POSIX file syscalls, and the syscalls that tie them to user space.

Result: a full first-level Doom playthrough at ~28 FPS average with working graphics, keyboard, OPL3-emulated music + SFX, and on-disk savegames — all on an emulated RV32 machine with 32 MiB of RAM.

Course: Operating Systems Implementation (CS6640). This was an open-ended final project built on top of the semester's lab work.


The question

canitrundoom.org catalogs the absurd range of platforms that have been made to run Doom. Getting there is a genuinely good systems exercise: Doom needs to draw pixels, read input, play sound, allocate memory dynamically, keep time, and persist files. A platform that can run Doom has, almost by definition, implemented the core of a general-purpose OS.

Doom is ported here via doomgeneric, which reduces a platform port to five callbacks:

DG_Init  DG_DrawFrame  DG_SleepMs  DG_GetTicksMs  DG_GetKey

Standing those five up required building out the rest of the OS underneath them.

What I built

Area Files What it does
Framebuffer earth/dev_ramfb.c, earth/boot.c QEMU ramfb driver: negotiates an XRGB8888 framebuffer over the fw_cfg interface and blits pixels from a user backbuffer by page-walking the caller's page table. Boot splash + restore-on-exit.
Keyboard grass/virtio_kbd.c virtio-input driver: sets up the event/status virtqueues, decodes evdev (code, value) events, filters auto-repeat, and buffers discrete down/up events (Doom needs key-release events, which stock egos couldn't detect).
Audio grass/virtio_snd.c, doomgeneric/i_egossound.c, doomgeneric/i_oplmusic.c, doomgeneric/opl/* virtio-sound TX pipeline (ring buffer + timer-driven drain) plus a cut-down Chocolate Doom OPL3/MIDI layer, so MUS music and SFX play without a real OPL chip or SDL_mixer. 44.1 kHz stereo S16.
Memory library/libc/malloc.c, grass/ipc.c SYS_MAP_HEAP_PAGE demand-paged heap extension wired into newlib _sbrk, so Doom's zone allocator and malloc can grow.
Filesystem (RW) library/file/rwfs.c, fs.c, apps/system/sys_file.c, library/libc/syscalls.c Create / unlink / rename on top of the rwfs primitive; a fix so indirect-block pointers survive writes; from-scratch newlib POSIX stubs (_open, _read, _write, _lseek, _unlink, rename, mkdir) that transparently span the read-only treedisk and the read/write filesystem. Enables Doom savegames and config files.
Syscalls library/syscall/syscall.{h,c}, grass/ipc.c, grass/kernel.c New syscalls: SYS_SLEEP, SYS_RAMFB_DRAW, SYS_RAMFB_RESET, SYS_MAP_HEAP_PAGE, SYS_KBD_GET_EVENT, SYS_AUDIO_WRITE. Timer-interrupt hooks keep the keyboard and audio rings drained even when no app is polling.
Image / build tools/mkfs.c, library/elf/app.lds, Makefile Packs DOOM1.WAD + boot splash + boot chime into the disk image at fixed inodes; rebalances the user ELF layout to 512 K code / 512 K data so the Doom binary fits; bumps QEMU RAM 8 → 32 MiB and wires up the ramfb / virtio-kbd / virtio-snd devices.
Doom backend doomgeneric/doomgeneric_egos.c Implements the five DG_* callbacks against the new syscalls and maps evdev key codes to Doom key values.

Host-side helpers scripts/convert_xr24.py and scripts/wav2dmx.py generate the boot-splash image (assets/egos_logo.bin) and DMX boot chime (assets/boot_sfx.lmp) that mkfs packs into the image. The converted assets are committed, so the build itself needs no Python — re-run the scripts (no arguments) only after editing the source assets/egos.png or assets/oxp.wav.

Repository layout

earth/        # hardware abstraction layer (drivers, boot, CPU/MMU)
grass/        # kernel: scheduler, IPC, syscalls, virtio drivers
apps/         # system servers + user programs (shell, benchmarks, ...)
library/      # libc/newlib glue, filesystem, ELF loader, syscall wrappers
doomgeneric/  # vendored doomgeneric + egos backend + OPL music (see NOTICE)
tools/        # mkfs (builds the on-disk image)
scripts/      # host-side asset converters (png -> XR24, wav -> DMX)
assets/       # boot logo + chime: sources (egos.png, oxp.wav) + converted output
Makefile      # top-level build + `make qemu`
setup.sh      # downloads the RISC-V toolchain + QEMU for your platform
env.sh        # puts the RISC-V toolchain + QEMU on your PATH

Build & run

Requirements: macOS (Intel or Apple Silicon) or Linux/WSL (x64 or arm64). You need git, make, a C compiler for host tools, and curl (or wget). The build uses a prebuilt RISC-V cross-compiler and a RISC-V build of QEMU from xpack-dev-tools; setup.sh fetches the right build for your platform automatically.

git clone <this-repo> egos-doom && cd egos-doom
./setup.sh            # detects your OS/arch, downloads + verifies the toolchains
source ./env.sh       # once per shell, from the repo root (puts them on PATH)
make qemu             # cross-compile the OS + apps + Doom, then boot in QEMU

At the egos shell prompt, type doomgeneric to launch Doom. Quit QEMU with Ctrl-a then x.

setup.sh pins the exact toolchain versions this project was built with (RISC-V GCC 14.2.0-3, RISC-V QEMU 8.2.2-1), verifies their SHA-256 checksums, and unpacks them into the repo root (they're git-ignored). env.sh discovers them with a glob, so there's nothing to edit by hand. Windows users: run everything inside WSL.

macOS may block the toolchain binaries on first run (Gatekeeper). Allow them under System Settings → Privacy & Security, or run xattr -dr com.apple.quarantine xpack-*.

Results

Measured on a 2019 Intel MacBook Pro (numbers from final_proj.txt):

Doom — first-level playthrough (~5 min of real gameplay, 8,640 frames):

Metric Value
Average FPS 28.21
1% low FPS 8.38 (worst frame 592 ms, the level load)

Close to Doom's 35 Hz target and comfortably playable. The playtest is the real benchmark — it exercises every subsystem at once (framebuffer draws, keyboard events, OPL3 + SFX audio, sys_sleep timing, demand-paged heap, and rwfs savegame writes) for sustained minutes with no dropped events and no filesystem asserts firing even after thousands of writes past the direct-block boundary.

Microbenchmarks (in apps/user/, run from the egos shell):

Benchmark Result
bench_fb — framebuffer throughput 1,174 frames/s, 286 MiB/s (320×200 XRGB8888)
bench_kbd — keyboard events 20/20 events captured (10 down, 10 up), none lost
bench_audio — audio TX 100% of submitted bytes accepted at 44.1 kHz stereo S16, no ring overflow
bench_heap — demand-paged heap ~90 µs/malloc, ~1 µs/page first-touch, ~1.5 GiB/s memset
bench_rwfs — read/write FS 314 KiB/s write, 441 KiB/s read (IPC-bound, not bandwidth-bound)

Debugging

The repo ships a .gdbinit for source-level kernel debugging over QEMU's gdb stub (target remote 127.0.0.1:6640, RV32 architecture, symbols from build/release/egos.elf).

Attribution & license

This repository combines code under two licenses:

  • egos-2k+ / egos — the base operating system, MIT licensed. See LICENSE. Derived from Cornell's egos-2000 (Yunhao Zhang, Robbert van Renesse et al.) and the CS6640 course variant (Cheng Tan). My contributions are the drivers, syscalls, filesystem, and Doom integration described above.
  • doomgeneric / Doom — the doomgeneric/ directory is vendored from ozkl/doomgeneric and the id Software Doom source, distributed under the GNU GPL v2. The OPL/MIDI code is adapted from Chocolate Doom (also GPL v2). See doomgeneric/NOTICE.md. DOOM1.WAD is the freely redistributable shareware IWAD.

About

Doom ported to egos-2k+, a ~2,000-line RISC-V (RV32) teaching OS: QEMU ramfb framebuffer, virtio keyboard/sound, demand-paged heap, and a read/write filesystem. ~28 FPS on emulated RISC-V.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages