# Fomu: An FPGA in your USB Port

#### A whirlwind introduction to Fomu; a workshop in three levels

Sean Cross - https://xobs.io/ - @xobs

## Levels of Fomu

Fomu aims to be accessable on three levels:

1. Python / Interpreter
2. RISC-V / C
3. FPGA / HDL

## Workshop Outline

1. What do I need to get started?
2. What is an FPGA, and what is Fomu?
3. Working with Fomu using Python, RISC-V, and HDL

## What do I need to get started?

1. DFU utilities
2. Serial console
3. RISC-V toolchain
4. FPGA Toolchain
5. Python 3

## What is an FPGA?

| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
--- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | ---
IO0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1
IO1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1
IO2 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1
IO3 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1
O | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ?

## What is an FPGA?

    						module example (output reg [0:5] Q, input C);
    							reg [0:8] counter;
    							always @(posedge C)
    							begin
    								counter <= counter + 1'b1;
    								Q = counter[7] ^ counter[5] | counter<<2;
    							end
    						endmodule

## About the ICE40UP5K

1. 5280 4-input LUTs (LC)
2. 16 kilobytes BRAM
3. 128 kilobytes "SPRAM"
4. Current-limited 3-channel LED driver
5. 2x I2C and 2x SPI
6. 8 16-bit DSP units
7. Warmboot capability
8. Open toolchain

## What is Fomu?

- ICE40UP5K
- 2MB QSPI flash memory
- Four edge-plated pads
- ESD protection
- USB implemented in HDL
- Fits in your USB port

## Fomu Block Design Diagram

## What is this PCB?

### Fomu EVT1

## Misleading Datasheets

## What modifications does it have?

- Shorting out two zero-ohm resistors (R7, PU)
- Programming SPI flash
- Bending SPI flash pins inward (U4)
- Mounting crystal on its side (U7)
- Attaching power to crystal

## Fomu SPI Flash Layout

## Working with Fomu

## FAT Bootloader

- Presents itself as a USB disk
- Drag and drop files to program
- Multiple interpreter support

## "fail safe" bootloader

Device Firmware Update - DFU

## Updating Fomu

    $ dfu-util -l
    Found DFU: [1209:5bf0] name="Fomu DFU Bootloader v1.7.2"
    $ dfu-util -D evt-installable.dfu
    Download        [=========                ]  36%   38912 bytes
    Download done.
    $ dfu-util -l
    Found DFU: [1209:5bf0] name="Fomu DFU Bootloader v1.8.1"
    $

## Python / Interpreted

1. Goal: Multiple interpreters, auto-reload, USB disk interface
2. Now: MicroPython binary

## Loading Programs onto Fomu

    $ dfu-util -l
    Found DFU: [1209:5bf0] name="Fomu DFU Bootloader v1.8.1"
    $ dfu-util -e                  # Boot current program
    $ dfu-util -D new-image.dfu    # Load new program

### u`5b f0`mu

## Loading MicroPython

    $ dfu-util -D micropython-fomu.dfu

## Connecting via serial

    screen /dev/cu.usbserial*

    screen /dev/ttyACM*

    Tera Term

    MicroPython v1.10-296-g0a5a77a on 2019-06-18; fomu with vexriscv
    >>>

## Interacting with Fomu

    							>>> import fomu
    							>>> rgb = fomu.rgb()
    							>>> rgb.mode("error")
    							>>>

## Read SPI ID

                                    >>> spi = fomu.spi()
                                    >>> hex(spi.id())
                                    '0xc2152815'
                                    >>>

## Memory-Mapped Registers

    #define CSR_VERSION_MAJOR_ADDR 0xe0007000
    #define CSR_VERSION_MINOR_ADDR 0xe0007004
    #define CSR_VERSION_REVISION_ADDR 0xe0007008

    >>> import machine
    >>> machine.mem32[0xe0007000]
    1
    >>>

## RGB LED Driver reference

    >>> rgb.write_raw(0b0001, 255)
    >>> rgb.write_raw(0b1010, 14)
    >>> rgb.write_raw(0b1011, 1)
    >>>

## Future Work

- CircuitPython
- eLua
- Espurino?

## RISC-V

## LiteX Model

## Wishbone Bridge

## CPU is Optional

- Multiple CPUs available
- - VexRiscv
  - picorv32
  - lm32
  - ...
- Also works just fine with no CPU

## CSR Access

    #define CSR_VERSION_MAJOR_ADDR 0xe0007000
    #define CSR_VERSION_MAJOR_SIZE 1
    #define CSR_VERSION_MINOR_ADDR 0xe0007004
    #define CSR_VERSION_MINOR_SIZE 1
    #define CSR_VERSION_REVISION_ADDR 0xe0007008
    #define CSR_VERSION_REVISION_SIZE 1
    #define CSR_VERSION_GITREV_ADDR 0xe000700c
    #define CSR_VERSION_GITREV_SIZE 4
    #define CSR_VERSION_GITEXTRA_ADDR 0xe000701c
    #define CSR_VERSION_GITEXTRA_SIZE 2

Excerpt from `csr.h`

## Reading CPU Version

    $ wishbone-tool --pid 0x5bf0 0xe0007000
    Value at e0007000: 00000001
    $ wishbone-tool --pid 0x5bf0 0xe0007004
    Value at e0007004: 00000008
    $ wishbone-tool --pid 0x5bf0 0xe0007008
    Value at e0007008: 00000001

