Creating a design¶
This chapter explains how to create a design in Topwrap, including a detailed overview of how Topwrap design files are structured.
Design description¶
To create a complete and fully synthesizable design, a design file is needed. It is used for:
specifying IP cores and their parameters
specifying interconnects
describing hierarchies for the project
connecting the IPs and hierarchies
defining design interfaces and ports
You can see example design files in the examples directory. The structure of the design file is shown below:
name: {design_name} # optional name of the toplevel
ips:
# specify relations between IPs instance names in the
# design yaml and IP cores description YAMLs
{ip1_instance_name}:
file: {resource_path} # see "Resource path syntax" section for more information
parameters: # specify IP parameter values to be overridden
{parameters_name} : {parameters_value}
...
clocks:
{module_clock_name}: {domain_name}
resets:
{module_reset_name}: {domain_name}
...
connections:
ports:
# specify the incoming ports connections of an IP named `ip1_name`
{ip1_name}:
{port1_name} : [{ip2_name}, {port2_name}]
# connections between modules can be (bit-wise) inverted
{port3_name} : ~[{ip2_name}, {port4_name}]
...
# specify the incoming ports connections of a hierarchy named `hier_name`
{hier_name}:
{port1_name} : [{ip_name}, {port2_name}]
...
# specify the external port connections
{ip_instance_name}:
{port_name} : ext_port_name
# connections to external ports can be (bit-wise) inverted
{other_port_name} : ~other_ext_port_name
...
interfaces:
# specify the incoming interface connections of the `ip1_name` IP
{ip1_name}:
{interface1_name} : [{ip2_name}, {interface2_name}]
...
# specify the incoming interface connections of the `hier_name` hierarchy
{hier_name}:
{interface1_name} : [{ip_name}, {interface2_name}]
...
# specify the external interface connections
{ip_instance_name}:
{interface_name} : ext_interface_name
...
interconnects:
# see the "Interconnect generation" page for a detailed description of the format
...
clock_domains:
default:
signal: [{ip_name}, {port_name}]
{other_domain_name}:
signal: [{ip_name}, {port_name}]
{another_domain_name}:
signal: {external_port_name}
...
reset_domains:
default:
signal: [{ip_name}, {port_name}]
polarity: active high
{other_domain_name}:
signal: [{ip_name}, {port_name}]
polarity: active low
{another_domain_name}:
signal: {external_port_name}
polarity: active low
synchronous_to: default
...
external: # specify the names of external ports and interfaces of the top module
ports:
out:
- {ext_port_name}
inout:
- [{ip_name/hierarchy_name, port_name}]
interfaces:
in:
- {ext_interface_name}
# note that `inout:` is invalid in the interfaces section
hierarchies:
# see "Hierarchies" below for a detailed description of the format
...
memory_maps:
{memory_map_name}: # this name is referenced in an interconnect
{instance_name}: # component instance reference
{interface_name1}: # interface of component
address: {address1}
{additional_parameter_name}: {value_accepted_by_an_interconnect_implementation} # additional parameters for specific interconnects can be specified
...
{interface_name2}:
address: {address2}
size: {size2} # a default size can be overwritten
...
{instance_name}: # when an instance has only one interface, it is not required to specify the interface name
address: {address1}
inout ports are handled differently than the in and out ports. When an IP has an inout port or when a hierarchy has an inout port specified in its external.ports.inout section, it must be included in the external.ports.inout section of the parent design. It is required to specify the name of the IP/hierarchy and the port name that contains it. The name of the external port is identical to the one in the IP core. In case of duplicate names, a suffix $n is added (where n is a natural number) to the name of the second and subsequent duplicate names. inout ports cannot be connected to each other.
The design description YAML format allows for creating hierarchical designs. In order to create a hierarchy, add its name as a key in the hierarchies section and describe the hierarchy design “recursively” by using the same keys and values (ports, parameters etc.) as in the top-level design (see above). Hierarchies can be nested recursively, which means that you can create a hierarchy inside another one.
Note that IPs and hierarchies names cannot be duplicated on the same hierarchy level, as each entry in hierarchies is treated as equal with entries in ips.
Additionally, designs can contain clock and reset domains. These domains allow for automatic wiring of clock and reset inputs of IP cores, and performing sanity checks for interface connections. Domains reference a port that acts as the source. This may be either a module port (for example from a PLL instance), or an external port.
Additionally, reset domains contain information about the reset signal’s polarity and whether it’s synchronous to some domain or asynchronous.
The polarity may be either active low or active high.
The synchronicity is specified via the synchronous_to key, which can be null/omitted if asynchronous, or otherwise specifies the name of a clock domain.
When instantiating IP cores, their clocks and resets can be assigned to domains via the clocks and resets keys.
If a domain is not assigned explicitly, the clock/reset signal is implicitly assigned to the corresponding default domain.
For reset connections, Topwrap will automatically invert connections between resets of different polarities. For resets that don’t have the same synchronicity, an error is raised describing the problem.
Hierarchies¶
Hierarchies allow for creating designs with subgraphs in them. The subgraphs can contain multiple IP cores and other subgraphs, allowing for the creation of nested designs in Topwrap.
Format¶
Hierarchies are specified in the design description. The hierarchies key must be a direct descendant of the design key.
The format is as follows:
hierarchies:
{hierarchy_name_1}:
# Design description, see above
...
A more complex example of a hierarchy can be found in the examples/hierarchy directory.
IP description files¶
The primary purpose of the IP core YAML is to provide the tool with essential metadata and structural information about a specific IP core to facilitate automated SoC assembly.
It contains information about signals, clock domains, parameterization and interfaces.
Interfaces are defined as a list of signals and parameters such as mode and type.
The example below presents an example IP YAML core description file.
id:
library: libdefault
name: axi4_to_ahb
vendor: vendor
parameters:
RESET_ADDRESS: 32'h80000000
signals:
in:
- sys_clk
- sys_rst
- name: core_id
bound: [3, 0]
default: 0
out:
- core_halted
interfaces:
ifu_axi:
type: AXI4
mode: manager
# the size field in manager mode is unused by topwrap
signals:
out:
bready: ifu_bready
awaddr: [ifu_awaddr, 31, 0]
# ... remainder of AXI signals ...
in:
awready: ifu_awready
rdata: [ifu_rdata, 63, 0]
# ... remainder of AXI signals ...
iface1_wb:
type: wishbone
mode: subordinate
size: 0xFFFF # the size field in subordinate mode is optional
signals:
# wishbone signals
File format explanation¶
id- identification of the Core. An instance of Identifier IR classlibrary- by defaultlibdefaultvendor- by defaultvendorname- same as the name in the HDL file
parameters- a dictionary with IP core parameterssignals- contains the names of signals used in the module portsout- a list of signals that are in the output directionin- a list of signals that are in the input directioninout- a list of signals that can be both input and output, similar to aninoutport in Verilog
interfaces- a list of interfacestype- the name of the interface definition that is used for that interface. See the Interface Definition YAML.mode-manager,subordinateorunspecified. When generating ports, the mode is used to determine the direction of the port.unspecifiedbehaves the same asmanager. When doing inference, all newly created interfaces have theirtypeset tounspecified.size- the optional field that is only used whenmodeissubordinate, it is used when there is an instance of this module specified inaddress_mapsin the design YAML.signals- a list of signals mapped to ports. Signals from the interface definition need to be mapped to ports in the HDL module.
Signals¶
Each signal (for both port and interface definitions) can be specified in one of two formats.
New signal format¶
The signal is defined by a YAML object, with the following properties:
name(required) - the name of the signal.bound(optional) - the bounds of the bit range, determining the width.slice(optional) - the bounds of the slice bit range (only applicable to interface definitions).default(optional) - default value to assign to this port if nothing is connected to it, applicable only to input ports.
Example port definition using this format:
signals:
in:
- name: foo
bound: [31, 0]
default: 32'hDEADBEEF
Example interface signal definition using this format:
interfaces:
foo:
type: AXIStream
mode: subordinate
signals:
in:
BAR:
name: bar
bound: [63, 0]
slice: [47, 16]
...
BAZ:
name: BAZ
Old signal format¶
The signal is defined by either:
just a name (implied one-bit signal):
signals: in: - fooan array consisting of the name and the bounds of the bit range:
signals: in: - [foo, 31, 0]an array consisting of the name, the bounds of the bit range, and the bounds of the slice bit range (only applicable to interface definitions):
signals: in: TDATA: [foo, 63, 0, 47, 16]
As an example, this is the description of ports in the Clock Crossing IP:
# file: clock_crossing.yaml
name: cdc_flag
signals:
in:
- clkA
- A
- clkB
out:
- B
Interface definitions¶
The purpose of the interface YAML file is to store information about interface definitions. These definitions are used for inferring ports to interface instances. It contains the id of the interface, which is used to reference the definition from the IP core YAML file.
The YAML file separates signals into those that are required for a list of ports to be interpreted as this interface and optional signals that, if present, are inferred into a single interface instance.
Regular expressions are used to find ports in the HDL module that correspond to the signals in the interface definition.
id:
library: libdefault
name: wishbone
vendor: vendor
signals:
required:
out:
cyc: cyc
stb: stb
in:
ack: ack
optional:
out:
dat_w: dat_(w|mosi)|mosi
adr: adr
tgd_w: tgd_w
lock: lock
sel: sel
tga: tga
tgc: tgc
we: we
cti: cti
bte: bte
in:
dat_r: dat_(r|miso)|miso
tgd_r: tgd_r
stall: stall
err: err
rty: rty
File format explanation:
id- identification of the interface. An instance of Identifier IR classlibrary- by defaultlibdefaultvendor- by defaultvendorname- same as the name in the HDL file
signals- contains the names of signals with regular expressions that are used in the ports of Modules.required- contains signals that must be present in the list of ports in the moduleoptional- contains signals that are connected to the interface instance when present in the list of ports in the moduleout- a list of signals that are in the output directionin- a list of signals that are in the input directioninout- a list of signals that can be both the input and the output, similar to aninoutport in Verilog
Interfaces can also specify which clock and reset input of the IP core they use.
This is done via the optional clock and reset keys, which specify the name of the input (see below).
When connecting interfaces that have clock inputs assigned on both sides of the connection, Topwrap will automatically perform sanity checks, and report errors for connections between interfaces that belong to different clock domains.
Parameterization¶
Port widths don’t have to be hardcoded, as parameters can describe an IP core in a generic way, and values specified in IP core YAMLs can be overridden in a design description file (see Design description).
parameters:
DATA_WIDTH: 8
KEEP_WIDTH: (DATA_WIDTH+7)/8
ID_WIDTH: 8
DEST_WIDTH: 8
USER_WIDTH: 1
interfaces:
s_axis:
type: AXI4Stream
mode: subordinate
signals:
in:
TDATA: [s_axis_tdata, DATA_WIDTH-1, 0]
TKEEP: [s_axis_tkeep, KEEP_WIDTH-1, 0]
...
TID: [s_axis_tid, ID_WIDTH-1, 0]
TDEST: [s_axis_tdest, DEST_WIDTH-1, 0]
TUSER: [s_axis_tuser, USER_WIDTH-1, 0]
The parameter values can be integers or math expressions.
Clock and reset inputs¶
IPs can define clock and reset inputs, which allow for automatic connections to clock/reset domains within a design. Each clock/reset specifies a port that is used for it. Additionally, reset inputs specify polarity and synchronicity similarly to reset domains in the design description.
Example clock and reset definition:
# Two independent clocks
clocks:
some_clock:
signal: some_clock_port
some_other_clock:
signal: some_other_clock_port
# Two resets, one synchronous, one asynchronous
resets:
some_reset:
signal: some_reset_port
polarity: active high
synchronous_to: some_other_clock
some_other_reset:
signal: some_other_reset_port
polarity: active low
synchronous_to: null
In both cases, the signal key defines which of the module’s ports belongs to this clock/reset.
The polarity and synchronous_to keys are the same as the ones in the design description, with one difference: in IP cores synchronous_to references a clock input, not a clock domain.
Interface definition files¶
Topwrap can use predefined interfaces, as illustrated in YAML files that come packaged with the tool. The currently supported interfaces are AXI3, AXI4, AXI Lite, AXI Stream and Wishbone.
An example file looks as follows:
name: AXI4Stream
port_prefix: AXIS
signals:
# The convention assumes the AXI Stream transmitter (manager) perspective
required:
out:
TVALID: tvalid
TDATA: tdata
TLAST: tlast
in:
TREADY: tready
optional:
out:
TID: tid
TDEST: tdest
TKEEP: tkeep
TSTRB: tstrb
TUSER: tuser
TWAKEUP: twakeup
The name of an interface must be unique.
Signals are either required or optional, and their direction is described from the perspective of the manager (i.e. the direction of signals in the subordinate are flipped).
Note that clock and reset are not included as these are usually inputs to both the manager and subordinate, so they are not supported in the interface specification.
Every signal is a key-value pair, where the key is a generic signal name (normally taken from the interface specification) and used to identify it in other parts of Topwrap (i.e. IP core description files), and the value is a regex used to deduce which port defined in the HDL sources represents this signal.
Interface compliance¶
During the build process, an optional verification of whether the interface instances used in IP cores are compliant with their respective descriptions can be enabled. The verification consists of checking in the instance if:
all signals designated as required in the description are included.
no additional signals beyond those defined in the description are included.
This feature is controlled by the --iface-compliance CLI flag or the force_interface_compliance key in the configuration file and is turned off by default.
Resource path syntax¶
Fields specified in the YAML file as a “resource path” support extended functionality and have their own specific syntax.
This field type is used for example in the Design Description for specifying an IP Core description location:
ips:
ip_inst_name:
file: {resource path}
...
The syntax is as follows:
SCHEME[ARG1|ARG2...]:SCHEME_PATH
SCHEMEis the scheme of this path (e.g.getfor remote resources)ARGSare|-separated positional arguments for the specific scheme (e.g. the user repo name for thereposcheme)If there are no arguments to supply you can omit the square brackets entirely
SCHEME_PATHis the path to the resource interpreted by the specific scheme (e.g. the URL for thegetscheme)
Available schemes¶
fileSCHEME_ARGS: NoneSCHEME_PATH: A filesystem path relative from the currently edited YAML file to the resource
repoSCHEME_ARGS: Repository nameSCHEME_PATH: A name of resource
getSCHEME_ARGS: NoneSCHEME_PATH: The URL address of the remote resource. Onlyhttp(s)://URLs are currently supported.
Warning
If a repository was injected to the config with the --repo CLI option, the name of such repository will be the name of the directory containing it.
Examples¶
file:./my_directory/file.txt
A path to the file on the filesystem.
repo[builtin]:axi_protocol_converter
This loads the axi_protocol_converter core located in the builtin user repository.
repo[my_repo]:res.txt
This loads the res.txt file inside the my_repo loaded user repository.
get:https://raw.githubusercontent.com/antmicro/topwrap/refs/heads/main/pyproject.toml
This loads the remote resource. When necessary, it’s automatically downloaded into a temporary directory.