OpenVizsla OV3 - Hello, World!

I’ve previously talked about the hardware design of the the OpenVizsla OV3 USB hardware analyzer, and about the FPGA design.

Now we could use OV3 to sniff some USB traffic - but we can do this later as well. Instead we should write a ‘hello world’-type component.

Hello, World

I’ve chosen to implement something that allows to write an 8-bit register, does a computation (invert all bits), and allow that to be read back. Sounds simple? It is!

Layer 1 Input Value Output Value xor 0xFF crapto engine

What Migen does for you

TL;DR: Migen doesn’t replace your brain.

One warning - Migen allows you to generate FPGAs design using Python. It does not allow you to write “Python code on your FPGA”, and it doesn’t attempt to provide “a subset of Python that can be synthesized”. This is not how Migen works.

You’re still assembling logic. Migen helps you in describing this logic. Have you drawn schematics before? Think of a writing a Python program that generates a schematic based on your ideas. Python allows you to script all parts of this process, so instead of drawing 20 NAND-gates, you can write a for-loop that draws these 20 NAND-gates for you. Or you can make function that draws a full-blown flipflop at a given location on the sheet. Or something that takes care of all the boring wires like clock and reset. So you can use all fancy Python features as much as you want, but the end result will still be a schematic diagram.

Migen is very similar, except that it creates verilog in the end - just a different representation of a schematic diagram.

The Business Logic

Anyway, let’s go ahead. We need to write the business end of our tool, which XORs the bits. We start by writing a Module which contains two CSRs. CSRs are registers that can be accessed from the host. They can be either read-only from host (CSRStatus), read-only from FPGA (CSRStorage), or more complicated, user-defined (CSR). We use a CSRStorage to implement an input port, and CSRStatus to deliver the result of the very complicated XOR computation.

We create a file called software/fpga/ovhw/hello.py with the following content:

from migen.fhdl.std import *
from migen.bank import description

class Hello(Module, description.AutoCSR):
    def __init__(self):
      self._input = description.CSRStorage(8)
      self._output = description.CSRStatus(8)

      self.sync += self._output.status.eq(self._input.storage ^ 0xff)

The first line gets us the basic Migen primitives like Module. We define a class Hello, which is a Migen Module, that we will later insert into the OV3 design.

The second line gets us the AutoCSR feature, which we use to create CSR registers. In the background, the Migen feature creates a list of all registers in the design, and ties them to the CSR BankArray, similar to BTN_status, LED_outputs etc.

We define two CSR registers, _input and _output. _input is readable from our design, and _output is writable. Both are 8 bit in size.

Finally, we do the computation - self.sync is a list of everything that happens at a clock edge. (More accurately, on a clock edge in the sys clock domain. There can be multiple, but we simply run in the same clock domain as the rest of the CSR logic, which simplifies things. On OV3, this was chosen to be a 100MHz clock.)

Wiring it Up

Now we need to add our Module to the OV3 design. The top-level module is defiend in software/fpga/ovhw/top.py:

diff --git a/software/fpga/ov3/ovhw/top.py b/software/fpga/ov3/ovhw/top.py
index 5813e8f..0cd9a9a 100644
--- a/software/fpga/ov3/ovhw/top.py
+++ b/software/fpga/ov3/ovhw/top.py
@@ -23,6 +23,7 @@ from ovhw.ftdi_lfsr_test import FTDI_randtest
 from ovhw.ulpicfg import ULPICfg
 from ovhw.cfilt import RXCmdFilter
 from ovhw.ov_types import ULPI_DATA_D
+from ovhw.hello import Hello
 import ovplatform.sdram_params

 class OV3(Module):
@@ -115,6 +116,8 @@ class OV3(Module):

         self.submodules.buttons = BTN_status(~plat.request('btn'))

+        # hello, world
+        self.submodules.hello = Hello()

         # Bind all device CSRs
         self.csr_map = {
@@ -124,6 +127,7 @@ class OV3(Module):
                 'randtest' : 3,
                 'cstream' : 4,
                 'sdram_test' : 5,
+                'hello' : 6,
                 }

         self.submodules.csrbankarray = BankArray(self,

3 simple changes: We import our new module that we created above, we add it as a submodule to the OV3-top-level module, and we assign a register prefix to the registers in the hello CSR space. Don’t worry about the actual number, as it automagically ends up in the map file, which is later consumed by ovctl.py.

Synthesize the thing by running make in software/fpga/ovhw, and you should end up with a new firmware package, in software/fpga/ovhw/build/build/ov3.fwpkg. A firmware package contains an FPGA bitstream and the register mapping files.

Accessing the Logic

The map file can also be found in software/fpga/ovhw/build/build/map.txt and describes the address of all CSR resources. For our hello component, the following lines were automatically added by the build process:

# hello
HELLO_BASE = 0xc00
HELLO_INPUT = 0xc00
HELLO_OUTPUT = 0xc01

It means that the _input register is at address 0xC00, and the _output register is at address 0xC01. Again, no need to remember the values, as the map file gets parsed automatically.

Finally we need to add code to talk to our new module in software/host/ovctl.py:

@command('hello', ('v', int16))
def hello(dev, v):
    dev.regs.hello_input.wr(v)
    print("Hello returns %02x" % dev.regs.hello_output.rd())

@command registers the following function to the command line handler. We expect a single argument, an integer.

Our implementation of that command simply writes that value to the hello-Module _input-register, then reads back then value from the _output register, and displays it.

Finally, we instruct ovctl.py to take the new fwpkg (--pkg ../fpga/ov3/build/ov3.fwpkg), we force-reload the bitstream (-l), and invoke our hello function:

$ python3 ovctl.py -l --pkg ../fpga/ov3/build/ov3.fwpkg hello 12
FPGA: Bitstream timestamp 2014/08/27 13:35:19
FPGA: sending configuration bitstream
FPGA: CRC OK
FPGA: configured
Hello returns ed

We quickly compute that 0x12 is really the inversion of 0xED, and call this success.

You can find this code on github.