DOOM on the ULX3S Part 1: Introduction
In the embedded systems world, it's a right of passage for hackers to get a piece of hardware to run DOOM. I was recently rummaging around my box of development boards and I rediscovered my awesome ULX3S which would be an awesome candidate for me to learn how to port DOOM. Not only is it a powerful dev board based around the Lattice ECP5 FPGA, but thanks to the amazing work of a huge community of people, it also has a fully open-source toolchain and a huge ecosystem of compatible gateware. Time to meet our DOOM.
FPGAs Are Pretty Neat (Especially This One)
If you aren't already familiar, one of my favorite ways of explaining the difference between a processor and a field-programmable gate array is this:
A processor executes code - it is provided instructions which tell it what to do. An FPGA implements digital logic - it is provided a circuit description which tells it what to become.
This is a profound philosophical distinction. This means that we can use a hardware description language (HDL) like Verilog or Amaranth to describe a processor, a communication bus, or any other digital circuit and the FPGA will reconfigure itself to function as that circuit.
So not only are FPGAs powerful by themselves, they can be made even more powerful with additional hardware. In addition to the ECP5, the ULX3S in this project also sports:
- 32MB SDRAM
- HDMI port (called GPDI or General-Purpose Differential Interface for licensing reasons)
- Audio jack
- Buttons, switches, & LEDs
- A bunch of GPIO
- FLASH memory
- A whole ESP32 module for Bluetooth and WiFi
- Lots more
There are several other DOOM-on-FPGA projects out in the wild, even one that implements the game in gateware (no CPU), and another that uses a custom processor instruction set architecture. My goal with this was to learn how to get all the way from the logic gates all the way to running applications in Linux only using open source tools. That's
FPGA -> SoC -> Bootloader -> OS -> DOOM all with FOSS tooling. Maybe one day I'll even run it on open-source silicon.
Planning to Plan
I'm breaking the project up into a few milestones to make sure that I have the tools set up correctly and that I can tweak things at the various layers without everything breaking. This is important because each piece relies on the pieces below it to function. The plan is thus:
- Accumulate resources like datasheets, manuals, tutorials, and similar projects to serve as reference materials
- Download and install the FPGA toolchain
- Flash a pre-built bitstream of the "Hello World" Blinky project to verify that my computer can communicate with the ULX3S
- Clone the Blinky project and run the synthesis, placing, and routing tools verify that HDL-to-bitstream generation works
- Modify the speed or pattern of the blinking LED in the source code and re-upload to verify basic modifications work
- Research SoC options for running Linux on an FPGA
- Download and flash pre-built SoC bitstream
- Clone design files, build bitstream, and flash to FPGA
- Explore source code and make a small modification to the design before building and uploading again
- Binary interface, bootloader, root FS, & OS
- Download and transfer via serial port pre-built SBI, root filesystem, and Linux system with included bootloader. Verify Linux is running.
- Clone source projects for each of the above. Build the items, and transfer them to the FPGA
- Explore source code and make a small modification to the design before building and uploading again. Also speed up the transfer process since the default method is very slow.
- Graphics on Linux
- Investigate the ways to support graphics in embedded Linux and the ULX3S
- Choose a solution and try to find pre-built binaries to test with
- Clone necessary projects and build the graphics stack necessary to run Doom
- Get a WAD file
- Clone Chocolate DOOM
- Build it natively and/or cross-compile it
- Play DOOM