FPGARelated.com
Blogs

In TCL FPGA Wizards Trust

GLENN KirilowOctober 16, 2024

TCL, Tool Command Language, is THE swiss army knife of choice for FPGA wizards. Learn it or be left behind, but what exactly is it and how does one go about mastering it?

As is the case for all great names, the answer is within. In this case TCL is a tool to take command of tools with a language. It is a programmatic way to interact with a tool, as opposed to the point and click adventures that all too often end in frustration expressed through cursing.

But why should we go back to the 70's? Wasn't the command line all the rage in the 70's? Because every tool, of which TCL is also one, has a place and in the context of FPGA's it solves a lot of pain points, particularly when it comes to building and testing projects in a reproducible way that is also orders of magnitude faster than a frustrating fury of highly error prone mouse clicks.

Let's then take a dive head first with a simple TCL script to create a project for a Xilinx xc7a35tcpg236-1 FPGA with a setlist of source and constraint files followed by synthesis and implementation steps:

We can do this piecewise, beginning with the project name and directory structure:

# Set the reference directory for relative paths
set origin_dir "."

set project_name "example_project"

# Set the directory path for the new project
set proj_dir [file join $origin_dir $project_name]

# Create project and set the part number
create_project $project_name $proj_dir -part xc7a35tcpg236-1

# Set the directory path for the new project
set proj_dir [get_property directory [current_project]]

Not too bad so far, right? Just a bit of syntactical sugar in the form of directives more or less.

From here we can define the design and simulation languages (VHDL/Verilog in this case):

set obj [current_project]
set_property -name "default_lib" -value "xil_defaultlib" -objects $obj
set_property -name "simulator_language" -value "VHDL" -objects $obj
set_property -name "target_language" -value "VHDL" -objects $obj

We have chosen the default library and VHDL for the simulator and design languages. We can then bake in our design and constraint files:

# Create 'sources_1' fileset (if not found)
if {[string equal [get_filesets -quiet sources_1] ""]} {
  create_fileset -srcset sources_1
}

# Add HDL source files
add_files -norecurse -fileset [get_filesets sources_1] [list \
 "[file normalize "$origin_dir/src/top.vhd"]"\
 "[file normalize "$origin_dir/src/ip_core.vhd"]"\
]

# Set 'sources_1' fileset properties
set obj [get_filesets sources_1]
# Assign the "top level" file to be top.vhd
set_property -name "top" -value "top" -objects $obj

# Create 'constrs_1' fileset
if {[string equal [get_filesets -quiet constrs_1] ""]} {
  create_fileset -constrset constrs_1
}

# Add constraints files
add_files -fileset constrs_1 [list \
 "[file normalize "$origin_dir/constrs/constraints.xdc"]"\
]

# Create 'sim_1' fileset
if {[string equal [get_filesets -quiet sim_1] ""]} {
  create_fileset -simset sim_1
}

# Add simulation files
add_files -fileset sim_1 -norecurse [list \
 "[file normalize "$origin_dir/sim/top_tb.vhd"]"\
 "[file normalize "$origin_dir/sim/ip_core_tb.vhd"]"\
]

# Set 'sim_1' fileset properties
set obj [get_filesets sim_1]
set_property -name "top" -value "testbench" -objects $ob

Finally we can save our project (it would be a shame to lose all that hard work) followed by the synthesis and implementation and bitstream generation steps as well as opening our implemented design:

# Save project
save_project_as $project_name $proj_dir -force

puts "Project created: $project_name"

# Run Synthesis
puts "Running synthesis..."
launch_runs synth_1 -jobs 8
wait_on_run synth_1

# Run Implementation
puts "Running implementation..."
launch_runs impl_1 -jobs 8
wait_on_run impl_1

# Generate Bitstream
puts "Generating bitstream..."
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1

puts "Synthesis, implementation, and bitstream generation completed."

# Open implemented design
open_run impl_1

Note the -jobs in the above, this denotes the number of cores used during the step. Feel free to change this as is appropriate for your machine.

The above is a very simple example, in a similar vein we can also cook up some testbench goodness:

# Run Simulation
puts "Running simulation..." 
launch_simulation
run all

# Open Wave Window
puts "Opening waveform viewer..."
open_wave_config
 
# Add signals to the wave window
add_wave {{/testbench/dut}} 
# Add more signals as needed, for example: 
# add_wave {{/testbench/dut/sub_module_inst}}  

# Rerun simulation to populate waveform 
run all

One can imagine the above applied to automated pipelines running on platforms such as Jenkins which are triggered by commits to a branch.


To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: