-
Notifications
You must be signed in to change notification settings - Fork 550
Reuse a (System)Verilog, VHDL, Amaranth, Spinal HDL, Chisel core
Most of the open-source/shared LiteX designs are directly described in Migen/LiteX, but LiteX but is also heavily used to integrate/reuse traditional Verilog/VHDL cores (commercial or open-source) and also to integrate cores described in Spinal-HDL, nMigen or other high level HDL DSL through Verilog.
It is for example very common to create mixed language SoC similar to the following one:
In this SoC, the integration and most of the cores are directly described with Migen/LiteX but external cores are also integrated:
- The VexRiscv CPU, described in Spinal-HDL language, is integrated as a verilog standalone core in the LiteX SoC (Pre-generated verilog configuration from https://github.com/litex-hub/pythondata-cpu-vexriscv or https://github.com/litex-hub/pythondata-cpu-vexriscv_smp).
- The LiteDRAM core is in this case integrated as verilog standalone core, generated from a LiteDRAM's Generator and a
.yml
configuration file (ex: Arty's yml) with:litedram_gen arty.yml
- Core 0 is a VHDL core.
- Core 1 is a Verilog core.
Note: The VexRiscv configurations can also be directly generated from design's parameters/Spinal-HDL sources when the configuration is not cached/present, where verilog is just used as an intermediate language for the integration.
Integrating an external core in LiteX that is not written in native Migen/LiteX is pretty straightforward and follows the exact sames rules than other design flows; the tool just needs to know:
- The configuration of the core (through parameters) and the description of the interfaces for the integration in the design.
- The list of sources describing the core that will be passed to the toolchain for synthesis, place and route.
Doing the instance of the core in the design configures the core and specifies the interfaces. The framework will then consider the core as a blackbox with known name and interfaces and will only discover its contents and integrate it during the synthesis of the design.
To instantiate a core in LiteX we are simply reallying on Migen's Instance:
din = Signal(32)
dout = Signal(32)
dinout = Signal(32)
self.specials += Instance("custom_core",
p_DATA_WIDTH = 32,
i_din = din,
o_dout = dout,
io_dinout = dinout
)
The first parameters of the Instance
is the Module's name (custom_core
in our example) followed by the parameters and ports of the Module:
Prefixes are used to specify the type of interface:
-
p_
for a Parameter (Python'sstr
orint
or Migen'sConst
). -
i_
for an Input port (Python'sint
or Migen'sSignal
,Cat
,Slice
). -
o_
for an Output port (Python'sint
or Migen'sSignal
,Cat
,Slice
). -
io_
for a Bi-Directional port (Migen'sSignal
,Cat
,Slice
).
If you are already familiar with VHDL/Verilog, you can see that the approach is very similar to the one you are already using in these languages, with just more flexibility thanks to Python :)
While an instance has to be declared as a single block in (System)Verilog/VHDL, it's not mandatory with Migen/LiteX since Migen's Instance is relying on a Python's Dict: It's possible to have a lot more flexibility and prepare the Instance's parameters in conditionally or in advance:
# Create a Dict for the Parameters/IOs.
params_ios = dict()
# Add the Parameters.
params_ios.update(
p_DATA_WIDTH = 32
)
# Add the IOs.
params_ios.update(
i_din = din,
o_dout = dout,
io_dinout = dinout
)
# Do the Instance:
self.specials += Instance("custom_core", **self.params_ios)
Splitting the Instance allows the use of Python as a powerful pre-processor to define the Parameters and/or assign the IOs.
This can be very useful to update some Parameters/IOs of the Instance from methods; for example allowing the update the CPU reset address from the design:
def set_reset_address(self, reset_address):
self.cpu_params.update(i_externalResetVector=Signal(32, reset=reset_address))
It can also provide some interesting flexibility to connect group of ports, as done for example below to connect an abitrary number of FIFOs ports when re-integrating a LiteDRAM's standalone core in LiteX design:
for i in range(fifo_ports):
litedram_params.update(**{
# FIFO In.
f"i_user_fifo_{i}_in_valid": axis_in[i].valid,
f"o_user_fifo_{i}_in_ready": axis_in[i].ready,
f"i_user_fifo_{i}_in_data" : axis_in[i].data,
# FIFO Out.
f"o_user_fifo_{i}_out_valid": axis_out[i].valid,
f"i_user_fifo_{i}_out_ready": axis_out[i].ready,
f"o_user_fifo_{i}_out_data" : axis_out[i].data,
})
...Or do a one-line connect of a bus with a different name for each bit to a bus of the LiteX design, as done for example on the LiteICLink ECP5's SerDes:
serdes_params.update(**{
# CHX TX — data
**{f"i_CHX_FF_TX_D_{n}" : tx_bus[n] for n in range(tx_bus.nbits)}
})
Note: Python restricts Dict creation/update to 255 items, so large Instances have to split the Dict creation as just described above.
With the Instance
, the design is now aware of the configuration and interfaces of the integrated core but still don't know from where this core comes and in which language it is described.
Adding the sources of the core to the design will allow LiteX to pass these informations to the synthesis toolchain and let the toolchain do the synthesis of the core and integration in the design.
Adding the single source to the LiteX design is done with platform.add_source(...)
platform.add_source("core.v") # Will automatically add the core as Verilog source.
platform.add_source("core.sv") # Will automatically add the core as System-Verilog source.
platform.add_source("core.vhd") # Will automatically add the core as VHDL source.
As can be seen, to simplify things for the User, LiteX automatically determines the language based on the file extension:
Extension | Language |
---|---|
.vhd, .vhdl, .vho | VHDL |
.v, .vh, .vo | Verilog |
.sv, .svo | System Verilog |
Still to simplify things for User, it is possible to pass multiple sources at once with platform.add_sources(...)
:
platform.add_sources(path="./",
"core0.v",
"core1.vhd",
"core2.sv"
)
Or just provide the path and let LiteX automatically collect and add the sources:
platform.add_source_dir(path="./")
This last method is however not always possible for all external cores: Some projects provide different implementation of the same module: One for simulation, one specialized for one type of FPGA, etc... and the synthesis toolchain will not be able to automatically select the one to use. In theses cases, the previous methods manually specifying the sources should be used.
For more information about the supported parameters of these methods, the LiteX source code be consulted.
Just do the Instance
in your LiteX design and add the source to the LiteX platform as just described just above.
Note: For verilog cores litex_read_verilog tool can be useful to generate the Instance template.
This is very similar than reusing a Verilog/VHDL core with just one extra step: Generate the Amaranth/Spinal-HDL/Chisel/etc.. core as a verilog core :)
The Betrusted.io project relies on LiteX for the integration and makes heavy use of external Verilog/System Verilog cores. This can then be a good source of integration examples such as the integration of an I2C core from OpenCores.
In some cases, some encrypted cores or proprietary core formats needs to be passed to the toolchain: On Xilinx design you'll generally have to integrate .xci
files or .tcl
scripts that will automatically generate the cores.
Most of these use-cases are probably already supported by LiteX but aren't (yet) documented here. Please have a look at the LiteX source code, the different LiteX ressources, projects or at the targets from LiteX-Boards to find similar integration cases.
Have a question or want to get in touch? Our IRC channel is #litex at irc.libera.chat.
- Welcome to LiteX
- LiteX's internals
- How to
- Create a minimal SoC-TODO
- Add a new Board-TODO
- Add a new Core-WIP
- Add a new CPU-WIP
- Reuse-a-(System)Verilog,-VHDL,-Amaranth,-Spinal-HDL,-Chisel-core
- Use LiteX on the Acorn CLE 215+
- Load application code the CPU(s)
- Use Host Bridges to control/debug a SoC
- Use LiteScope to debug a SoC
- JTAG/GDB Debugging with VexRiscv CPU
- JTAG/GDB Debugging with VexRiscv-SMP, NaxRiscv and VexiiRiscv CPUs
- Document a SoC
- How to (Advanced)