Migen and LiteX¶
“Hello world!” - Blink a LED¶
Migen is an HDL embedded in Python. The verilog examples (in directory
verilog
) can also be written using Migen; an implementation is provided
in directory migen
.
To try them out, go to the migen
directory and execute blink.py
or
blink-expanded.py
respectively (before, ensure that you have set
the FOMU_REV
environment variable correctly). This will create a
build
directory with a top.bin
file.
Using dfu-util -D build/top.bin
, it can be loaded onto the Fomu and should
work identically as the corresponding verilog example.
Wishbone Bus Basics¶
LiteX provides us with a Wishbone abstraction layer. There really is no reason we need to include a CPU with our design, but we can still reuse the USB Wishbone bridge in order to write HDL code.
We can use DummyUsb
to respond to USB requests and bridge USB to
Wishbone, and rely on LiteX to generate registers and wire them to
hardware signals. We can still use wishbone-tool
to read and write
memory, and with a wishbone bridge we can actually have code running on
our local system that can read and write memory on Fomu.
Go to the litex
directory and build the design;
python3 workshop.py --board $FOMU_REV
Load it onto Fomu:
dfu-util -D build/gateware/fomu_$FOMU_REV.bin
If you get an error message about missing modules, check you have all submodules cloned and setup with;
git submodule update --recursive --init
Take a look at build/csr.csv
. This describes the various regions
present in our design. You can see
memory_region,sram,0x10000000,131072
, which indicates the RAM is 128
kilobytes long and is located at 0x10000000
, just as when we had a
CPU. You can also see the timer, which is a feature that comes as part
of LiteX. Let’s try reading and writing RAM:
wishbone-tool 0x10000000
wishbone-tool 0x10000000 0x98765432
wishbone-tool 0x10000000
Wishbone Bus Extension¶
Aside from that, there’s not much we can do with this design. But
there’s a lot of infrastructure there. So let’s add something we can see
(workshop_rgb.py
contains the completed example).
As explained in Hardware Description Languages the RGB LED driver is defined in Verilog/VHDL
(as SB_RGBA_DRV
), but we can import it as a Module into Migen:
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",
)
This will instantiate this Verilog block and connect it up. It also
creates a CSRStorage
object that is three bits wide, and assigns it
to output
. By having this derive from AutoCSR
, the CSRStorage
will have CSR bus accessor methods added to it automatically. Finally,
it wires the pads up to the outputs of the block.
We can instantiate this block by simply creating a new object and adding
it to self.specials
in our design:
...
# Add the LED driver block
led_pads = soc.platform.request("rgb_led")
soc.submodules.fomu_rgb = FomuRGB(led_pads)
Finally, we need to add it to the csr_map
:
...
soc.add_csr("fomu_rgb")
Now, when we rebuild this design and check build/csr.csv
we can see
our new register:
csr_register,fomu_rgb_output,0x60003000,1,rw
We can use wishbone-tool
to write values to 0x60003000
(or whatever
your build/csr.csv
says) and see them take effect immediately.
wishbone-tool 0x60003000 0x1 # make LED green
wishbone-tool 0x60003000 0x2 # make LED red
wishbone-tool 0x60003000 0x3 # make LED yellow
wishbone-tool 0x60003000 0x4 # make LED blue
wishbone-tool 0x60003000 0x5 # make LED teal
wishbone-tool 0x60003000 0x6 # make LED pink
wishbone-tool 0x60003000 0x7 # make LED white
You can see that it takes very little code to take a Signal from HDL and expose it on the Wishbone bus.