Perl For Hardware Design
Perl For Hardware Design
$a = $a + 1; # Add 1 to variable $a
$a += 1; # Add 1 to variable $a
$a++; # Add 1 to variable $a
Scalar variables can store strings of characters or words. Single quotes, 'like these', allow
spaces to appear inside strings. Double quotes, "like these", allow variables to be automatically
substituted or interpolated inside strings.
$a = 'Number of DFFs: '; # No interpolation with 'single quotes'
$b = "$a$c\n"; # Interpolation (variable substitution) with "double
quotes"
# \n is the newline character
print $b; # This makes "Number of DFFs: 17\n" appear on the
standard output
print $a, $c, "\n"; # As does this line because print takes
# a comma separated list of arguments to print
print "That's all\n"; # No commas means a list of one element
# or use qw''. Saves typing commas or quotes, gives the same result
# qw stands for Quoted Words
@components = qw'X_LUT4 X_AND2 X_BUFGMUX X_BUF_PP X_FF';
# or even put the data in columns, gives the same result again
@components = qw'
X_LUT4
X_AND2
X_BUFGMUX
X_BUF_PP
X_FF
'; # Easier to read this way
In a scalar context an array variable returns its size, the number of elements it contains. The
test expression of a while loop is tested for true or false, a scalar question, a scalar context. The
shift statement removes items from the bottom of the array, one at a time.
while( @components ) {
# ^^^^^^^^^^^^^ Array in scalar context
$next_component = shift( @components );
print "$next_component\n";
}
# Array variable @components is now empty
In this example @components begins with size 7, which is true. After 7 loops each of the 7
elements have been shifted or removed from the bottom of the array. In the while test expression
@components would have returned 7, 6, 5, 4, 3, 2, 1 and finally 0. Zero is false, end of while
loop.
Hash arrays differ from arrays because the elements are not ordered numerically but
associated with unique strings or keys. Hash arrays are associative arrays because they associate
values with keys. Maintaining many separate counters is a good hash array application as we will
see later. Hashes make a big contribution to Perl's text processing power. The % symbol is used
to denote a hash array.
# Initialising several hash keys
%components = qw'
X_LUT4 0
X_AND2 0
X_BUFGMUX 0
X_BUF_PP 0
X_FF 0
';
# ^^^^^^^^^ keys
# ^ values
$components{'X_LUT4'} = 1; # Set key X_LUT4 to the value 1
$components{'X_LUT4'}++; # Increment value associated with X_LUT4
print $components{'X_FF'}; # Print value associated with X_FF
@keys = keys %components; # Get a list of hash keys
print "@keys\n"; # Print them - order is indeterminate
%components = (); # Emptying the components hash
$netlist_filename = $ARGV[0];
$report_filename = $ARGV[1];
print " Processing $netlist_filename\n";
print " Writing report to $report_filename\n";
print " ARGV contains '@ARGV'\n";
Conditions
Perl has many conditional statements. The if statement asks a true/false question. If the answer
is true it executes a block of code.
if( $ff_count == 1 )
# ^^^^^^^^^^^^^^ Is this expression true or false?
{
# Do this action if it is true
print "There is 1 flip flop\n";
}
else
{
# Do this action if it is false
print "There are $ff_count flip flops\n";
}
Files
Text files are created with the open and print statements. Perl uses file handles to refer to each
open file. A file handle is just a string, conventionally written in uppercase letters without quotes.
Use the print statement to write text into a file.
open( FILE1, '>file1.txt' );
# ^ > means open in write mode
print FILE1 "The first line to file1.txt\n";
print FILE1 "The final line to file1.txt\n";
close( FILE1 ); # Don't have to explicitly
close a file
Here is a short program. It reads every line from a file named in the first command line
argument. The lines are written to a report file named in the second command line argument.
Numbers are printed at the beginning of each line.
$netlist_filename = $ARGV[0];
$report_filename = $ARGV[1];
open( FILE_IN, $netlist_filename );
open( FILE_OUT, ">$report_filename" );
while( $line = <FILE_IN> ) {
$line_count++;
print FILE_OUT "$line_count: $line";
}
# perl filter_netlist.pl chip_timesim.vhd report.txt
This Perl script does the same using standard input and standard output.
while( $line = <STDIN> ) {
$line_count++;
print "$line_count: $line";
}
# perl filter_netlist.pl < chip_timesim.vhd > report.txt
Pattern Matching
Perl's matching operator uses regular expressions to search a string for a match. Regular
expressions are patterns used to find a match for a string of characters contained in a longer
string. Regular expressions are built from character classes. The simplest character class is a
single character. The letter A matches a capital letter A once. Character classes only match one
character but the character can be any from a list of qualifying characters.
$string = "Novice to Expert in a 3 day Perl course.\n";
print $string;
if( $string =~ m/Expert/ ) {
# A successful match returns 1 so this statement is executed
print "This string contains the substring 'Expert'\n";
}
# m stands for match
# Forward slashes are used to /delimit/ regular expressions.
# =~ tells the m operator which string to search.
# The m is optional when // are used.
Regular Expressions
Individual letters are very limited character classes. Ranges of characters are matched using
character class shortcuts. Any alphanumeric character matches \w, including underscores.
Conveniently, most languages recognise identifiers containing letters, digits and underscores.
Quantifiers allow character classes to be repeated. The most common quantifiers are question
mark, ?, asterisk, *, and plus, +. Zero or one repetition is ?. Zero or more repetitions is *. One or
more repetitions is +.
use English;
$string = "Novice to Expert in a 3 day Perl course.\n";
if( $string =~ /\w+/ ) {
# \w+ matches one or more alphanumeric characters in a row
print "Matched: $MATCH\n"; # Matched: Novice
}
Readable English names for some Perl special variables are provided by the English Perl
module. $MATCH gets a copy of the substring successfully matched. Without using the English
Perl module $MATCH would have to be called $&.
use English;
$string = "Novice to Expert in a 3 day Perl course.\n";
if( $string =~ /Perl\s+\w+/ ) {
# ^^^^ matches Perl
# ^^^ matches one or more white space characters
# (including space, tab and newline)
# ^^^ matches one or more alphanumeric characters
print "Matched: $MATCH\n"; # Matched: Perl course
}
# \w? Zero or one letter, digit or underscore
# \w One letter, digit or underscore
# \w* Zero or more letters, digits or underscores
# \w+ One or more letters, digits or underscores
# \W One character but not a letter, digit or underscore
# \s White space character, space, tab or newline
# \S One character but not a space, tab or newline
Groups
Sub expressions can be grouped together and stored in back reference buffers. Round brackets
are used to (group) sub expressions. The back reference buffers have the names $1, $2, $3 etc.
The substring that matches the first bracketed group is copied into $1. The second into $2 etc.
There is a Xilinx Vital timing model in a file called chip_timesim.vhd. Here is one component
instantiation from it:
c4_n001449 : X_LUT4
generic map(
INIT => X"0001"
)
port map (
ADR0 => c4_count(4),
ADR1 => c4_count(18),
ADR2 => c4_count(3),
ADR3 => c4_count(5),
O => CHOICE121_pack_1
);
The component name, X_LUT4, could be matched using a regular expression containing a
group. This short script opens the file, finds every component instantiation reporting the
component name.
open( VHDL_FILE, 'chip_timesim.vhd' );
while( $line = <VHDL_FILE> ) {
if( $line =~ /\w+\s*:\s*(X_\w+)/ ) {
# ^^^ Instance label
# ^^^ Zero or more white space characters
# ^ :
# ^^^ Zero or more white space characters
# ^^^^^ Group containing a word beginning
with X_
# (copied
into $1)
print "Found instantiation of $1\n";
}
}
Netlist Filtering
The following script takes the filename of a Xilinx netlist from the command line. It finds and
counts every component instantiation. Finally, it prints a list of all the component names found
and the number of appearances.
# Pulling it all together
# Everything in this script is described above
$netlist_filename = $ARGV[0];
open( VHDL_FILE, $netlist_filename );
while( $line = <VHDL_FILE> ) {
if( $line =~ /\w+\s*:\s*(X_\w+)/ ) {
$component_hash{$1}++;
}
}
Perl is also useful for Coding Style conformance checking and enforcement.
Report Filtering
EDA tools are notorious for creating verbose report files. Many megabytes of text may
contain little interesting information. Perl is well matched to extracting key information quickly.
Netlist Patching
EDA tools within design flows never fit together perfectly. Often, really useful features are
unsupported. Netlists generated from one tool need modifying before the next tool will read
them. Perl is well matched to intelligent search and replace jobs.
# patch_sdf.pl
# Version 1.0
# 8 Jan 2003, SD
# www.doulos.com
#
# Copyright (c) 2003, Doulos Limited
# Training and consultancy for hardware engineers.
#
# Perl script for patching Standard Delay Format files.
unless( @ARGV == 3 ) {
die <<EOF;
undef $/;
foreach( <INSTANCES> =~ /\S+/g ) { $instances{$_} = 1 }
$/ = '(INSTANCE';
print OUT scalar( <IN> ); # First record only - force scalar context
while( <IN> ) {
# Match the instance name and check if it's in %instances
if( /([^\s)]+)/o and $instances{$1} ) {
print "Found INSTANCE $1\n";
delete( $instances{$1} ); # So instances not found can be reported
# Match this:
# IOPATH SET O (6500:6500:6500)(6500:6500:6500)
# -----$1------
# End up with this:
# IOPATH SET O (0:0:0)(0:0:0)
s/
(
IOPATH[^(]+ # Find "IOPATH" and everything before "("
) # Back reference in $1
How It Works
You may copy/modify and use this script with your own SDF files. Download the demo then
unzip it:
gzip -d doulos_demo_sdf.pl
This file, doulos_demo_sdf_pa.pl, is a self extracting Perl archive. Execute it to create a
directory called doulos_demo_sdf/ containing all necessary files to experiment with sdf_patch.pl.
The example works on both Unix and Windows.
perl doulos_demo_sdf_pa.pl
Checking Command Line Arguments
This script must be called with three arguments: the name of an SDF file to read, the name of a
text file containing a list of instance names to patch and the name of a file to write the patched
SDF file to. In a list context the @ARGV array returns all command line arguments. But, in a
scalar context, provided here by the == operator, an array returns its number of elements. Unless
there are exactly three we want our script to die and print an explanation.
unless( @ARGV == 3 ) {
die <<EOF;
EOF
}
Here file syntax like this <<EOF; . . . . EOF tells Perl to interpret the enclosed lines as an
interpolated string. That is, a string that would otherwise have been enclosed in double quotes
allowing variable interpolation (though none is used here). It's a neat syntax for clearly
presenting a chunk of text inside a Perl script. It also self-documents the script.
Open Three Files
There are three files, so we'll open them. Being engineers and knowing this script is a point
solution never extending beyond 30 lines of code we'll dispense with unnecessary formalities
like declaring and scoping variables. However, meaningful variable names will save time when
reusing and extending scripts later. Don't be afraid to type longer names. (If you can't touch type
- learn quickly!)
Capitalised identifiers are file handles by convention in Perl so the name only has to tell us
which file handle it is. IN, INSTANCES and OUT are succinct and self-documenting.
( $fn_in, $fn_instances, $fn_out ) = @ARGV;
open( IN, $fn_in ) or die "Cannot open $fn_in\n";
open( INSTANCES, $fn_instances ) or die "Cannot open $fn_instances\n";
open( OUT, ">$fn_out" ) or die "Cannot create $fn_out\n";
In Perl open or die is a great self-documenting construction. It pinpoints problems very
quickly if you add a meaningful comment. Consider adding $! the $OS_ERROR special variable
to the error string it will contain the file system error message.
To save time, complexity and typing we deliberately chose not close these file handles
explicitly. Perl tidies up for us once it runs off the end of the script closing all open file handles.
Of course there are occasions when closing file handles explicitly part way through a script is
necessary.
Reading The Instance List
The record input operator, <FILE_HANDLE>, reads everything upto and including the first
occurrence of the string stored in $/, the special variable $INPUT_RECORD_SEPARATOR.
Undefining $/ causes the record input operator to read the whole file in one go because undef
doesn't occur before the end of file.
$/ always has the initial value "\n". Changing $/ may cause trouble if another part of the Perl
script is going to assume it still has its initial value. Assigning "\n" back to $/ afterwards might
save frustration in longer scripts.
undef $/;
foreach( <INSTANCES> =~ /\S+/g ) { $instances{$_} = 1 }
The instances.txt file contains a list of names separated by white space. Keys for each instance
name are added to the %instances hash. True values, 1s, are associated with each key.
Perl interprets this last line by first reading a record from INSTANCES, the whole file into
one string. Next this string is bound to the matching operator, /..../, using the binding operator
=~. The regular expression \S+ greedily matches the first substring of non-white space
characters.
The whole matching operation is evaluated in a list context provided by foreach and with the
global modifier g so returns a list of every instance name. No loop variable is specified so Perl
assumes the default $_.
Inside the loop $_ is used to add a key/value pair to the %instances hash.
Preparing To Read, Patch And Write Each Record
Speed is very important when patching files that could be tens of MB long. For best
performance we need to select a pragmatic approach to parsing the SDF file. We could slurp the
whole file into a scalar variable but this could overstretch our computer's resources. We could
process the file one line at a time but each record spans several lines and would entail extra work
keeping track of where we are. Ideally we need to read one complete record at a time. There's a
really neat trick to do this with Perl.
Setting $/ to the literal string '(INSTANCE' makes this patching process very efficient. Each
use of the record input operator reads in exactly one record. It does split the records part way
down so each string read begins with the instance name of one record and continues to the space
in front of the instance name of the following record.
But who cares? Excepting the very first read, each read contains the right amount of
information to process. ie. An instance name and the IOPATH lines that may need patching.
$/ = '(INSTANCE';
print OUT scalar( <IN> ); # First record only - force scalar context
The first read gets everything up to the first instance name. We'll print this straight out. The
record input operator would read every record in one go inside print's list context so the scalar
command is used to force only one read.
Subsequent record input operations read a string ending in '(INSTANCE' like this one:
Data_0_INBLOCK_IBUF)
(DELAY
(ABSOLUTE
(IOPATH I O (3000:3000:3000)(3000:3000:3000))
)
)
)
(CELL (CELLTYPE "X_BUF")
(INSTANCE
For this record the instance name is Data_0_INBLOCK_IBUF which is one of those listed in
the demonstration so all six 3000s need changing to 0s.
Record Patching
Here's the SDF record patching bit:
while( <IN> ) {
# Match the instance name and check if it's in %instances
if( /([^\s)]+)/o and $instances{$1} ) {
print "Found INSTANCE $1\n";
delete( $instances{$1} ); # So instances not found can be reported
# Match this:
# IOPATH SET O (6500:6500:6500)(6500:6500:6500)
# -----$1------
# End up with this:
# IOPATH SET O (0:0:0)(0:0:0)
s/
(
IOPATH[^(]+ # Find "IOPATH" and everything before "("
) # Back reference in $1