## Interacting with LEDD directly

    #define CSR_RGB_DAT_ADDR 0xe0006800L
    #define CSR_RGB_ADDR_ADDR 0xe0006804L

    $ wishbone-tool --pid 0x5bf0 0xe0006804 1
    $ wishbone-tool --pid 0x5bf0 0xe0006800 0xff

## Writing RISC-V Code

    $ make
      CC       ./src/main.c        main.o
      CC       ./src/rgb.c rgb.o
      CC       ./src/time.c        time.o
      AS       ./src/crt0-vexriscv.S       crt0-vexriscv.o
      LD       riscv-blink.elf
      OBJCOPY  riscv-blink.bin
      IHEX     riscv-blink.ihex
    $

From `riscv-blink` directory in `teardown2019-workshop`

## Modifying RISC-V Code

    --- a/riscv-blink/src/main.c
    +++ b/riscv-blink/src/main.c
    @@ -38,6 +38,7 @@ void isr(void) {
     void main(void) {
         rgb_init();
         irq_setie(0);
    +    rgb_write((100000/64000)-1, LEDDBR);
         int i = 0;
         while (1) {
             i++;

## Other RISC-V Programs

riscv-usb-cdcacm: echo characters back after adding 1

## Hardware Description Language

## Yosys and NextPNR

- Timing Driven!

    Max frequency for clock 'clk12':   24.63 MHz (PASS at 12.00 MHz)
    Max frequency for clock 'clk48_1': 60.66 MHz (PASS at 48.00 MHz)
    Max frequency for clock 'clkraw': 228.05 MHz (PASS at 48.00 MHz)

## Blinking an LED

    $ make FOMU_REV=evt
    ...
    20 warnings, 0 errors
     PACK     blink.bin
    Built 'blink' for Fomu evt1
    $ dfu-util -D blink.bin

## LiteX and MiGen

1. Define hardware in Python
2. Evaluate Python to produce netlist
3. Synthesize netlist to FPGA

## lxbuildenv.py

1. Python environment using native interpreter
2. Very stable, good for hardware projects
3. Should work with system Python
4. Runs on Linux, Windows, Raspberry Pi

## Why do we need a CPU?

## What if we remove the CPU?

- Workshop project has no CPU
- DummyUsb module automatically enumerates
- Wishbone Debug Bridge still accessible

## Build Workshop Module

    $ python3 workshop.py --placer heap
    ...
    5 warnings, 0 errors
    $

## Load onto Fomu

    $ dfu-util -D build/gateware/top.bin
    Download      [=========================] 100%    104090 bytes
    Download done.
    $

## Write a value to RAM

    $ wishbone-tool --pid 0x5bf0 0x10000000
    Value at 10000000: 0baf801e
    $ wishbone-tool --pid 0x5bf0 0x10000000 0x12345678
    $ wishbone-tool --pid 0x5bf0 0x10000000
    Value at 10000000: 12345678
    $

## Adding Hardware

## Technology Library Reference

    // Verilog Instantiation
    SB_RGBA_DRV RGBA_DRIVER (
    .CURREN(ENABLE_CURR),
    .RGBLEDEN(ENABLE_RGBDRV),
    .RGB0PWM(RGB0),
    .RGB1PWM(RGB1),
    .RGB2PWM(RGB2),
    .RGB0(LED0),
    .RGB1(LED1),
    .RGB2(LED2)
    );
    defparam RGBA_DRIVER.CURRENT_MODE = "0b0";
    defparam RGBA_DRIVER.RGB0_CURRENT = "0b111111";
    defparam RGBA_DRIVER.RGB1_CURRENT = "0b111111" ;
    defparam RGBA_DRIVER.RGB2_CURRENT = "0b111111";

SBTICETechnologyLibrary201504.pdf page 147

## RGB Block

    class FomuRGB(Module, AutoCSR):
        def __init__(self, pads):
            self.output = CSRStorage(3)
            self.specials += Instance("SB_RGBA_DRV",
                i_CURREN = 0b1,
                i_RGBLEDEN = 0b1,
                i_RGB0PWM = self.output.storage[0],
                i_RGB1PWM = self.output.storage[1],
                i_RGB2PWM = self.output.storage[2],
                o_RGB0 = pads.r,
                o_RGB1 = pads.g,
                o_RGB2 = pads.b,
                p_CURRENT_MODE = "0b1",
                p_RGB0_CURRENT = "0b000011",
                p_RGB1_CURRENT = "0b000011",
                p_RGB2_CURRENT = "0b000011",
            )

## Instantiating FomuRGB

    @@ -55,6 +75,10 @@ class BaseSoC(SoCCore):
                     with_ctrl=False,
                     **kwargs)

    +        # Add the LED driver block
    +        led_pads = platform.request("rgb_led")
    +        self.submodules.rgb = FomuRGB(led_pads)
    +
             # UP5K has single port RAM....
             # Use this as CPU RAM.
             spram_size = 128*1024

## Interacting with the CSR

    csr_register,rgb_output,0xe0006800,1,rw

From `test/csr.csv`

## VexRiscv

## Thank you

## Lunch Time!