Setting up the ICE-V Wireless ESP32-C3 RISC-V + iCE40 FPGA development board with the Award Winning SERV RISC-V Core.


What’s a SERV Core? It’s an Award Winning tiny RISC-V soft CPU on an FPGA that was designed by Olaf Kindgren. See for more info.

See also my micro-blog thread on Twitter related to the ICE-V board. Hopefully I’ll capture all of the key details here as well.

Disclaimer: I received a free ICE-V Wireless board as described in my prior blog. I have not been compensated in any other way. Opinions expressed are my own. That said, it is a pretty cool board. It is available on a GroupGets campaign that ends Tue, 16 Aug 2022 16:42:49 PDT.

This blog is still WIP and needs a bit of cleanup.


Espressif ESP-IDF and/or VisualGDB is used to build and flash the ESP32 software.

SERV Core (see below regarding pending PR for ICE-V board)

FuseSoC Cores (see below regarding PR for ICE-V board)

Write to FPGA with See SENDBIT setting in the icestorm Makefile.

ICE-V Wireless FPGA pcf file.

USB Configuration

A USB Driver Utility such as Zadig or the Sysprogs USB Driver Tool is essential for Windows users. After all these years, Microsoft still hasn’t figured out the whole USB thing. Even after getting a device working, a reboot later (typically after a Windows update) and Zadig will likely be needed again to assign the proper drivers.

See my prior blogs such as iCE40 FPGA Programming with WSL and Programming FPGA Devices from WSL.

In the case of the ICE-V Wireless, here’s what my driver list looks like:


Yes, there’s Interface 0 and Interface 2 but NO Interface 1:


The devices looks like this in Windows Device Manager:



Reminder that unlike the Radiona ULX3S that has an FPGA that sits IN FRONT of the ESP32 (requiring a special FPGA passthru to program the ESP32) - in contrast, the ICE-V Wireless has the FPGA sitting BEHIND the ESP32-C3, requiring a special app on the ESP32 to program the FPGA:

# enable the EDS-IDF environment
. $HOME/esp/esp-idf/

cd /mnt/c/workspace/
git clone

cd ICE-V-Wireless/Firmware build

I have an interim VisualGDB project for the ESP32-C3 Firmware app with working code to receive the FPGA binary and write to the local iCE40. Note this is currently based on an older branch, as I had encountered some problems with both my local ESP-IDF 4.4.1 as well as my ESP-IDF 5.0 that are as of yet, unresolved.

There’s a stable release of ESP-IDF v4.4.2 available on GitHub.

The ICE-V-Wireless is currently my upstream. Fetching my ICE-V-Wireless fork.

As a Windows/WSL user, I’m venturing into somewhat new territory for SERV & FuseSoC. Fortunately Olaf, Michael, Eric, and others are patient and kind in helping newbies like me. Thank you.

Potential Challenges

Out of that gate, I encountered a variety of challenges, presented here so that others may avoid:

One may be somewhat specific to me, but heads up the detection of Python 2.7 vs 3.x is not as robust as it could be. TL;DR: be sure to use Python 3.x and make sure that python --version returns a modern version and not the 2.7 one for best results.

The next problem is regarding fusesoc duplicates. Perhaps also petty, but this could be important, particularly during versioning conflicts. The fusesoc library list command will readily identify duplicate libraries.

Similarly related to Python 2.x vs 3.x challenge is the pip vs pip3 option. In my case it was best to explicitly state pip3 despite the instructions indicating jsut pip. See SERV Issue #83 for more detail if you encounter pip install problems.

Despite all the challenges related to my environment, I’d like to again extend thanks to Olaf for being so kind in reaching out to help & offer suggestions.

At the moment, my PR’s are still fresh off the press and not yet merged. Thus to follow along with the ICE-V Wireless, my forks will be needed.

Stay tuned for SERV PR #88 and FuseSoC Cores PR #15 merge that each that adds ICE-V Support. Additionally there’s a FuseSoc Blinky PR #89 which also adds ICE-V Support. Most recently emeb submitted PR #90 as discussed on Discord channel regarding the POR (Power On Reset) timing.

Note that I manually added the POR initial condition locally; it is not in my fork nor PR.

Key Technical Details

Here are some key documents for reference:

Technical Notes

Here are some gems gleaned during the Discord chat:

The PicoRV32 core that’s used in this design doesn’t have JTAG debugging capabilities. There may be others out there that do, but this one is very bare-bones to keep the resource use down.

