Build System

This build system has been developed for easier implementation and simulation of the large projects and individual components as well. The main idea is based on the uniform definition of components hierarchy, which is used for sythesis and simulation purposes. Translation system is mainly implemented using Makefile and Tcl scripts. The Tcl language is independent of target operation system and it is supported in the most of tools dedicated for hardware developement.

Hierarchy description in Modules.tcl

The objective of hierarchy description is to define a structure of complex projects. Generally, a project is composed of several components and a component is recursively composed of several subcomponents and modules. Further, each subcomponent can occure in several instances and each subcomponent instance can use a different architecture. The Modules.tcl file describes all direct necessary dependencies (e.g. instantiated subcomponents or packages) for the component and the source file of the component itself. Sometimes it may also be appropriate to include more independent components into one Modules.tcl file as one bundle. In such cases, it is recomended to include only source files from the same directory as the Modules.tcl file, exceptionally from direct subdirectories. file.

For the definition of a component structure two variables are reserved - MOD and COMPONENTS. The MOD variable specifies the list of modules (typically VHDL files in current directory) while the COMPONENTS variable specifies the subcomponent list of the component.

Variables in Modules.tcl obtained by the build system

  • PACKAGES defines the list of VHDL files, which serve as packages. These packages are usually used at the begining of VHDL files using command “use library.package_name.function”. Via this build system, packages are translated sooner than other VHDL modules and components. During translation, the order of files defined in variable is preserved. Common packages used by multiple sources are usually included in Modules.tcl for top-level, not in component’s Modules.tcl.

  • MOD variable defines the list of modules (VHDL or Verilog source files), which specify the component structure. It’s important to preserve the order of modules declared in this list. Module used by another module in current Modules.tcl scope has to be defined sooner in the list.

  • COMPONENTS defines the list of subcomponents, needed for the component. Each item of the list is also the list with following parameters:

    [list ENTITY ENTITY_BASE ARCHGRP]
    
    • ENTITY specifies the entity name. It’s used by the Translation System to distinguish between several entities in the same directory, although it is not commonly used.

    • ENTITY_BASE defines the path to the subcomponent. It’s strongly recommended to specify this path relatively to another directory (mostly to the root folder of currently used git repository), the current ENTITY_BASE should be used for this purpose. Target path must contain the Modules.tcl file, which will be parsed.

    • ARCHGRP specifies the architecture (or more architectures) of a subcomponent. For example, architecture can be an empty implementation (string EMPTY) or a full implementation (string FULL), but the value can also be a list of specific configurations to its subcomponents.

  • SV_LIB List of dynamic libraries used for SystemVerilog DPI verification. If the dynamic library file doesn’t exist, the build system requires existing Makefile in the same directory and executes make.

Note

Do not include prefix (lib) nor suffix (.dll or .so) in the filename.

Variables ENTITY, ENTITY_BASE and ARCHGRP are predefined (provided) by the build system to be used in every Modules.tcl file. Their values are obtained from the respective COMPONENT list item of the ancestor’s Modules.tcl.

Note

Prefer to use lappend MOD "myfile.vhd" instead of set MOD "$MOD myfile.vhd", because the lappend better express the operation and is faster.

PLATFORM_TAGS

In the situation, when a platform (build tool: Quartus, Vivado, simulator: Questa Sim, etc.) supports various architectures / implementation schemes, the PLATFORM_TAGS list variable can be used to distinguish, which source file should be included into project.

List of available platforms:

  • xilinx - platform supports complete set of Xilinx component and products

  • altera - platform supports complete set of Intel/Altera component and products

Those tags are currently not available, but show the way of potential extension and usage:

  • vivado:ge_2024.0 - Vivado version is equal or greater than 2024.0 (this doesn’t means automatic comparison)

  • xilinx:usp - Restrict for UltraScale+ platform.

  • xilinx:sim:gty - Inculde simulation models from highspeed transceivers.

