Module structure
Source code management in tsfpga is centered around modules. This page describes how modules must be structured in the file system in order to use all available functions.
Some functions in tsfpga require that modules use a certain folder structure.
For example, if we want to set up local test configurations we
must use a file called exactly module_<name>.py
in the root of the module.
Additionally the get_modules()
function in tsfpga, which creates
module objects
from a source tree, will look for source files
only in certain sub-directories.
Below is a recommended folder structure. The different files and folders are explained further down.
modules/
├── foo
│ ├── module_foo.py
│ ├── regs_foo.toml
│ ├── ip_cores
│ │ ├── fifo.tcl
│ │ └── ...
│ ├── scoped_constraints
│ │ ├── sample_data.tcl
│ │ └── ...
│ ├── src
│ │ ├── foo_top.vhd
│ │ ├── foo_pkg.vhd
│ │ ├── sample_data.vhd
│ │ └── ...
│ └── test
│ ├── tb_foo_top.vhd
│ └── ...
│
├── bar
│ └── ...
│
└── ...
At the top level there is a folder called modules
that contains all available modules.
It does not have to be named that, but it is a name that fits well with the tsfpga nomenclature.
Within this folder there are source code modules: foo
, bar
, etc.
Sources and testbenches
Source code and packages are recommended to be placed in the src
folder.
There is no distinction made between entity source files and packages in tsfpga.
The corresponding test benches are recommended to use the test
folder.
We don’t have to use these exact folders; BaseModule
will look
for files in many folders, to accommodate for different projects using different structures.
For example, at the moment
BaseModule.get_synthesis_files()
will look
for source files in
src
rtl
hdl/rtl
hdl/package
Note
If your project uses a different folder structure, and is locked into using it, tsfpga can be updated to accommodate that as well. This goes for most of the folders within the module, described below. Feel free to create an issue or a pull request.
module_foo.py
If we want to, e.g., set up FPGA build projects or do
local test configurations we can use a file
called module_<name>.py
.
The Python file shall contain a class definition called Module
that inherits
from BaseModule
.
Methods from BaseModule
can then be overridden to achieve the desired behavior.
Extra files
An FPGA build project might need a lot extra files, such as TCL scripts for pinning,
block design, etc.
Or maybe some simulations need data files stored on disk.
It is perfectly valid to create other folders within the module, e.g. tcl
or test/data
,
and place files there.
Extra folders like these can be used freely and will not have any significance to tsfpga.
regs_foo.toml
The file regs_<module_name>.toml
, if it exists, will be parsed with the
hdl-registers register generator.
It contains the registers that the module uses and the fields within those registers.
Per default, the module will generate all register VHDL artifacts. Which includes register packages, AXI-Lite register file wrapper, and simulation support packages. If want only a subset of these to be created, you can achieve that by creating a module_foo.py, see Choosing what artifacts to generate for details.
IP cores
In tsfpga, IP cores are handled using TCL files that contain the code snippet that generates the core. This TCL snippet can be found in the Vivado TCL console when creating or modifying the IP. It typically looks something like this:
create_ip -vlnv "xilinx.com:ip:mult_gen:12.0" -module_name "mult_u12_u5"
set_property -dict [list \
"CONFIG.PortAType" "Unsigned" \
"CONFIG.PortAWidth" "12" \
"CONFIG.PortBType" "Unsigned" \
"CONFIG.PortBWidth" "5" \
"CONFIG.OutputWidthHigh" "16" \
] [get_ips "mult_u12_u5"]
These TCL files shall be place in the ip_cores
folder within the module.
The IP cores will be included in all build projects that include the module, and in the
simulation project.
Using small TCL snippets like this is preferred to using the .xci
file generated by Vivado,
especially when it comes to version control.
The .xci
file is very large and contains much extraneous information, that tends to be updated
depending on what computer and Vivado version you’re using.
The .tcl
file on the other hand contains only the few settings that are needed.
Another advantage of using TCL is that it is a full-fledged scripting language. We can use variables, loops and if/else branches to parameterize our IP core creation. In the TCL snippet above for example, the IP core name or some of the property values could be replaced by variables.
To find the settings of an existing IP, the following TCL code can be executed with an existing design open:
foreach ip [get_ips] {
puts "\n\n";
set ipdef [get_property IPDEF ${ip}]
puts "create_ip -vlnv \"${ipdef}\" -module_name \"${ip}\""
puts "set_property -dict \[list \\"
foreach property [list_property ${ip} -regexp {^CONFIG\.\w+$}] {
if {[get_property ${property}.value_src ${ip}] == "USER"} {
set property_value [get_property $property ${ip}]
puts " \"${property}\" \"${property_value}\" \\"
}
}
puts "\] \[get_ips \"${ip}\"\]"
}
Alternatively, the Vivado command write_ip_tcl [get_ips <name>]
can be used and the generated
.tcl
file inspected.
However the TCL format that write_ip_tcl
produces does not allow variable usage.
Scoped constraints
Scoped constraints are constraint files that are applied in Vivado relative to a certain entity.
This is handled in build projects
using the
Constraint
class.
Constraint files in the scoped_constraints
directory will be automatically added to
build projects as scoped constraints.
The name of a scoped constraint file must be the same as the entity name and source file name.
In the example tree above there is a scoped constraint file sample_data.tcl
that will be applied
to sample_data.vhd
, which presumably contains an entity called sample_data
.