Upside is that the firmware is stored in EBR that’s loaded with the bitstream and the ice40 tools allow changing the RAM contents w/o fully rebuilding, so the firmware development cycle is pretty fast. Look in the Makefile - there’s a target called recode that will recompile firmware, update the bitstream and reload it to hardware in a blink. There’s also a 115200kbps UART in there that you can printf() to.

Currently there’s 64kB of RAM and 8kB of ROM in the soft MCU system on the FPGA. That can be expanded somewhat if needed.

The PSRAM is not mapped into the addressable space in this system, but there are examples of how to do that out there.

The LY68L6400 is 64Mb - equal to 8MB.

The FPGA image and all the ESP32C3 code is stored in a 4MB flash that’s integrated into the Mini module. That’s entirely separate from what happens w/ the FPGA.

The UP5k can load itself at reset from an external SPI flash chip, but on the ICE-V-Wireless we don’t let it do that - the bitstreams come exclusively from the C3 over it’s SPI port. The PSRAM is not at all involved in this.

_when you’re sending a bitstream to the board with the script there are two different options:

  • if you don’t specify -f then the bitstream is immediately loaded into the FPGA
  • if you do specify -f then the bitstream is saved in SPIFFS and loaded upon reboot. So if you’re using the -f option you’re not immediately seeing the latest bitstream you sent._

ATM the only non-WiFi way to put bitstreams on is by building them into the SPIFFS filesystem at compile / flash time.

– emeb from discord

Fusesoc Blinky

I’ve added ICE-V Wireless support in my gojimmypi/fusesoc-blinky in my fusesoc/blinky.


  • FuseSoC FPGA core does not currently start when first loaded.

  • Core only starts after reset.

  • Unless --flash is used, prior FPGA binary image load is lost at reset.

This means that FuseSoC blinky requires the --flash parameter, and the board must be reset or power cycled to actually start the blinky.

See the Discord chat for a discussion on Power-On-Reset (POR) timing.

To use the FuseSoc Blinky: In a new, empty fusesoc project directory:

cd /mnt/c/workspace

#fetch ICE-V Wireless, if not already there:
git clone

mkdir myfusesocblinky
cd myfusesocblinky

fusesoc library add fusesoc_cores
fusesoc library add serv
fusesoc library add blinky

# similation
fusesoc run --target=sim fusesoc:utils:blinky

# build for ICE-V Wireless
fusesoc run --target=icev_wireless fusesoc:utils:blinky

python3 ../ICE-V-Wireless/python/ -a --flash ./build/fusesoc_utils_blinky_1.1.1/icev_wireless-icestorm/fusesoc_utils_blinky_1.1.1.bin


# These steps were performed in WSL on Windows:

cd /mnt/c/workspace
git clone
cd ICE-V-Wireless
git remote add upstream
git fetch upstream
git pull upstream main
git push

pip3 install fusesoc

mkdir /mnt/c/workspace/mynewfusesoc
cd /mnt/c/workspace/mynewfusesoc

# this will:
# git clone fusesoc_libraries into current directory
# git clone serv fusesoc_libraries into current directory
# create a default fusesoc.conf
# note these are from the gojimmypi fork, pending merge of the respective PRs:
fusesoc library add fusesoc_cores
fusesoc library add serv

fusesoc run --target=icev_wireless servant

# see the files in C:\workspace\mynewfusesoc\build\servant_1.1.0\icev_wireless-icestorm

../ICE-V-Wireless/python/ --flash ./build/servant_1.1.0/icev_wireless-icestorm/servant_1.1.0.bin

SERVANT Hello (not currently fully operational; Seems to be POR timing issue)

# with no --memfil parameter,  zephyr_hello.hex is used. 
# From log: Preloading $paramod...\servant_ram from zephyr_hello.hex
fusesoc run --target=icev_wireless servant --memfile=./zephyr_hello.hex
cp ./build/servant_1.1.0/icev_wireless-icestorm/servant_1.1.0.bin ./servant_hello.bin
python3 ../ICE-V-Wireless/python/ -a ./servant_hello.bin

Of interest is the pin definition file used by fusesoc. In my case, once installed, is found here:


With just a couple of lines of FPGA pin definitions:

# 12 MHz clock
set_io i_clk        35

# RS232
set_io q 9

# use q 39 for red, q 40 for green, q 41 for blue LED

FuseSoC Zephyr

There’s some discussion on the Discord channel regarding the blinky hex, but the zephyr_hello.hex is confirmed to work with the ICE-V Wireless:

