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 interconnects and IP cores

  • setting parameter values and describing hierarchies for the project

  • connecting the IPs and hierarchies

  • picking external ports (those which will be connected to the physical I/O).

You can see example design files in the examples directory. The structure of the design file is shown below:

ips:
  # specify relations between IP instance names in the
  # design yaml and IP cores description YAMLs
  {ip_instance_name}:
    file: {path_to_ip_description}
  ...

design:
  name: {design_name} # optional name of the toplevel
  hierarchies:
      # see "Hierarchies" below for a detailed description of the format
      ...
  parameters: # specify IP parameter values to be overridden
    {ip_instance_name}:
      {parameters_name} : {parameters_value}
      ...
  ports:
    # specify the incoming ports connections of an IP named `ip1_name`
    {ip1_name}:
      {port1_name} : [{ip2_name}, {port2_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
    ...

  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
    ...
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

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 design 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. For example, the design section cannot contain two identical keys, but it is possible to have ip_name key in this section and ip_name in the design section of a separate hierarchy.

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}:
      ips: # ips that are used on this hierarchy level
        {ip_name}:
          ...

      design:
        parameters:
          ...
        ports: # ports connections internal to this hierarchy.
          # note that also you have to connect port to it's external port equivalent (if exists).
          {ip1_name}:
              {port1_name} : [{ip2_name}, {port2_name}]
              {port2_name} : {port2_external_equivalent} # connection to external port equivalent. Note that it has to be the parent port.
            ...
        hierarchies:
          {nested_hierarchy_name}:
            # structure here will be the same as for {hierarchy_name_1}
            ...
      external:
        # external ports and/or interfaces of this hierarchy; these can be
        # referenced in the upper-level `ports`, `interfaces` or `external` section
        ports:
            in:
              - {port2_external_equivalent}
        ...
    {hierarchy_name_2}:
      ...

A more complex example of a hierarchy can be found in the examples/hierarchy directory.

IP description files

Every IP wrapped by Topwrap needs a description file in the YAML format.

The ports of an IP should be placed in the global signals key, followed by the direction - in, out or inout. The module name of an IP should be placed in the global name key, and it should be consistent with the definition in the HDL file.

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

The previous example can be used with any IP. However, in order to benefit from connecting entire interfaces simultaneously, the ports must belong to a named interface as in this example:

#file: axis_width_converter.yaml
name: axis_width_converter
interfaces:
    s_axis:
        type: AXIStream
        mode: subordinate
        signals:
            in:
                TDATA: [s_axis_tdata, 63, 0]
                TKEEP: [s_axis_tkeep, 7, 0]
                TVALID: s_axis_tvalid
                TLAST: s_axis_tlast
                TID: [s_axis_tid, 7, 0]
                TDEST: [s_axis_tdest, 7, 0]
                TUSER: s_axis_tuser
            out:
                TREADY: s_axis_tready
    m_axis:
        type: AXIStream
        mode: manager
        signals:
            in:
                TREADY: m_axis_tready
            out:
                TDATA: [m_axis_tdata, 31, 0]
                TKEEP: [m_axis_tkeep, 3, 0]
                TVALID: m_axis_tvalid
                TLAST: m_axis_tlast
                TID: [m_axis_tid, 7, 0]
                TDEST: [m_axis_tdest, 7, 0]
                TUSER: m_axis_tuser
signals: # These ports do not belong to an interface
    in:
        - clk
        - rst

The names s_axis and m_axis will be used to group the selected ports. Each signal in an interface has a name which must match with the signal that it is connected to, for example TDATA: port_name must connect to TDATA: other_port_name.

To speed up the generation of YAMLs, Topwrap’s parse command (see Generating IP core description YAMLs) can be used to generate YAMLs from HDL source files.

Port widths

You can specify the port width in the following format:

signals:
    in:
        - [port_name, upper_limit, lower_limit]
  • port_name - name of the port.

  • upper_limit and lower_limit define the bit range, where [upper_limit, lower_limit] determines the number of bits for the port (e.g. [63, 0] for 64 bits).

As an example:

signals:
    in:
        - [gpio_io_i, 31, 0] # 32 bits

If the bit range is omitted, as in the example below, then the default width of port_name is 1 bit.

signals:
    in:
      - port_name

You can also specify the signal width within interfaces.

interfaces:
    s_axis:
        type: AXIStream
        mode: subordinate
        signals:
            in:
                TDATA: [s_axis_tdata, 63, 0] # 64 bits
                ...
                TVALID: s_axis_tvalid # defaults to 1 bit
  • TDATA is assigned to s_axis_tdata and is 64 bits wide, defined by [63, 0].

  • TVALID is assigned to s_axis_tvalid and, without a specified range, defaults to 1 bit.

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.

Port slicing

Ports can be sliced for using some parts of the port as a signal that belongs to a defined interface.

As an example: Port m_axi_bid of the IP core is 36 bits wide. Use bits 23..12 as the BID signal of the m_axi_1 AXI manager

m_axi_1:
    type: AXI
    mode: manager
    signals:
        in:
            BID: [m_axi_bid, 35, 0, 23, 12]

Interface description 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 deduction

During IP core parsing, you can use the --iface-deduce flag to enable automatic pairing of raw ports from HDL sources to interface signals.

This feature matches signal regexes from all available interface descriptions with raw port names of the IP core in order to discover possible interface instances. The pairing is performed on port names that are transformed to lowercase and have the common port_prefix removed, which means that the regexes must also be written in lowercase.

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.


Last update: 2024-12-10