This is a compact open-source FPGA game console targetting the Lattice iCE40 UltraPlus series. It's being developed using the open-source yosys and nextpnr tools and can be run on both the iCEBreaker** and ULX3S* boards.
As a retro-inspired console, it does not use framebuffers, rasterizers, shaders etc. It uses character maps, sprites and raster effects to implement various effects. This allows it to render 480p scenes from 64KByte.
It is feature-complete and confirmed working on both the iCEBreaker and ULX3S targets. Remaining work is mainly cleanup, adding more supported gamepads, more demos and possibly other optional non-core features.
*: Only the ECP5-45F and 85F have the 128kbyte of block RAM required. It's been confirmed working on boards with Winbond flash but thought to work on others too. Potentially boards with a 25F can be supported by moving the CPU RAM to external memory.
**: iCEBreaker requires the additional 12bpp HDMI PMOD for video output. On the ULX3S, the onboard GPDI port is used for video output.
Platformer game hosted here.
Demo with placeholder assets just for illustration. This along with other similarly copyrighted assets are not published here.
- RISC-V CPU (configurable with VexRiscV or PicoRV32)
- Custom VDP for smooth-scrolling layers and sprites
- Custom "copper" coprocessor integrated into VDP to perform raster effects
- Custom multichannel ADPCM decoder / mixer (44.1KHz, 16bit stereo)
- Configurable video modes of 640x480@60hz or 848x480@60hz
- 4bpp graphics assembled from 8x8 tiles
- ARGB16 colors arranged into 16 palettes of 16 colors each
- Optional alpha blending using 4bit alpha intensity**
- 3x or 4x scrolling layers up to 1024x512 pixels each*
- 1x 1024x1024 pixel affine-transformable layer*
- 256x sprites of up to 16x16 pixels each
- 1060+ sprite pixels per line depending on clock and video mode
- 64kbyte of CPU RAM (2x iCE40 SPRAM)
- 64kbyte VDP RAM (2x iCE40 SPRAM)
- (S)NES-compatible pad interface***
The system doesn't require any RAM beyond what is available in the iCE40 UP5K.
*: Only one of these layer types can be enabled at any given time but they can be toggled multiple times in a frame using raster-timed updates. The 4x layer implementation is still included in this repo but was disabled due to VRAM usage constraints.
**: Visual artefacts can been seen if more than one alpha-enabled layer intersects with another i.e. using overlapping sprites.
***: PCB push buttons are used by default. although an original SNES gamepad can be used.
- yosys
- nextpnr (-ice40 for iCEBreaker, -ecp5 for ULX3S)
- icetools (if building for iCEBreaker)
- GNU RISC-V toolchain (newlib)
- fujprog (if flashing to ULX3S)
The CROSS
variable in software/common/cross.mk
must be set according to the name of your RISC-V installation.
While the RISC-V toolchain can be built from source, the PicoRV32 repo includes a Makefile with convenient build-and-install targets. This project only uses the RV32I
ISA. Those with case-insensitive file systems will likely have issues building the toolchain from source. If so, binaries of the toolchain for various platforms are available here.
The open-tool-forge fpga-toolchain project also provides nightly builds of most of these tools.
cd hardware
make icebreaker_prog
cd hardware
make ulx3s_prog
Demo software is included under /software
and can be programmed separately. For example, to build and program the sprites
demo:
cd software/sprites/
make icebreaker_prog
cd software/sprites/
make ulx3s_prog
Note that fujprog can be slow to flash larger files. Programming over WiFi using the ESP32 can be done as a faster alternative.
More detailed hardware documentation for the platform can be found in doc/platform.md.
A simulator using SDL2 and its documentation is included in the /simulator directory.
Demo software and simulator screenshots are included in the /software directory.
One of two RISC-V implementations can be selected. The implementation is chosen using the boolean USE_VEXRISCV
parameter in the ics32
module.
- PicoRV32: Enabled when
USE_VEXRISCV=0
. This was the original choice and the rest of the system was designed around its shared bus interface. While it is not as compact as the Vex, it is much faster to run in simulators as it is fully synchronous. - VexRiscV (default): Enabled when
USE_VEXRISCV=1
. This was a later addition. A wrapper module is used to bridge between the Vex split IBus/DBus and the Pico-compatible shared bus. It is the more compact CPU which is especially important considering the size of the UP5K target.
One of two video modes can be selected and only when the project is built. The video modes can't be toggled in software. The VIDEO_MODE
variable in /hardware/Makefile selects which video mode to use. This Makefile variable in turn determines the value of the ENABLE_WIDESCREEN
parameter in the ics32
module.
- 640x480@60hz: 25.175MHz VDP/CPU clock
- 848x480@60hz: 33.75MHz VDP clock, 16.88MHz CPU clock
The 848x480 video mode has two clock domains because the UP5K cannot run the CPU at 33.75MHz while also meeting timing. For the icebreaker
target, the dual clock domain setup is automatically enabled when ENABLE_WIDESCREEN
is set. This can otherwise be manually configured using the ENABLE_FAST_CPU
parameter.
If the ENABLE_FAST_CPU
parameter is set, there is only one clock domain with the CPU running at the VDP clock. Otherwise, the CPU runs at half the VDP clock as shown above.
The ULX3S can run the CPU at the video clock (and beyond) but it defaults to the same configuration as the iCEBreaker for consistency. This also means software running on one platform will behave identically on the other.
The GAMEPAD_PMOD
parameter enables audio output using the 1BitSquared gamepad / audio PMOD. A stereo PDM DAC is used with this PMOD. This is the only option available for audio on the iCEBreaker.
Audio is output through the headphone jack in both analog / digital form:
- Analog output direct to headphones (noisy)
- Digital SPDIF using a 3.5mm -> RCA cable. The "composite video" cable can be plugged into the coax-in of an SPDIF receiver.
The assumed controller layout matches an original SNES gamepad.
By default, the controller is implemented using push buttons on the PCBs. Both the ULX3S and iCEBreaker buttons (on break out board) can be used but inputs are limited.
The GAMEPAD_PMOD
parameter in the top level module can be set to use an original SNES gamepad rather than the 3 buttons on the breakout board.
The GAMEPAD_SOURCE
parameter in the top level module sets the controller input. This string must be set to one of three values:
PCB
: PCB buttons (default, limited inputs)USB
: USB gamepad (if the gamepad to be used has a corresponding HID report decoder)BLUETOOTH
: Bluetooth gamepad
For the USB
option, HID report descriptors aren't parsed so a fixed layout is assumed. More gamepads can be added over time since the only addition needed is a HID report decoder. The USB_GAMEPAD
parameter selects which decoder to use and this file shows which gamepads are supported.
For the BLUETOOTH
option, an ESP32 program must be flashed first. A separate repo has the instructions on setting this up. The BLUETOOTH
option will not do anything useful unless the ESP32 program in the linked repo is flashed first. If there's issues getting a Bluetooth gamepad to connect, pressing button 0 will reset the ESP32 (without resetting the rest of the system). This usually fixes any connection problems.
The GAMEPAD_LED
parameter can also be set to show some some of the current button inputs. There are only 8 LEDs so not all can be seen at once. The CPU controlled LED state is otherwise shown.
The logic resources used can vary a lot based on which commit was used to build yosys. This can break UP5K builds if the LC usage is too high.
yosys commit c75d8c74 produces this result which fits and routes reasonably quick:
Info: Device utilisation:
Info: ICESTORM_LC: 4790/ 5280 90%
- A better README file!
- Many bits of cleanup and optimization
- Support for more USB gamepads
- Confirm ULX3S boards with ISSI flash work as expected