Using TCL in FPGA Development

    Hello! I haven’t written articles on my favorite topics for a long time and have finally matured to something more or less decent and worthwhile. This article will discuss a very interesting task that a development engineer faces almost every day. I suggest you look at how you can use the full power and simplicity of TCL scripts for designing on FPGA . In this article, the description is based on Xilinx FPGAs , but this does not negate the capabilities of TCL scripts for FPGAs from other manufacturers.


    Interesting? Go…

    What is TCL?


    TCL (Tool Command Language) is a high-level scripting language for performing various tasks. Often, TCL is used in conjunction with the Tk graphical shell (Tool Kit) , but this aspect will not be considered in this article. The language is widely used in various tasks of process automation:

    • Testing complex modules, nodes, code parts;
    • Speed ​​prototyping
    • Creation of graphical interfaces for console applications;
    • Implementation in application applications and tasks.

    One way or another, the main function of TCL is the automation of routine tasks , and a significant reduction in the time spent on development. TCL programs do not require compilation and compilation , which makes the task of debugging scripts simple and straightforward. The TCL interpreter is distributed under a free license and is available for almost all platforms (in many Linux distributions it is available by default). This means that you can use it in the development of private programs and proprietary applications without any restrictions. At the time of writing, the current version of TCL is 8.6. Many distributions are available for working with TCL scripts, debugging and visualizing them - MyTcl, TclKit, ActiveTcletc. The price for 1 ActiveTcl license is about ~ $ 1,500, which is unjustified for the development of commercial applications. From personal practice, most developers use the familiar command line.

    All TCL programs consist of commands that are separated by a ";" or a newline character. As in many other programming languages, the first word is a command, the rest of the words are the arguments of the commands.

    command arg1 argt2 ... argN

    For example:

    set NewValue “Hello World!”
    puts $NewValue

    The first command creates the NewValue variable , and the second command prints the value of the variable to the console. In order to use variables with spaces , quotation marks are used. In other cases, they are not required. The result of the execution of the commands is presented in the figure below:


    In my opinion, the main convenience of TCL is that any argument to a command can be replaced by another command . To do this, it must be placed in square brackets. In the example below I will show this feature. Among other things, TCL is able to control program behavior.based on various events. This means that the command handler can perform certain actions not only by the condition recorded in the script, but also by various external events (changing the value of the variable in the external file, capturing data in the channel, terminating the application, reaching the timer counter of a certain value and etc.). TCL language is rich in a set of commands, it contains quite convenient means of working with data arrays and regular expressions. On TCL, the ability to write functions and procedures is implemented, a description of loops and expressions by condition is available, which greatly facilitates the writing of code.

    Why do you need TCL?


    Almost all FPGA / ASIC developers sooner or later encounter the TCL language in their projects. In modern FPGA development, TCL scripts are actively used for automation and process integration tasks. TCL is included in all leading CAD FPGAs - Quartus for Altera , ISE Design Suite and Vivado for Xilinx . What allows TCL?

    • project creation (adding source files, setting options, design hierarchies, assigning a top-level file, etc.),
    • synthesis and tracing (up to the creation of independent stages with different settings),
    • testing of finished nodes, individual modules and the entire project,
    • automatic generation of restriction files (UCF / XCI) based on templates,
    • checking time constraints for a synthesized and traced project.
    • setting parameters of FPGA circuits, components and primitives, setting options for IP cores,

    etc.

    All these stages, one way or another, are the basic operations in the development process on FPGAs: from creating models of node behavior in VHDL / Verilog languages ​​to debugging a finished project in CAD at the synthesis and tracing stage. Typically, complex projects contain a large number of modules written by different developers, several IP cores, restriction files, libraries, and feature packages. As a result, the completed project has a certain hierarchical structure and a set of rules for connecting certain modules to the required project nodes. It is difficult for a developer to keep in mind the knowledge of where and how debugged modules should be located and what functions they perform if they are used in his work, but knowledge of their work is not required at the development stage (the so-called “ black-box””Modules). A TCL script comes to the rescue, which manages the project structure and links the required nodes according to pre-prepared templates. This provides flexibility in development and enables repeatability of complete nodes when migrating from one project to another.

    As a rule, simultaneously with the stage of creating new nodes for FPGAs, the stage of debugging these nodes separately from the project and in conjunction with the completed system proceeds. Primary modeling is carried out abstracted from the FPGA on the computer in specialized CAD systems and modeling environments: these are Modelsim, ISim, Aldec Active-HDL and others. To accomplish the task of debugging projects, TCL scripts also come to the rescue, allowing you to process events that occur during modeling and make decisions based on the results of the model. When debugging an RTL node purely in HDL languages, it may be difficult to write a model, since any change in the behavior of the circuit will lead to the need to change the model and test suites. Using a bunch of models in HDL language and TCL scripts is quite convenient and for many solutions allows you to speed up the debugging process,

    The stages of writing code and debugging are followed by the usual steps of synthesizing, placing, and tracing a project in an FPGA chip. Perhaps this is one of the most difficult steps, which requires large computing resources of the workstation and a long execution time to complete. TCL scripts allow you to manage execution events at each stage, analyze the results of certain calculations to achieve the best design and routing characteristics (amount of resources used, maximum clock frequencies, acceptable latency values ​​for timings, etc.). In addition, TCL makes it possible to exclude routine actions for selecting and changing settings, restarting the verification stages, restarting a specific stage when creating the FPGA firmware file.

    I hope that after reading these lines, you are already convinced that TCL is a convenient and powerful thing that you absolutely need to use in your projects. Below I will analyze one of the useful scripts that our team uses to create a project in the Vivado environment, to add already written source files, all kinds of IP kernels, XCI restriction files, and much more.

    TCL your FPGA!


    Consider one of the simplest TCL scripts to automatically create an FPGA project. The preliminary steps are very minimal: the local machine requires a directory with the source code of the project , as shown in the figure below.


    For convenience, I use independent catalogs for projects created in the Xilinx ISE Design Suite and in Vivado , if the FPGA family allows it ( Series 7: Artix, Kintex, Virtex ). The source files are in the / src directory , the vivado project is in the directory of the same name, and the project for the ISE environment is created in the / ise directory , but the results of the synthesis and wiring are stored in the / implement directory . All this is done for the convenience of managing the project as a whole and independent management in different environments. It also makes the hierarchy more visible and saves you from a bunch of junk files in the source. Separately, the / top directory should be noted .in the source directory, where the top-level file and the necessary restriction files are located (for ISE it is a * .ucf file, for Vivado it is a * .xdc file).

    The project contains mixed IP cores - old ones created in ISE and new ones created in Vivado. The core_k7 directory contains all the kernels created in CoreGenerator for ISE. They are not regenerated or updated when used in a Vivado project (the * .vhd file is used for modeling, the * .ngc file is used for synthesis, and the * .xco file is not added to the Vivado project). The / ipcores directory contains new kernels in the * .xci format, created directly in the Vivado environment. It should be noted that for each core a separate sub-directory is required, otherwise the attribute “ LOCKED ” is set for IP cores in the project”, Which makes it impossible to update the nucleus and regenerate them for synthesis.

    Let's move on to the description of the TCL script:

    # Stage 1: Specify project settings
    set TclPath [file dirname [file normalize [info script]]]
    set NewLoc [string range $TclPath 0 [string last / $TclPath]-5]
    set PartDev "xc7k325tffg900-2"
    set PrjDir [string range $TclPath 0 [string last / $NewLoc]]
    set TopName [string range $NewLoc [string last / $NewLoc]+1 end]

    The first line looks for the location of the TCL script on the local machine (located in the src / tcl directory ) and creates a string variable with the full path to the file.

    The second line creates an additional variable from which part of the path is cut. Both variables are needed so that the following variables do not manually indicate the path to the project and the name of the top-level file.

    The PartDev variable contains the name of the FPGA chip. And this is the only variable that changes in the project! All other lines of the script remain unchanged in any project.

    # Stage 2: Auto-complete part for path
    set PrjName $TopName.xpr
    set SrcDir $PrjDir/$TopName/src
    set VivNm "vivado"
    set VivDir $PrjDir/$TopName/$VivNm
    cd $PrjDir/$TopName
    pwd
    if {[file exists $VivNm] == 1} { file delete -force $VivNm }
    file mkdir $VivNm
    cd $VivDir

    At the next stage, additional variables are created that determine the location of the source files, create the vivado directory if it is not, etc. I want to note that I am checking for the presence of the vivado directory on the local machine. If the directory exists, it is deleted and recreated so that there are no conflicts in the new project.

    The cd command changes the working directory, and the pwd command shows the location of the working directory.

    # Stage 3: Find sources: *.vhd, *.ngc *.xci *.xco *.xdc etc.
    # This stage used instead of: add_files -scan_for_includes $SrcDir
    set SrcVHD [findFiles $SrcDir "*.vhd"]
    set SrcVer [findFiles $SrcDir "*.v"]
    set SrcNGC [findFiles $SrcDir "*.ngc"]
    set SrcXCI [findFiles $SrcDir "*.xci"]
    set SrcXDC [findFiles $SrcDir "*.xdc"]
    set SrcPCI [findFiles $SrcDir "cl_pcie*"]
    set NewLoc [string range $SrcPCI 0 [string last / $SrcPCI]-6]
    

    Everything is primitive and clear here - variables are created that define the names of all the source files in the / src directory. To find files, use the findFiles procedure, which we will return to later.

    A separate search is made for the component of the PCI-E node, which is the basic and integral part for all our projects.

    # Stage 4: Find all subdirs for IP cores (VHD, XCO, NGC, EDN)
    set PrjAll {}
    lappend PrjAll $DirIps $DirAdm $SrcDir/core_v2_ise $SrcDir/core_v4_ise $SrcDir/core_v5_ise $SrcDir/core_v6_ise $SrcDir/core_k7 $SrcDir/TestBench
    set SrcSim {}
    for {set i 0} {$i < [llength $PrjAll]} {incr i} {
    	set SrcXXX [findFiles [lindex $PrjAll $i] "*.vhd"]
    	put $SrcXXX
    	foreach SrcAdd $SrcXXX {
    		lappend SrcSim $SrcAdd
    	}
    }

    At the next stage, all IP cores in the project are searched. Moreover, the names of files that are used for modeling are written to the SrcSim variable. The lappend command in the loop adds other values ​​to the variable, forming an array, which in TCL is called a sheet. On this, the preparatory part of the script ends and the creation of the project begins.

    # Stage 5: Create project and add source files
    create_project -force $TopName $VivDir -part $PartDev
    set_property target_language VHDL [current_project]
    add_files -norecurse $SrcNGC
    add_files -norecurse $SrcXCI
    export_ip_user_files -of_objects [get_files $SrcXCI] -force -quiet
    add_files $SrcVHD
    add_files -fileset constrs_1 -norecurse $SrcXDC

    We create a project, define a top-level file, set the type of FPGA chip (in this example, Kintex-7 K325T), add the found source files.

    # Stage 6: Set properties and update compile order
    set_property top $TopName [current_fileset]
    for {set i 0} {$i < [llength $SrcSim]} {incr i} {
    	set_property used_in_synthesis false [get_files [lindex $SrcSim $i]]
    }
    set NgcGlb [findFiles $DirIps "*.ngc"]
    for {set i 0} {$i < [llength $NgcGlb]} {incr i} {
    	set_property IS_GLOBAL_INCLUDE 1 [get_files [lindex $NgcGlb $i]]
    }
    set_property IS_GLOBAL_INCLUDE 1 [get_files $SrcPCI]
    

    Set the options for the simulation files (exclude from the synthesis), set the GLOBAL_INCLUDE parameter for the cores used in the PCI-E node (this is a specific feature required for our projects).

    # Stage 7: Upgrade IP Cores (if needed)
    report_ip_status -name ip_status 
    set IpCores [get_ips]
    for {set i 0} {$i < [llength $IpCores]} {incr i} {
    	set IpSingle [lindex $IpCores $i]
    	set locked [get_property IS_LOCKED $IpSingle]
    	set upgrade [get_property UPGRADE_VERSIONS $IpSingle]
    	if {$upgrade != "" && $locked} {
    		upgrade_ip $IpSingle
    	}
    }
    report_ip_status -name ip_status

    At this stage, the IP-cores of the project are searched in the XCI format, the need to update the kernel version and the locked parameter are checked, which is affected by the change in the FPGA chip. After analysis of the kernels, an update occurs and a report on the successfully completed operation is issued.

    # Stage 8: Set properties for Synthesis and Implementation (Custom field)
    set_property strategy Flow_PerfOptimized_high [get_runs synth_1]
    set_property strategy Performance_ExtraTimingOpt [get_runs impl_1]
    launch_runs synth_1
    wait_on_run synth_1
    open_run synth_1 -name synth_1
    launch_runs impl_1 -to_step write_bitstream
    wait_on_run impl_1
    

    The final stage, at which the synthesis and tracing settings are set up, selects a strategy from the list of available ones. Then, synthesis, placement and tracing are started one by one until the FPGA firmware is fully wired.

    As you can see, using a script allows you to save the user from the chore of creating a project, adding new files, updating IP cores and many other things of the same kind. The script is fully automated and requires the installation of a single argument - the type of FPGA chip. It can be set as a variable in the file, or as an argument, which is executed simultaneously with the launch of the TCL script. The figure below shows a screenshot of the project workspace in the Vivado environment, which was launched using a script:


    Separately, you should pay attention to the findFiles procedure , with which you can search for all files in a directory. Function arguments: basedir - search directory, pattern - search mask.

    proc findFiles { basedir pattern } {
        set basedir [string trimright [file join [file normalize $basedir] { }]]
        set fileList {}
        foreach fileName [glob -nocomplain -type {f r} -path $basedir $pattern] {
            lappend fileList $fileName
        }	
        foreach dirName [glob -nocomplain -type {d  r} -path $basedir *] {
            set subDirList [findFiles $dirName $pattern]
            if { [llength $subDirList] > 0 } {
                foreach subDirFile $subDirList {
    		lappend fileList $subDirFile
                }
            }
        }
        return $fileList
    }

    The search is performed in several steps: determining the working directory as a file template, creating a list by the file name with the full path, and forming an array of list type list, if there are more than one found file. An example of the findFiles function is shown in the figure below. For clarification, a cycle has been written that displays all the files found. As you can see, the full path to each file is indicated.


    The script is run from the command line, or using the GUI of the Vivado application. In the first case, you need to run Vivado TCL Shell and write a simple command

    vivado –mode tcl –source %full_path/example.tcl

    Note: you can also start the graphical environment from the command line by changing the launch mode mode to gui .

    In the Vivado environment, scripts are run simple and simple: Menu -> Tools -> Run TCL Script ...


    This completes the introduction to the TCL language. The possibilities of project automation do not end there. In this simple example, I wanted to show how, using TCL scripts, you can automate FPGA design. TCL is very convenient, easy to understand, and most importantly, open to use. According to personal estimates, the implementation of scripts in the life of developers allows several times to reduce the time it takes to completely create a project from the initial to the final stage, and leave more time for “clean” development (writing code). The following are useful links for exploring TCL scripts on FPGAs.

    Literature:



    Thanks for attention!

    Also popular now: