-
Notifications
You must be signed in to change notification settings - Fork 79
Notes and Tips
...because it's hard to remember all those command lines...
- If you have a multi-bit signal, the syntax for breaking out individual bits is totally fucked up in Python compared to sane languages. For example, if you have a 30-bit signal[29:0], and you want to split it into three sets of ten, you don't do a.eq(signal[9:0]), b.eq(signal[19:10]), c.eq(signal[29:20]). Because of the way python reads those indices, you'll end up with verilog that's a=signal[-1:9], b=signal[9:19], c=signal[19:29] (WHAT THE FUCK PYTHON). Instead, you do a.eq(signal[:10]), b.eq(signal[10:20]), c.eq(signal[20:]). This assumes a, b, and c were declared as Signal(10); if they have different widths and you make a mistake, good luck.
- All signals are active high by convention, unless indicated by _b or _n suffix.
- Boundary scan SPI repos: https://github.com/jordens/bscan_spi_bitstreams - set of precompiled bitstreams that allow bscan update of SPI flash.
- scripts/enter-env.sh # run this before trying anything in litex
- export TARGET= # to change which target the environment builds
- ssh pi@151.217.44.31 -L1234:localhost:1234 -v # forward port to localhost via ssh
edit -- what you really want is pycharm. You'll need to make sure it's pointing to python3.5 and also you need to setup the litex paths (using python3 setup.py in the litex directory) but once it's running you can control-click through the object hierarchy.
Use ipython or bpython (via pip install bpython) for browsing netlist structure
from targets.netv2.video import SoC
from platforms.netv2 import Platform
s=SoC(Platform())
now you can use dir/completion to nav hierachy via the "s" object. Signals resolve as "signal" and these can be dropped into litescope
There's some nice features and sanity checks available in the Vivado UI that LiteX doesn't have. Just because it's all command-line doesn't mean you have to lose this.
Inside your build's gateware directory (e.g. build/platform_target_cpu/gateware) there is a "top.tcl" file. This is a script that drives vivado. If you want to e.g. run additional analyses on the automated compilation, you can just comment out the "quit" statement at the end and then run the tcl file, and it'll stop with the UI open.
If you want to use a pre-built run, you can just load a checkpoint. Start vivado from the build's gateware directory (the command is literally "vivado", assuming you've already entered the litex environment using the "source scripts/enter-env.sh command") and then just type
open_checkpoint top_route.dcp
(there's also a menu option to "open checkpoint" under the File menu)
This will load in the entire design just after place and route and where all the analysis steps get run. From here you can view the graphical flooplan, schematics, and run additional timing/clock interaction reports.
To make a clock global (so you can pull it in submodules without passing it explicitly through the args), you declare a "clock domain"
self.clock_domains.cd_george = ClockDomain()
self.specials += Instance("BUFG", name="george", i_I=georgeunbuf, o_O=self.cd_george.clk)
self.specials += AsyncResetSynchronizer(self.cd_george, unsynced_reset_signal) # unsynced_reset_signal is any signal from another clock domain that's to serve as a reset, eg a PLL lock etc
In the submodule, you can then use
self.george_clk = self.ClockSignal("george") # use george_clk wherever the clock is needed
self.res_george_sync = ResetSignal("george") # use res_george_sync wherever the reset is needed
If you want to use an existing verilog module you've written in a LiteX project, you need to first import it by calling add_source for every file in your module's heirarchy, e.g.
self.platform.add_source("full/path/to_module/module1.v")
self.platform.add_source("full/path/to_module/module2.v")
By "full/path/to_module" I mean the full path from the directory where you type "make", e.g. the top level of the build, no the absolute path relative to the filesystem.
You want to put those calls in your "target" file, inside the init function of your top-level SoC (not in the file with the list of pin mappings).
Then, you use the "specials" construct to instantiate the module. This can be done at any level in the LiteX hierarchy once the source files are incorporated in your target file.
The python syntax:
self.specials += Instance("module1",
i_port1=litex_signal1,
o_port2=litex_signal2
)
Would correspond to this verilog template:
module module1 (
input wire signal1,
output wire signal2
);
The "i_" and "o_" prefixes are automatically added to the beginning of signal names from the verilog template.
Litescope is the LiteX equivalent of Chipscope ILA.
Litescope can export its data through several ports: a dedicated UART port (separate from the firmware/CLI port), ethernet, PCI-express...in this example, we assume we've created an additional UART port dedicated to the Litescope interface. This is the slowest method but also the simplest and good for bootstrap debugging.
You will want to insert these lines inside your top-level .py SoC description file. At the top:
from litex.soc.cores import uart # these two lines may already be present
from litex.soc.cores.uart import UARTWishboneBridge
from litescope import LiteScopeAnalyzer
Then inside your top-level SoC instantiation function:
class MySoC(BaseSoc):
csr_peripherals = {
"probably_several_other_peripherals_already", ## you'll have several of these
"analyzer", ## << this line
}
csr_map_update(BaseSoC.csr_map, csr_peripherals) ## you probably have this already
def __init__(self, platform *args, **kwargs): ## you definitely have this
BaseSoC.__init__(self, platform, *args, **kwargs)
# these two lines add in the debug interface
self.submodules.bridge = UARTWishboneBridge(platform.request("debug_serial"), self.clk_freq, baudrate=115200)
self.add_wb_master(self.bridge.wishbone)
### all of your SoC code here
## now connect your analyzer to the signals you want to look at
analyzer_signals = [
self.hdmi_in0.frame.de,
self.crg.rst,
self.hdmi_in0.resdetection.valid_i
]
self.submodules.analyzer = LiteScopeAnalyzer(analyzer_signals, 1024) # 1024 is the depth of the analyzer
## add this function to create the analyzer configuration file during compliation
def do_exit(self, vns, filename="test/analyzer.csv"):
self.analyzer.export_csv(vns, filename)
After you run "make gateware", there will be a "test" sub-directory in your build/ directory. The test directory will contain an analyzer.csv and a csr.csv file. These configure the host-side drivers for the litescope.
Now in order to engage litescope, you have to start the litex_server:
litex_server uart /dev/ttyUSB0 115200 &
This of course assumes you have your UART cable at /dev/ttyUSB0 and connected properly to the board. If it works well, you'll get some output to the effect of:
LiteX remote server [CommUART] port: /dev/ttyUSB0 / baudrate: 115200 / tcp port: 1234
You can telnet to this port to confirm it's up and running.
Once the server is running you can run the following script to drive the interface:
#!/usr/bin/env python3
import time
from litex.soc.tools.remote import RemoteClient
from litescope.software.driver.analyzer import LiteScopeAnalyzerDriver
wb = RemoteClient()
wb.open()
# # #
analyzer = LiteScopeAnalyzerDriver(wb.regs, "analyzer", debug=True)
analyzer.configure_trigger(cond={"hdmi_in0_frame_de" : 1}) # only include this if you want a trigger condition
# analyzer.configure_trigger(cond={"foo": 0xa5, "bar":0x5a}) # you can add my conditions by building a "dictionary"
analyzer.configure_subsampler(1)
analyzer.run(offset=32, length=128) # controls the "pre-trigger" offset plus length to download for this run, up to the length of the total analyzer config in hardware
analyzer.wait_done()
analyzer.upload()
analyzer.save("dump.vcd")
# # #
You'll get a "dump.vcd" file which you can view using gtkwave (which you should be able to apt-get install if you don't have it).
Check https://github.com/enjoy-digital/litescope/blob/master/litescope/core.py#L199 for arguments to Litescope.
So when instantiating the analyzer, do this:
self.submodules.analyzer = LiteScopeAnalyzer(analyzer_signals, 1024, cd=my_domain, cd_ratio=1)
If my_domain is <= sys_clk, cd_ratio = 1; if my_domain is >=sys_clk, cd_ratio=2. The fastest that you can go is 2x sys_clk.
At least in my design, sys_clk is set to 100MHz, so that would give a max of 200MHz.
Florent's version of LiteX combines the platform+target files into a single file, along with most of the code you need for a specific FPGA. This makes a lot of sense.
To bootstrap into the environment, you'll need to cross-compile gcc for lm32 first. Don't clone the gcc source repo. That's a waste of time. Follow this gcc build guide up until the point where they say to run configure. Then, run
mkdir build && cd build
../configure --target=lm32-elf
make
sudo make install
Then you can run florent's "setup.py" script, first by doing setup.py init, then setup.py install
The specific SoC you're working on is in the -soc directory, e.g. netv2-soc for netv2mvp.
To use bpython3 to try and wade through problems in code, entering is a bit different. Start bpython3 and use these commands:
bpython3
exec(open("./netv2mvp.py").read()
platform=Platform()
soc=VideoSoc(platform)
And that will get you to a point where you can browse around in the hierarchy for figuring out what connects to what.
If you get some complaint about the litex environments not being found, go up a dir and run "bpython3 setup.py update" -- this will do some weird magic that maps the libraries into the current version of bpython3 that you're using. This broke a couple times for me because as I updated my python environment something in the library mappings break.
Oh also -- note that if you installed any python stuff from within the litex build environment, it "eats" it and keeps it local. So you have to re-install e.g. pip, bpython3 and so forth.