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 1Input ValueOutput Valuexor 0xFFcrapto 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:

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 CSRBankArray, 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.)

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 helloCSR 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:

# helloHELLO_BASE=0xc00HELLO_INPUT=0xc00HELLO_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.