Priority for PLATFORM_TAGS

The PLATFORM_TAGS list can be potentially used also for specifying preference/priority (latter position in list means higher priority): set PLATFORM_TAGS "xilinx:bram:behav xilinx:bram:macro" The priority can be easily overriden simply by appending item to the list. (Also the lsearch can be easily used as returns -1 for non-existing item, which means lowest priority.) In general, the highest priority tag from set of supported tags can be obtained by the proc nb_preference_filter {PLATFORM_TAGS SUPPORTED_TAGS}

set SUPPORTED_PLATFORM_TAGS "xilinx altera"
set TARGET_TAG [nb_preference_filter $PLATFORM_TAGS $SUPPORTED_PLATFORM_TAGS]

List of properties used in MOD variables

For translation, it is often required to specify more of the details about items (files) present in MOD and PACKAGES variables. In this case the translation system can get a list with property name and value pairs instead of a single item, e.g.:

lappend MOD [list $ENTITY_BASE/myfile.vhd LIBRARY another_lib SIM_MODULE glbl]
  • LIBRARY specifies another library name than default work into which the module will be compiled.

  • TYPE overrides automatically selected file type which is otherwise based on the file extension. Currently supported types are:

    • CONSTR_QUARTUS

    • CONSTR_VIVADO

    • VIVADO_IP_XACT - automatically used for xci files

  • SCOPED_TO_REF - only for the CONSTR_VIVADO type, calls set_property SCOPED_TO_REF for the file

  • PROCESSING_ORDER - only for the CONSTR_VIVADO type, calls set_property PROCESSING_ORDER for the file

  • USED_IN - only for the CONSTR_VIVADO type, calls set_property USED_IN for the file

  • VIVADO_SET_PROPERTY calls set_property {*}$value for the file

  • SIM_MODULE - the file uses another module for simulation, which must be simulated together like this: vsim extra_module testbench

  • SIM_LIB - the file uses a simulation library which must be loaded like this: vsim -L extra_library testbench

Example of using properties

lappend MOD [list $ENTITY_BASE/dp_bmem_behav.vhd VIVADO_SET_PROPERTY [list -quiet FILE_TYPE {VHDL}]] ;# set the VHDL98 standard for this file
lappend MOD [list "$ENTITY_BASE/bus_handshake.xdc" TYPE "CONSTR_VIVADO" SCOPED_TO_REF "ASYNC_BUS_HANDSHAKE" PROCESSING_ORDER "LATE"]

List of properties used in SV_LIBS

  • MAKE_PARAMS - value will be passed to make command as the parameters

Example of using Modules.tcl variables

# HFE top level entity
if {$ENTITY == "HFE_TOP"} {
   if {$ARCHGRP == "FULL"} {
      # This architecture relies on HFE component, which is located
      # in the same directory as current entity and shares this Modules.tcl file.
      lappend COMPONENTS [list HFE $ENTITY_BASE FULL]

      # This file will be compiled to library work
      lappend MOD $ENTITY_BASE/file_to_work.vhd

      # This file will be compiled to library anotherlib
      lappend MOD [list $ENTITY_BASE/file_to_anotherlib.vhd LIBRARY anotherlib]
   }

   if {$ARCHGRP == "EMPTY"} {
      lappend MOD $ENTITY_BASE/hfe_empty.vhd
   }
}

# HFE core entity
if {$ENTITY == "HFE"} {
   if {$ARCHGRP == "FULL"} {
      lappend MOD "$ENTITY_BASE/hfe_pipe.vhd"
      lappend MOD "$ENTITY_BASE/hfe_parser.vhd"
      lappend MOD "$ENTITY_BASE/hfe_full.vhd"
   } elseif {$ARCHGRP == "EMPTY"} {
      lappend MOD "$ENTITY_BASE/hfe_empty.vhd"
   }
}

Component synthesis

Synthesis of the component is typically handled by a simple user-created Makefile. It can be located anywhere, but the recommendation is to use the synth subdirectory of the synthesized component. The Makefile sets the TOP_LEVEL_ENT variable and calls the comp target from global Makefile located in $OFM_PATH/build/Makefile, which must be included. After calling make the synthesis will be performed.

Advanced synthesis configuration

User can specify those variables in Makefile:

  • TOP_LEVEL_ENT - required. Name of the synthesized entity.

  • TOP_LEVEL_PATH - optional, default value is “..”. Path to the Modules.tcl with the synthesized entity.

  • TOP_LEVEL_ARCHGRP - optional, default value is “FULL”.

  • CLK_PORTS - optional, default value is CLK. Name or list of space-separated names of component ports which serves as clock input.

  • CLK_PERIOD - optional, default value is 5.0. One or more space-separated integer or float values. Clock constraints will be generated with this value in ns. If there are more CLK ports than period values, unspecified periods will be calculated with a simply formula (add 1.0 for each next clock).

  • SYNTH - optional, default value is “vivado”. Synthesis tool can be {vivado, quartus}. For lazy users, there is a vivado / quartus target in global Makefile, which sets this variable and calls make recursively with the default target.

Example of Makefile for component synthesis

TOP_LEVEL_ENT=RX_MAC_LITE
TOP_LEVEL_PATH=../../mac/rx

SYNTH=quartus

CLK_PORTS=RX_CLK TX_CLK MI_CLK
CLK_PERIOD=3.500 2.500 5.000

.PHONY: all
all: comp

include ../../../../../build/Makefile

The comp target in Makefile

The make comp runs the comp_$(SYNTH).tcl script located in $OFM_PATH/build/targets/ with the synthesis tool. Script sets some default values for mandatory variables and fetches environment variables listed above. The script also tries to source Vivado.inc.tcl / Quartust.inc.tcl file (if it exists) in a current directory. This can be useful for overriding some variables, e.g. SYNTH_FLAGS or CONSTR_TEXT.

User should override the CONSTR_TEXT variable in this file for example when the TOP_LEVEL_ENT has very specific clock/constraints requirements. The constraint file for current synthesis tool is generated from the CONSTR_TEXT variable at the end of the preparation. The file is overwritten only when needs to be updated, otherwise is leaved untouched, which is useful for typical make run: If all sources are unchanged from the last build, the targed file (synthesised project) is up-to-date and doesn’t need to rebuild.

Finally, the script calls default Tcl target (proc target_default) which then passes to SynthesizeProject procedure documented below.

Chip design synthesis and implementation

It is a good practice to split common functionality from application specific functionality:

  1. top-level entity of card together with main constraints and build scripts,

  2. application entity for end user with minimum build scripts.

In this scheme, the process basically starts at the user Vivado/Quartus.tcl file (the default value of SYNTHFILES variable in Makefile) where the user includes a common build script from a top-level entity. This fills the HIERARCHY array with varables COMPONENTS and MOD and sets up other neccessary values in SYNTH_FLAGS array.

After Tcl interpreter goes back from common build script, the user tcl should add architecture (implementation) of application entity into the appropriate variables of HIERARCHY array, either MOD or COMPONENTS. User tcl can tune some values of SYNTH_FLAGS as well.

Final step in user tcl file is to call the nb_main procedure, which passes to SynthesizeProject procedure within target_default similarly as in the comp target.

SynthesizeProject

1. Init phase (SetupDesign)

This creates a project within synthesis tool, sets the FPGA device type and does the necessary project setup before adding any source files.

2. File add phase (AddInputFiles)

In this stage, files and components are processed from HIERARCHY array and passed to procedure EvalFile. EvalFile is called for each entry in PACKAGES/MOD variables and should instruct the synthesis tool to compile source file including fine-tunnig of additional properties based on extra file properties

3. Synthesis and Implemenation (SynthetizeDesign, ImplementDesign)