# from your fusesoc project directory:
fusesoc run --target=icev_wireless servant --memfile=fusesoc_libraries/serv/sw/zephyr_hello.hex
python3 ../ICE-V-Wireless/python/ -a --flash ./build/servant_1.1.0/icev_wireless-icestorm/servant_1.1.0.bin

GitHub maintenance

A reminder for

cd /mnt/c/workspace/fusesoc-cores

# one-time:
git remote add upstream

git fetch upstream
git pull upstream master
git push

cd /mnt/c/workspace/serv

# one-time:
git remote add upstream
git fetch upstream
git pull upstream main
git push


Although the board is indeed called a Wireless FPGA, I found it a bit awkward to not have an optional wired FPGA bitstream upload capability. This also means during any ESP32 development effort, any FPGA updates will require the ESP32 chip to be running the bitstream loader. It could of course be running in an RTOS thread.

The RGB LEDs connected to the FPGA are connected directly to the FPGA. Modern LEDs can be rather bright. Keep this in mind to avoid any eye injuries. (kidding, it is not that bright, but still brighter than you might expect, with no current-limiting resistors)


Keep in mind that the board version used was prior to completion of the GroupGets campaign. Software and hardware may well be revised to address issues. Some problem were clearly related to my system config. ymmv.

IndexError: index out of range

The IndexError: index out of range error seemed to typically occur if there’s a weak WiFi signal and the connection is lost during transfer. The current code version does not seem to recover well and needed a restart to successfully upload.

$ python3 ./ -a -f servant.bin
Size of servant.bin is 104090 bytes
Traceback (most recent call last):
  File "./", line 197, in <module>
    send_file(args[0], cmmd, addr, port)
  File "./", line 34, in send_file
    if reply[0] :
IndexError: index out of range

Other stuff

fusesoc library add servant
fusesoc library add fusesoc-cores
fusesoc run --target=icev_wireless servant

fusesoc core list | grep blink

There were some other issues that are here for reference only. More investigation is needed to determine if the

fusesoc:utils:blinky:0                 : empty
fusesoc:utils:blinky:1.0               : empty
fusesoc:utils:blinky:1.1               : empty

blinky not found:

$ fusesoc core list | grep blink
fusesoc:utils:blinky:0                 : empty
fusesoc:utils:blinky:1.0               : empty
fusesoc:utils:blinky:1.1               : empty
$ fusesoc run --target=icev_wireless fusesoc:util:blinky
ERROR: 'fusesoc:util:blinky' or any of its dependencies requires 'blinky', but this core was not found

blinky still not found after manual load:

$ fusesoc library add blinky
INFO: Cloning library into fusesoc_libraries/blinky
Cloning into 'fusesoc_libraries/blinky'...
remote: Enumerating objects: 605, done.
remote: Counting objects: 100% (252/252), done.
remote: Compressing objects: 100% (65/65), done.
remote: Total 605 (delta 195), reused 200 (delta 182), pack-reused 353
Receiving objects: 100% (605/605), 115.45 KiB | 42.00 KiB/s, done.
Resolving deltas: 100% (314/314), done.
$ fusesoc run --target=sim fusesoc:util:blinky
ERROR: 'fusesoc:util:blinky' or any of its dependencies requires 'blinky', but this core was not found
$ fusesoc run --target=icev_wireless fusesoc:util:blinky
ERROR: 'fusesoc:util:blinky' or any of its dependencies requires 'blinky', but this core was not found

tool=modelsim fails:

$ fusesoc run --target=icebreaker tool=modelsim fusesoc:util:blinky
Traceback (most recent call last):
  File "/home/gojimmypi/.local/bin/fusesoc", line 11, in <module>
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 776, in main    args.func(cm, args)
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 374, in run
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 393, in run_backend
    core = _get_core(cm, system)
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 45, in _get_core
    core = cm.get_core(Vlnv(name))
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 267, in get_core
    c = self.db.find(name)
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 73, in find
    found = self._solve(vlnv, only_matching_vlnv=True)[-1]
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/fusesoc/", line 154, in _solve
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/simplesat/constraints/", line 149, in _from_string
    named_constraints = parser.parse(string, version_factory)
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/simplesat/constraints/", line 224, in parse
    tokens_blocks = _tokenize(self._scanner, requirement_string)
  File "/home/gojimmypi/.local/lib/python3.6/site-packages/simplesat/constraints/", line 154, in _tokenize
    raise InvalidConstraint(msg.format(requirement_string))
simplesat.errors.InvalidConstraint: 'tool=modelsim >= 0-0'(distribution name: 'tool')(unparsed '=modelsim >= 0-0')

See also: