Professional Documents
Culture Documents
TCL Language
TCL Language
Tutorial
What is Tcl?
Tcl is a programming language, but it's tailored to work best as a command language for
controlling the behaviour of other tools. In fact, the name Tcl stands for "Tool Control
Language" and Tcl was originally designed by programmers whose main interest was in
developing tools for physical layout of integrated circuits.
Whenever you use the command-line prompt in EDA tools such as Mentor's ModelSim,
Cadence's NC simulator or Synopsys's FPGA Express synthesis tool, you are probably issuing
commands to Tcl. Fully integrated with the EDA tool, Tcl deals with your commands, decides
which internal functions of the tool to execute, and passes your command-line arguments to
those functions.
Stand-alone Tcl
Although it's often integrated in other tools such as HDL simulators, Tcl is such a useful
language in its own right that it is also available as a stand-alone program known as Tclsh, the
Tcl shell. In a stand-alone Tcl environment, of course, you won't find commands like "run" and
"force" that might be available in a simulator. But the sixty or so commands that form the core of
Tcl are still available, and can be used to write all kinds of utility programs for file management,
text processing and similar tasks. File and text processing is very much easier to program in Tcl
than in traditional programming languages such as C.
What is Tcl/Tk?
Tk is the graphical user interface toolkit for Tcl. It provides an astonishingly straightforward
way to add GUI functionality to a Tcl program - windows, menus, dialog boxes, drawing, the
whole works. If you haven't tried it yet, you're missing a lot of fun!
Input files
The first thing we need to do is to tell the tool which source files will be used. We want to do
this in a maintainable way, and to this end all filenames are given at the top of the script.
We set some Tcl variables to contain the filenames we require:
A # denotes a comment; the comment is terminated by a newline and is ignored by the parser.
Care is required, as other Tcl behaviour means a hash character will not always be interpreted as
the beginning of a comment. Until you know this behaviour it's best to follow these rules about
comments:
Each comment appears on a separate line where # is the first character on that line
In a comment, don't include mismatched round or square brackets and don't use curly
brackets at all
In the setting of the variable "hdl_files" we used the list command to create a Tcl list from the
text strings that follow it. As a simpler alternative, we could have used braces to enclose a set of
strings:
set hdl_files {
../constants_spartan.vhd
../components.vhd
...Other files...
}
This approach has some drawbacks, especially when the list items have spaces or special
characters. It's generally best to construct the list using the list command. This also allows you to
use lappend and lindex commands to manipulate the list.
This code asks if the directory exists; if not, it is created. You may also find file exists useful.
Using $ in front of a variable name give you the content of that variable.
So, now we have a directory and we can do something there. Assuming no project exists in
this directory we can go ahead and make one. Xilinx provide the project command, which
includes sub-commands for the project settings and other related tasks. Let's try some:
project
project
project
project
project
new
set
set
set
set
$proj.ise
family Spartan3E
device xc3s500e
package fg320
speed -4
This is one of the strengths of Tcl; it is easy to add new commands by writing your own
procedures. Doing that is outside the scope of this discussion but is covered fully in Essential
Tcl/Tk.
Now we need to add some files to the project. Again, Xilinx provide a Tcl command for this:
the xfile command is used to add or remove files and to get information on any source files in the
current ISE project. We need to add some files, but we don't really want to type filenames again
(maintainability, remember?) and we already have a list of source files. The solution is to loop
through the list adding each file to the project:
foreach filename $hdl_files {
xfile add $filename
puts "Adding file $filename to the project."
}
The puts command is a simple way to write to STDOUT (which is usually the console/shell).
Note: don't forget to add the constraints file; NGDBuild does not automatically detect .ucf files
when run from Tcl. Use xfile add in the same way.
Running Processes
Before we run any processes, we can set process options using project set as above; see the
provided script for examples and the Xilinx Development System Reference Guide for
exhaustive detail.
Now we can run something. Just as with the ISE graphical interface, running any given
process will run any required prior processes. In our example, I can issue this command:
process run "Generate Programming File"
and the design will be synthesised and implemented first before the bitfile is created. Again, just
as in the graphical interface, source and intermediate file dependencies are checked and
processes are run only if required. For example, to force a rerun:
process run "Implement Design" -force rerun_all
Here we have to change our approach as Impact does not have a Tcl interface. However, this
does allow us to explore another way of controlling applications. Impact has the ability to run a
script file that consists of a series of commands. These are not Tcl commands, but we can use Tcl
to construct this file. We can then run Impact from with the Tcl script. Let's see how.
First we need to open a file that will become the Impact script. We do this with the open
command:
if {[catch {set f_id [open $impact_script_filename w]} msg]} {
puts "Can't create $impact_script_filename"
puts $msg
exit
}
What's going on here? The open command returns a handle to the file which we put in the
variable "f_id". If open fails it would stop the script; this is probably the best thing to do here,
but in general you may want to trap errors and continue with other things. That's what catch can
do; it will return a non-zero error code if the command it runs (in this case the open) fails, and it
places any error message from that command in the variable that here we have called "msg".
Hence, if the open fails $msg will contain the reason why.
How do we write text to the file? With the puts command. Earlier we used it to write text to
the console, but here we see an addition:
puts $f_id "addDevice -position 1 -file $bit_filename"
Well, it's not really an addition: the default destination of a puts command is the standard
output, so if we leave off the second argument then STOUT is where the output goes. The line
above uses a second argument, so the text is written to the file given by $f_id instead. After we
have written the required command to the Impact script we should close the file.
Now we have to start Impact. There are several ways of calling external programs from Tcl;
the easiest is to use the exec command, which runs the external program, waits for it to finish and
returns all its output. This is acceptable for quick programs but not for anything more
complicated, as you cannot direct any input to the program or control it while it is running. More
flexibility is provided by using open, but this time as a pipe from the program:
set impact_p [open "|impact -batch $impact_script_filename" r]
This line starts Impact with the script, and returns its output to the variable "impact_p". The
benefit of using the pipe is that we can now watch the STDOUT of the tool from within our Tcl
script:
while {![eof $impact_p]} { gets $impact_p line ; puts $line }
This code writes each line of Impact's output to the Tcl script's own standard output. The
command eof returns true when the external program finishes. This approach will not work if the
external program requires some interaction because that program will not tell you when it
requires some input. In our case, however, this is fine, because we want the Impact script to run
until completion. We explore ways of interacting with programs in Essential Tcl/Tk.
Assuming all this runs successfully you should have a working device. Congratulations!
Examples
ModelSim Compile Script
This is a general script for compiling, recompiling and simulating VHDL/Verilog code using
ModelSim. It is intended for rapid code writing and testing where small code modifications can
be checked very quickly using few keystrokes. Once ModelSim is running in GUI mode and the
script has been sourced then recompiling out-of-date files and rerunning a simulation requires
two keystrokes: r for recompile and press the Enter key. The key features are:
Here's the script. There's a zip file to download it with some example VHDL/Verilog at the end.
puts {
ModelSimSE general compile script version 1.1
Copyright (c) Doulos June 2004, SD
}
# Simply change the project settings in this section
# for each new project. There should be no need to
# modify the rest of the script.
set library_file_list {
}
set top_level
set wave_patterns {
design_library {counter.vhd}
test_library
{countertb.vhd
countercf.vhd}
test_library.Cfg_CounterTB
/*
}
set wave_radices {
}
hexadecimal {data q}
puts {
Script commands are:
r = Recompile changed and dependent files
rr = Recompile everything
q = Quit without confirmation
Download a copy of this script along with some example VHDL/Verilog code (up down counter
with test bench), compile_script.zip. Then modify the script settings and use it for your own
ModelSim projects.
Notes
The Tcl variable library_file_list stores the library name(s) and files that should be compiled into
each library. It is a two dimensional Tcl formatted list. The first element is the name of a library,
the second element is a list of files that should be compiled into that library. If there is more than
one library then a third and fourth element should be written for the second library name and its
list of files. And so on.
The order of libraries and files is significant, they should appear in order of dependency. The
script will compile every file the first time. Subsequent recompiles will run through the list of
files looking for a file that has been modified since the last compile time. The modified file and
every file after it in the list will be recompiled.
The Tcl variable top_level keeps the library and name of the top level for elaboration. In fact the
contents of this variable is used as the arguments to ModelSim's vsim command, so any valid
options may be specified for example:
set top_level {-t ps -sdftyp /uut=counter.sdf test_library.cfg_gate}
The Tcl variable wave_patterns can contain a list of signals or patterns that should be loaded into
the wave window. For most block level testbenches just /* is enough. If no waves are required or
the script is being used for command line simulations then this list can be left empty (nothing but
white space).
The Tcl variable wave_radices is a two dimensional Tcl formatted list like library_file_list. The
first element is a radix to use: hexadecimal, unsigned, binary etc. The second element is a list of
wave signals for applying this radix to. There can be zero, one or more pairs of elements in this
list.
To recompile out-of-date and dependent files type r and Enter. To force complete recompilation
type rr and Enter. To quit without ModelSim confirming that you want to quit type q and Enter.
To start ModelSim and source this script from the command line, type this:
vsim -do compile.tcl
Or, if ModelSim is already running with the correct working directory, type this in the ModelSim
main window:
source compile.tcl
Tcl/Tk Buttons
Here is a short example showing how to use Tcl/Tk to create toplevel windows containing
frequently used buttons. Mostly there are just three things we need to know about buttons:
Here, button creation is wrapped up in procedure add_button. It takes two arguments, what the
label should say and what Tcl commands should be executed when the button is clicked. Buttons
are arranged in a single column.
# A Tcl/Tk gadget to construct a window of buttons
# SD 20 Nov 2002
#######################################
####
####
#### Leave these lines unchanged
####
####
####
#######################################
destroy .buttons
toplevel .buttons
wm title .buttons "Buttons"
set count 0
proc add_button {title command} {
global count
button .buttons.$count -text $title -command $command
pack
.buttons.$count -side top -pady 1 -padx 1 -fill x
incr count
}
#######################################
####
####
####
Change these lines to
####
####
add your own buttons
####
####
####
#######################################
add_button "Resize Main" { wm geometry . 464x650+0+0
add_button "Hello"
{ puts "Hello there"
add_button "Goodbye"
{ puts "Cheerio"
add_button "Exit"
{ exit
}
}
}
}
The four add_button commands in this example produce a window like this
one. You can replace them with any commands appropriate for your EDA
tools. Any Tcl commands may be used for the second arguments.
#######################################
####
####
#### Change these lines to add
####
#### your own frames and buttons ####
####
####
#######################################
add_frame "Control"
add_button "Window Size"
{wm geometry . 464x692+0+0}
add_button "Create Library" {vlib mylib; vmap work mylib}
add_button "Compile"
{ vcom counter.vhd
vcom countertb.vhd
vcom countercf.vhd }
add_button "Load Simulation" {vsim mylib.cfg_countertb}
add_button "Wave Window"
{source wave.do}
add_button "Quit"
{quit -force}
add_frame
add_button
add_button
add_button
add_button
add_button
"Simulate"
"Run 10"
"Run 100"
"Run 1000"
"Run all"
"Restart"
{run 10}
{run 100}
{run 1000}
{run -all}
{restart -force}
add_frame
add_button
add_button
add_button
add_button
add_button
"Zoom"
"Full"
"2x"
"4x"
"1/2x"
"1/4x"
{.wave.tree zoomfull}
{WaveZoom .wave out 2.0}
{WaveZoom .wave out 4.0}
{WaveZoom .wave in 2.0}
{WaveZoom .wave in 2.0}
How To Use
These examples are useful for EDA tools that support Tcl and the Tk package. Copy and paste
one of the above examples into a file, maybe called buttons.tcl. Edit your copy changing
add_button commands to suit your project. From inside the Tcl command window of the EDA
tool type:
source buttons.tcl
Commands issued by button clicks are directly interpreted by the EDA tool.
"Control"
"Create Library"
"Compile"
add_button
add_button
add_button
add_frame
add_button
add_button
add_button
add_button
add_button
"Simulate"
"Run 10"
"Run 100"
"Run 1000"
"Run all"
"Restart"
{run 10}
{run 100}
{run 1000}
{run -all}
{restart -force}
}
proc add_frame title {
global buttons
set buttons(frame) .frame$buttons(widget_count)
frame $buttons(frame) -border 2 -relief groove
label $buttons(frame).label -text $title -font $buttons(font)
pack $buttons(frame)
-side left -padx 2 -pady 2 -anchor n -fill both
-expand 1
pack $buttons(frame).label -side top -padx 2 -pady 2
incr buttons(widget_count)
}
proc add_button {title command} {
global buttons
button $buttons(frame).b$buttons(widget_count) -text $title -font
$buttons(font) \
-command
"puts \"$command\""
pack
$buttons(frame).b$buttons(widget_count) -side top -pady 2 -padx 2
-fill x
incr buttons(widget_count)
}
proc respond_to_buttons {} {
global buttons
if [eof $buttons(pipe_id)] {
catch {close $buttons(pipe_id)}
} elseif { [gets $buttons(pipe_id) line] >= 0 } {
puts $line
eval $line
}
}
if [string compare $argv buttons_gui] {
global buttons
if [catch {set buttons(pipe_id) [open "|wish buttons.tcl buttons_gui" r+]}]
{
puts "Couldn't start wish for the buttons GUI\n"
} else {
fconfigure $buttons(pipe_id) -blocking 0 -buffering line
fileevent $buttons(pipe_id) readable respond_to_buttons
}
} else {
wm title . Buttons
set buttons(font) [font create -family {Arial Helvetica "Courier New"}
-size 12]
set buttons(frame) {}
set buttons(widget_count) 1
include_these_buttons
}
To make this example work the standalone Tcl/Tk interpreter must be installed. It's command
name is usually wish. The line containing this fragment starts the wish interpreter through a
pipeline:
[open "|wish buttons.tcl buttons_gui" r+]
If your system uses another name such as wish84 then you should modify this line.
This script is run twice. First from the EDA tool by typing source buttons.tcl. The script then
opens a command pipeline calling the Tcl/Tk interpreter and supplying its own name as the
script to execute but this time with buttons_gui command line argument. This tells the Tcl script
it's being run as the standalone application to display the buttons specified in the
include_these_buttons procedure.
Communication takes place through the pipeline. When a button is clicked it sends the Tcl
commands as plain text down the command pipeline to the EDA tool. For example:
puts {vlib mylib; vmap work mylib}
A file event is set up in the EDA tool to respond to new information from the pipeline:
fileevent $buttons(pipe_id) readable respond_to_buttons
This executes the procedure respond_to_buttons everytime the buttons application sends
commands. It reads from the pipeline and uses the eval command to pass them on to the EDA
tool's Tcl interpreter.
This example assumes the EDA tool uses an event loop. If it doesn't you can easily modify the
script to start one with the vwait command. However, the event loop may have to be terminated
to pass control back to the EDA tool's command window.
Tcl scripting in DC
Design Compiler (DC) uses an internal database to store your design throughout the synthesis
process. As you execute various DC commands to optimise and modify the synthesised design,
the database is updated. Finally, when you are satisfied with the synthesised results, you can
write the database out in some format such as EDIF, or as a Verilog or VHDL netlist.
When you use Tcl in DC you need to understand how DC exposes its database. Tcl can handle
only string data, and of course the DC database is in a proprietary binary format, so your Tcl
scripts must access the database through the commands and functions provided as part of DC. In
particular it's very important to understand how DC allows you to group database objects into
collections which can then be supplied as the arguments to various commands.
Doulos offers Tcl training including an optional module on Tcl in Synopsys DC, to give
synthesis users a get-you-started overview of how to approach DC scripting.