Procedures configure rest of parameters of the project and run the main process: the synthesis and the implementation.

4. Final phase (SaveDesign)

In this step, the binary programming file is generated.

Other features of the build system

EvalFile

EvalFile procedure is specific for each synthesis tool and is being used as callback when the common code goes through hierarchy of modules. Procedure usually adds source files into the project and sets additional properties based on extra file properties.

Batch feature in EvalFile

Although the EvalFile procedure receives one file for processing in each call, it can use the lazy evaluation mechanism, which processes a batch of source files in one command run. This mechanism is enabled in the simulation environment (Modelsim.inc.fdo file), where it has significantly positive impact on compilation time.

Source files which have the same compile flags (e.g. same library name or -vhdl2008 parameter) are stored into the special variable together with the flags instead of being processed (compiled) immediately. When EvalFile gets a file with a different set of flags, the files stored inside the batch variable must be compiled immediately, the variable is then emptied and the newly evaluated file is inserted into it. At the end of the AddInputFiles phase, the last batch must be compiled explicitely.

Makefile

There are few mechanisms in the global Makefile which deserve an explanation.

Some targets in the Makefile are aware of unchanged files. If none of the source files for such target has been modified and the target already exists, it will not be remade. This is handled by the make itself, but the build system must supply a list of source files. The list is generated by executing Tcl target called ‘makefile’, which goes through entire hierarchy of Modules.tcl, gathers filenames and writes the list in form of target: prerequisites into $PROJECT.$SYNTH.mk file, which is simply included in the main Makefile. This approach is not so simple and hides some caveats. Makefile doesn’t propagate target specific variables to global scope and it is unreliable to get prerequisites for generated Makefile. Hence the generated Makefile is created (by shadowed target with same name) in the first make run (only for concerned targets) and the real main target is launched in a recursive run of make.

Environment variables available in make run aren’t exported to subprocess, except variables which are set using the export keyword. If the user needs to pass an environment variable into tclsh or synthesis tool, it’s better he uses the USER_ENV variable. It is a necessity to export user defined variable for targets which needs a generated makefile mentioned above.

There are also targets, which can trigger an user defined procedure in Tcl: ttarget_% and starget_%. The user defines a Tcl procedure named for example target_myproc. Executing make ttarget_myproc will trigger the stem target: either bare tclsh (ttarget) or synthesis tool (starget) is started, the $SYNTHFILES script is sourced and if the script includes common build/[Vivado|Quartus|...].inc.tcl script and runs nb_main (which is recommended for best integration with build system), the user defined procedure will be run.

This is also used for generating a source files inside the Tcl from make target. Common used files are DevTree.dts/dtb/vhd and user_const.vhd.

The (incomplete) list of SYNTH_FLAGS array items

  • PROJ_ONLY {false, true}: Only the project file will be created. Neither synthesis nor implementation will be run.

  • SYNTH_ONLY {false, true}: Only the synthesis will be run, the implementation will be skipped.

  • PHASE_SAVE {true, false}: Do not generate programming files and archives after implementation.

  • DEVICE {ULTRASCALE, VIRTEX7, STRATIX10, AGILEX}: Sets the FPGA family. In the comp target is mapped to specific FPGA.

  • FPGA {xcvu7p-flvb2104-2-i, 1SD280PT2F55E1VG, …}: Sets the FPGA part directly.

  • SETUP_FLAGS: List of specific flags for entire project:
    • USE_XPM_LIBRARIES: includes XPM_CDC XPM_MEMORY XPM_FIFO in Vivado projects

For other values and their purpose see the Vivado.inc.tcl or Quartus.inc.tcl file in the build directory.

Device Tree nodes

For the software to find internal firmware components, a Device Tree (DT) is used. This provides a tree of available parts (called nodes in the DT terminology) of the design that can be accessed from the host without restriction. A developer creates TCL procedures that generate nodes to DT string (DTS) for the components he finds fit. Since the creation of the DTS can be challenging, there are several TCL procedures provided that simplify the process. These procedures are contained within the dts_templates.tcl file with clarifying comments. The following examples provide an overview of their usage.

