Fri 25 March 2022

The ULX3S

ECP5 based FPGA Board

Introduction

As you may already know, I am a proud owner of the iCEBreaker FPGA board and have already done several projects with it. The limiting factors of the FPGA (~5k LUTs) create interesting challenges and it is fun to solve them.

But sometimes you just wish to have some more resources on hand to get for example a VexRiscv based SoC running. That's why I had already eyeballed the ULX3S. After some delays due to production and the overall situation (chip shortage) I am now a proud owner of the ULX3S with an ECP5 85F FPGA.

ULX3S front side ULX3S back side

What a beauty.

The ULX3S is OSHW. This means the schematics and layout for the board are available together with a lot of documentation and example projects. Similar for the iCE40 series the ECP5 series also has ongoing Open Source tooling support.

Specs

You can get the full specs from the official website.

Here I list the ones which were interesting for me:

  • FPGA: Lattice ECP5 LFE5U-85F-6BG381C (84K LUT)
  • One USB connected to a FTDI FT231XS and another USB connected directly to the FPGA
  • WiFi through the ESP-32
  • Peripherals
    • buttons
    • switches
    • LEDs
    • display placeholder
  • Storage
    • 32MB SDRAM 166 MHz
    • 16MB Quad-SPI Flash
    • Micro-SD slot
  • Audio: 3.5 mm jack
  • Digital video GPDI
  • ADC 8 channels, 12 bit, 1 MSa/s
  • PMOD compatible pinout

There are for sure a lot of things you can already do just with this board alone. Especially USB stuff is going to be interesting for me. If you happen to have exhausted all of the peripherals and interfaces on this device then you can always plugin some PMODs.

I also bought the official expansion boards which provide:

  • Dual USB-A receptacle (with IR send/receive to solder on)
  • GPDI input or output
  • OV7670 camera connector (camera not included)

Expansion boards

The first "Blinky"

Tooling

Currently the Open Source tools change almost daily. You get a lot of improvements with the recent versions. To really have the newest version of the toolchain I decided to give the OSS CAD Suite a chance.

This suite is a collection of prebuilt binaries of all the great open source tools used in digital logic design.

Get the newest build from the oss-cad-suite-build GitHub repository under the Releases section.

After you have downloaded the file, unpack it. Now how to actually use the tools? I mean, I don't always want to specify the full path to the binaries.

Well, the solution for that is to add the oss-cad-suite directory to the PATH environment variable like this:

> export PATH="/path/to/oss-cad-suite/bin:$PATH"

Now whenever you type e.g. iverilog, first this directory will be searched for a matching binary and only if none is found, other places are searched.

Code

With that out of the way I started to adapt a simple blinky example I found here. It is already great as it is but I changed some things to have it more my way.

This Makefile is used for the complete flow: synthesis, place and route, generating the bitstream and uploading to the device.

default: prog

.PHONY: clean upload

blinky.json: blinky.sv
    yosys -p 'synth_ecp5 -top top -json $@' $^

ulx3s_out.config: blinky.json
    nextpnr-ecp5 --85k --json blinky.json \
        --lpf ulx3s_v20.lpf \
        --package CABGA381 \
        --textcfg ulx3s_out.config 

ulx3s.bit: ulx3s_out.config
    ecppack ulx3s_out.config ulx3s.bit

prog: ulx3s.bit
    openFPGALoader --board=ulx3s ulx3s.bit

clean:
    rm -rf blinky.json ulx3s_out.config ulx3s.bit

Well, of course we also need a simple SystemVerilog design. So here is the top module for blinky.

module top (
    input  logic clk_25mhz,
    input  logic [6:0] btn,
    output logic [7:0] led,
    );

    localparam cnt_width = 32;
    logic [cnt_width-1:0] cnt = 0;

    always @(posedge clk_25mhz) begin
        if (btn[0] == 1) begin
            cnt <= cnt + 1;
        end
    end

    assign led[7:0] = cnt[25:18];
endmodule

This counter increases continuously, bits 25 to 18 are wired to the LEDs on the board. When you press button 0 the counter will stop increasing and you can see the current value.

You will also need a constraint file. So get ulx3s_v20.lpf from here.

Upload

Before I could program the board I had to add a udev rule for the ULX3S. I followed the official manual for that.

Just create a new file:

> sudo touch /etc/udev/rules.d/80-fpga-ulx3s.rules

And fill it with this content:

# this is for usb-serial tty device
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", \
  MODE="664", GROUP="dialout"
# this is for ujprog libusb access
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", \
  GROUP="dialout", MODE="666"

Now just run the Makefile:

> make

Soon enough I had my ULX3S blinking!

ULX3S blinking

Summary

I can't describe how glad I am that I don't need to download a vendor locked-in toolchain, tens of gigabytes in size just to get a simple blinky to run.

But the more important point: These tools are Open Source. They are developed by the community and some very talented people. They will stay this way no matter what, because the source code is open. They will get ported to other architectures (e.g. RISC-V) and you don't need to worry about not having access to them one day.

The speed of the entire workflow is simply incredible. Calling make with no prebuilt files completes in just a few seconds with this blinky:

real    0m6,687s
user    0m2,189s
sys     0m0,261s

Keep in mind that most of the time was spent uploading the bitstream to the device.

Now back to the board: The ULX3S is a great device with a wide range of peripherals and a lot of resources. I am going to use this device for exciting things!