Example 1

This presents a least viable code that creates a node dma_calypte_rx_perf_cntrs0 with the base address 0x8000 and the size 0x30. It also contains a compatible string cesnet,dma_calypte_rx_perf_cntrs. The string property is appended to dts variable that contains a reference to the required Device Tree string (DTS).

dts_create_node dts "dma_calypte_rx_perf_cntrs0" {
    dts_appendprop_comp_node dts 0x8000 0x30 "cesnet,dma_calypte_rx_perf_cntrs"
}

Example 2

A second, more complex example demonstrates addition of multiple properties to a node called dma_ctrl_calypte_$dir$id (string can be further adjusted through parameters dir and id).

proc dts_dma_calypte_ctrl {DTS dir id base pcie} {
    upvar 1 $DTS dts

    dts_create_node dts "dma_ctrl_calypte_$dir$id" {
        # Adding compatible string "cesnet,dma_ctrl_calypte_$dir" and the
        # reg property with base address $base and the size 0x80.
        dts_appendprop_comp_node dts $base 0x80 "cesnet,dma_ctrl_calypte_$dir"
        # Integer property called "version" with the value 0x10000
        dts_appendprop_int dts "version" 0x10000
        # Integer prperty "pcie" with the value of $pcie
        dts_appendprop_int dts "pcie" $pcie

        # The addition of custom properties (customly named) can be done
        # through a standard "append" macro.
        if { $dir == "tx" } {
            append dts "data_buff = <&dma_calypte_tx_data_buff$id>;"
            append dts "hdr_buff = <&dma_calypte_tx_hdr_buff$id>;"
        }
        append dts "params = <&dma_params_$dir$pcie>;"
    }
}

Example 3

This example shows how complex node with multiple subnodes is created. The parent node is called dma_calypte_test_core0 and contains subnodes mfb_loopback0, dma_calypte_debug_core0, dma_calypte_latency_meter0 and dma_calypte_reset_fsm0. Further nesting of nodes is possible as can be seen when adding the mfb_generator0 node. Each of the called procedures contain a reference to the same DTS from the dts variable.

proc dts_calypte_test_core {DTS base_addr} {
    # Populate reference from the calling environment
    upvar 1 $DTS dts

    set LOOPBACK_BASE_ADDR      [expr $base_addr + 0x0]
    set TX_DBG_CORE_BASE_ADDR   [expr $base_addr + 0x10000]
    set LATENCY_METER_BASE_ADDR [expr $base_addr + 0x20000]
    set RESET_FSM_BASE_ADDR     [expr $base_addr + 0x30000]

    dts_create_node dts "dma_calypte_test_core0" {

        dts_create_node dts "mfb_loopback0" {
            dts_appendprop_comp_node dts $LOOPBACK_BASE_ADDR 8 "cesnet,mfb_loopback"
        }

        dts_create_node dts "dma_calypte_debug_core0" {
            dts_appendprop_comp_node dts $TX_DBG_CORE_BASE_ADDR 0x1600 "cesnet,dma_calypte_debug_core"

            dts_create_node dts "mfb_generator0" {
                dts_appendprop_comp_node dts [expr $TX_DBG_CORE_BASE_ADDR+0x8000] 0x40 "cesnet,mfb_generator"
            }
        }

        dts_create_node dts "dma_calypte_latency_meter0" {
            dts_appendprop_comp_node dts $LATENCY_METER_BASE_ADDR 0x30 "cesnet,dma_calypte_latency_meter"
        }

        dts_create_node dts "dma_calypte_reset_fsm0" {
            dts_appendprop_comp_node dts $RESET_FSM_BASE_ADDR 0x4 "cesnet,dma_calypte_reset_fsm"
        }
    }
}