File Coverage

blib/lib/Module/Build/Xilinx.pm
Criterion Covered Total %
statement 74 451 16.4
branch 15 206 7.2
condition 4 46 8.7
subroutine 13 34 38.2
pod 1 12 8.3
total 107 749 14.2


line stmt bran cond sub pod time code
1             package Module::Build::Xilinx;
2 2     2   26760 use base 'Module::Build';
  2         6  
  2         4979  
3              
4 2     2   300277 use 5.0008;
  2         7  
  2         78  
5 2     2   12 use strict;
  2         9  
  2         65  
6 2     2   11 use warnings;
  2         5  
  2         53  
7 2     2   11 use Carp;
  2         4  
  2         237  
8 2     2   12 use Cwd;
  2         6  
  2         141  
9 2     2   12 use Config;
  2         4  
  2         82  
10 2     2   10 use Data::Dumper;
  2         3  
  2         96  
11 2     2   13 use File::Spec;
  2         4  
  2         69  
12 2     2   10 use File::Basename qw/fileparse/;
  2         4  
  2         183  
13 2     2   1964 use File::HomeDir;
  2         14223  
  2         15923  
14              
15             our $VERSION = '0.13';
16             $VERSION = eval $VERSION;
17              
18             # Xilinx install path
19             __PACKAGE__->add_property('xilinx', undef);
20             __PACKAGE__->add_property('xilinx_settings32', undef);
21             __PACKAGE__->add_property('xilinx_settings64', undef);
22             # project name property
23             __PACKAGE__->add_property('proj_name', undef);
24             # project extension property
25             __PACKAGE__->add_property('proj_ext', '.xise');
26             # project parameters related to the device
27             __PACKAGE__->add_property('proj_params',
28             default => sub { {} },
29             # this check thing doesnt work
30             check => sub {
31             if (ref $_ eq 'HASH') {
32             return 1 if (defined $_->{family} and defined $_->{device});
33             shift->property_error(
34             qq{Property "proj_params" needs "family" and "device" defined});
35             } else {
36             shift->property_error(
37             qq{Property "proj_params" should be a hash reference.});
38             }
39             return 0;
40             },
41             );
42             __PACKAGE__->add_property('testbench', {});
43             # source files
44             __PACKAGE__->add_property('source_files', []);
45             # testbench files
46             __PACKAGE__->add_property('testbench_files', []);
47             # testbench source files
48             __PACKAGE__->add_property('testbenchsrc_files', []);
49             # tcl file
50             __PACKAGE__->add_property('tcl_script', 'program.tcl');
51              
52             sub new {
53 1     1 1 99 my $class = shift;
54             # build the M::B object
55             # hide the warnings about module_name
56 1         21 my $self = $class->SUPER::new(module_name => $class, @_);
57 1         73935 my $os = $self->os_type;
58 1 50       67 croak "No support for OS" unless $os =~ /Windows|Linux|Unix/i;
59 1 50 33     48 croak "No support for OS" if $os eq 'Unix' and $^O !~ /linux/i;
60 1         35 $self->libdoc_dirs([]);
61 1         41 $self->bindoc_dirs([]);
62             # sanitize proj_params
63 1         27 my $pp = $self->proj_params;
64 1 50       20 if (defined $pp->{language}) {
65 0 0       0 $pp->{language} = 'VHDL' if $pp->{language} =~ /vhdl/i;
66 0 0       0 $pp->{language} = 'Verilog' if $pp->{language} =~ /verilog/i;
67 0 0       0 $pp->{language} = 'N/A' unless $pp->{language} =~ /VHDL|Verilog/i;
68             }
69 1         6 $self->proj_params($pp);
70             # project name can just be dist_name
71 1         7 $self->proj_name($self->dist_name);
72             # add the Verilog/VHDL files as build files
73 1         55 $self->add_build_element('hdl');
74             # add the Verilog/VHDL testbench files as well
75 1         49 $self->add_build_element('tb');
76             # add the ucf files as build files
77 1         24 $self->add_build_element('ucf');
78 1 50       33 if (defined $self->tcl_script) {
79 1         41 my $tcl = File::Spec->catfile($self->blib, $self->tcl_script);
80 1         50 $self->tcl_script($tcl);
81 1         36 $self->add_to_cleanup($tcl);
82             }
83             # find the Xilinx install path
84 1         297 my $xil_path = $self->_find_xilinx($ENV{XILINX});
85 1 50       9 $self->xilinx($xil_path) if defined $xil_path;
86 1 50       4 print "Found Xilinx installed at $xil_path\n" if defined $xil_path;
87              
88 1   50     14 my $oref = $self->get_options() || {};
89 1 50       41 $oref->{device} = { type => '=s' } unless exists $oref->{device};
90 1 50       12 $oref->{view} = { type => '=s@' } unless exists $oref->{view};
91 1         11 return $self;
92             }
93              
94             sub ACTION_build {
95 0     0 0 0 my $self = shift;
96             # build invokes the process_*_files() functions
97 0 0       0 $self->SUPER::ACTION_build(@_) if $self->SUPER::can_action('build');
98 0         0 my $tcl = $self->tcl_script;
99 0         0 $self->log_info("Generating the $tcl script\n");
100 0 0       0 if ($self->verbose) {
101 0         0 local $Data::Dumper::Terse = 1;
102 0         0 my ($a, $b) = Data::Dumper->Dumper($self->source_files);
103 0         0 $self->log_verbose("source files: $b");
104             }
105             # add the tcl code
106 0 0       0 open my $fh, '>', $tcl or croak "Unable to open $tcl for writing: $!";
107 0         0 print $fh $self->_dump_tcl_code();
108 0         0 close $fh;
109             # we do this here since otherwise the tests will fail
110 0         0 my $xil_path = $self->xilinx;
111 0 0       0 croak $self->_cant_find_xilinx() unless defined $xil_path;
112 0         0 1;
113             }
114              
115             sub process_ucf_files {
116 0     0 0 0 my $self = shift;
117 0         0 my $regex = qr/\.(?:ucf)$/;
118 0         0 return $self->_process_src_files($regex);
119             }
120              
121             sub _process_src_files($) {
122 0     0   0 my ($self, $regex) = @_;
123 0         0 my @filearray = ();
124 0         0 foreach my $dir (qw/lib src/) {
125 0 0       0 next unless -d $dir;
126 0         0 eval {
127 0         0 my $files = $self->rscan_dir($dir, $regex);
128 0 0 0     0 push @filearray, @$files if ref $files eq 'ARRAY' and scalar @$files;
129             };
130 0 0       0 carp "hdl: $@" if $@;
131             }
132             # make unique
133 0         0 push @filearray, @{$self->source_files};
  0         0  
134 0         0 my %fh = map { $_ => 1 } @filearray;
  0         0  
135 0         0 $self->source_files([keys %fh]);
136             }
137              
138             sub process_hdl_files {
139 0     0 0 0 my $self = shift;
140 0         0 my $regex = qr/\.(?:vhdl|vhd|v)$/;
141 0         0 return $self->_process_src_files($regex);
142             }
143              
144             sub process_tb_files {
145 0     0 0 0 my $self = shift;
146             ## patterns taken from $Xilinx/data/projnav/xil_tb_patterns.txt
147 0         0 my $regex_tb =
148             qr/(?:_tb|_tf|_testbench|_tb_[0-9]+|databench\w*|testbench\w*)\.(?:vhdl|vhd|v)$/;
149 0         0 my $regex = qr/\.(?:vhdl|vhd|v)$/;
150 0         0 return $self->_process_tb_files($regex_tb, $regex);
151             }
152              
153             sub _process_tb_files($$) {
154 0     0   0 my ($self, $regex_tb, $regex) = @_;
155 0         0 my @filearray = ();
156             # find all the _tb files in lib/src
157 0         0 foreach my $dir (qw/lib src t tb/) {
158 0 0       0 next unless -d $dir;
159 0         0 eval {
160 0         0 my $files = $self->rscan_dir($dir, $regex_tb);
161 0 0 0     0 push @filearray, @$files if ref $files eq 'ARRAY' and scalar @$files;
162             };
163 0 0       0 carp "tb: $@" if $@;
164             }
165             # make unique
166 0         0 push @filearray, @{$self->testbench_files};
  0         0  
167 0         0 my %fh = map { $_ => 1 } @filearray;
  0         0  
168 0         0 $self->testbench_files([keys %fh]);
169             # find all the vhd/ver files in t/tb, since multiple testbench files
170             # and dependent entity files may co-exist in one as a supplement.
171             # this is similar to the t/ directory having a .pm file
172 0         0 my $tbsrc = $self->testbenchsrc_files;
173 0         0 foreach my $dir (qw/t tb/) {
174 0 0       0 next unless -d $dir;
175 0         0 eval {
176 0         0 my $files = $self->rscan_dir($dir, $regex);
177 0         0 foreach (@$files) {
178 0 0       0 next if $fh{$_};
179 0         0 push @$tbsrc, $_;
180             }
181             };
182 0 0       0 carp "tb: $@" if $@;
183             }
184 0         0 my %fh2 = map { $_ => 1 } @$tbsrc;
  0         0  
185 0         0 $self->testbenchsrc_files([keys %fh2]);
186             # find the correct testbench top-levels
187 0         0 my $tb = $self->testbench;
188 0         0 foreach my $key (keys %fh) {
189             # we only care about the files that Xilinx assumes can be a testbench
190 0 0       0 next unless $key =~ /$regex_tb/;
191 0 0       0 my $hh = exists $tb->{$key} ? $tb->{$key} : {};
192 0 0       0 croak "Property testbench{$key} has to be a hash reference" unless ref $hh eq 'HASH';
193 0         0 my ($file, $dirs, $ext) = fileparse($key, $regex);
194 0 0       0 $hh->{toplevel} = 'testbench' unless defined $hh->{toplevel};
195 0 0       0 $hh->{srclib} = 'work' unless defined $hh->{srclib};
196 0 0       0 $hh->{wdb} = $file . '.wdb' unless defined $hh->{wdb};
197 0 0       0 $hh->{exe} = $file . '.exe' unless defined $hh->{exe};
198 0 0       0 $hh->{prj} = $file . '.prj' unless defined $hh->{prj};
199 0 0       0 $hh->{cmd} = $file . '.cmd' unless defined $hh->{cmd};
200 0         0 $tb->{$key} = $hh;
201             }
202 0         0 $self->testbench($tb);
203             }
204              
205             sub _cant_find_xilinx {
206 0     0   0 return << 'CANTFIND';
207             Cannot find Xilinx ISE installation. Set the XILINX environment variable to point to it such as
208             /opt/Xilinx/13.2/ISE or set the 'xilinx' property in the Build.PL script of the
209             Module::Build::Xilinx. You will need to re-run Build.PL after this.
210             CANTFIND
211             }
212              
213             sub _find_xilinx {
214 1     1   47 my $self = shift;
215 1         4 my $env_xil = shift;
216 1         6 my $xil_path = $self->xilinx;
217 1         26 my $homedir = File::HomeDir->my_home();
218 1         77 my @xildirs = ();
219 1         3 my @final = ();
220 1 50 33     12 push @final, $env_xil if (defined $env_xil and -d $env_xil);
221 1 50 33     6 push @final, $xil_path if (defined $xil_path and -d $xil_path);
222 1 50       11 if ($self->is_windowsish()) {
223             # in Windows the Xilinx is installed in C:/Xilinx by default
224 0         0 my @drives = ( $ENV{SystemDrive}, $ENV{HOMEDRIVE} );
225 0         0 @drives = grep { defined $_ } @drives;
  0         0  
226 0         0 foreach (@drives) {
227 0         0 my $d = "$_\\Xilinx";
228 0 0       0 push @xildirs, $d if -d $d;
229             }
230 0   0     0 my $pf = $ENV{ProgramFiles} || $ENV{PROGRAMFILES};
231 0   0     0 my $pfx86 = $ENV{'ProgramFiles(x86)'} || $ENV{'PROGRAMFILES(X86)'};
232 0         0 foreach (($homedir, $pf, $pfx86)) {
233 0 0       0 next unless defined $_;
234 0 0       0 next unless -d $_;
235 0 0       0 push @xildirs, "$_\\Xilinx" if -d "$_\\Xilinx";
236             }
237             } else {
238             # in Unix/Linux Xilinx is installed in /opt by default
239 1         23 foreach (($homedir, '/opt', '/usr', '/usr/local')) {
240 4 50       11 next unless defined $_;
241 4 50       70 next unless -d $_;
242 4 50       64 push @xildirs, "$_/Xilinx" if -d "$_/Xilinx";
243             }
244             }
245 1 50       5 unless (scalar @xildirs) {
246 1         520 carp "Cannot find any directories with Xilinx software installed";
247 1         208 return;
248             }
249 0 0         if ($self->verbose) {
250 0           local $Data::Dumper::Terse = 1;
251 0           print "Found directories with Xilinx software installed: ", Dumper(\@xildirs), "\n";
252             }
253 0           foreach my $xdir (@xildirs) {
254 0 0         opendir my $fd, $xdir or carp "Cannot open directory $xdir";
255 0 0         next unless $fd;
256 0           my @filenames = readdir $fd;
257 0           closedir $fd;
258 0           my @possible = grep { /\d+\.\d+/ } @filenames;
  0            
259 0 0         next unless scalar @possible;
260 0           @possible = map(File::Spec->catfile($xdir, $_), @possible);
261 0           push @final, @possible;
262             }
263 0 0         if ($self->verbose) {
264 0           print "Found possible directories with Xilinx software installed: ", Dumper(\@final);
265             }
266 0 0         unless (scalar @final) {
267 0           carp "Cannot find any directories with Xilinx software installed";
268 0           return;
269             }
270 0           my $result;
271 0           foreach (@final) {
272 0 0         my $ext = $self->is_windowsish() ? 'bat' : 'sh';
273 0           $result = File::Spec->catfile($_, 'ISE_DS');
274 0           my $f32 = File::Spec->catfile($result, "settings32.$ext");
275 0           my $f64 = File::Spec->catfile($result, "settings64.$ext");
276 0 0 0       if (-e $f64 or -e $f32) {
277 0           $self->xilinx_settings64($f64);
278 0           $self->xilinx_settings32($f32);
279 0 0         print "Found $f64 and $f32 in $result\n" if $self->verbose;
280 0           last;
281             }
282             }
283 0           return $result;
284             }
285              
286             sub _exec_tcl_script($) {
287 0     0     my ($self, $opt) = @_;
288             # find xtclsh and run the tcl script
289             # for that you need to find the Xilinx install path or use a user supplied
290             # one and run it here
291 0           my $tcl = $self->tcl_script;
292 0 0         croak "$tcl is missing. Please run ./Build first" unless -e $tcl;
293 0           my $cmd1 = $self->xilinx_settings32;
294 0 0         $cmd1 = $self->xilinx_settings64 if $Config{archname} =~ /x86_64|x64/;
295 0           my $cmd2 = "xtclsh $tcl $opt";
296 0 0         print "Loading settings from $cmd1 and running $cmd2\n" if $self->verbose;
297 0 0         if ($self->is_windowsish()) {
298 0           my $bat = File::Spec->catfile($self->blib, 'runtcl.bat');
299 0 0         open my $fh, '>', $bat or croak "Unable to open $bat for writing: $!";
300 0           print $fh "call $cmd1\r\n";
301 0           print $fh "$cmd2\r\n";
302 0           print $fh "echo 'done running $cmd2'\r\n";
303 0           print $fh "exit\r\n";
304 0           close $fh;
305 0 0         system($bat) == 0 or croak "Failure while executing '$bat': $!";
306             } else {
307 0 0         system("source $cmd1 && $cmd2") == 0 or croak "Failure while executing '$cmd1 && $cmd2': $!";
308             }
309             }
310              
311             sub _exec_isimgui($) {
312 0     0     my ($self, $wdb) = @_;
313             # NO CHDIR here
314             # find xtclsh and run the tcl script
315             # for that you need to find the Xilinx install path or use a user supplied
316             # one and run it here
317 0           my $cmd1 = $self->xilinx_settings32;
318 0 0         $cmd1 = $self->xilinx_settings64 if $Config{archname} =~ /x86_64|x64/;
319 0           my $cmd2 = "isimgui -view $wdb";
320 0 0         print "Loading settings from $cmd1 and running $cmd2\n" if $self->verbose;
321 0 0         if ($self->is_windowsish()) {
322 0           my $bat = File::Spec->catfile($self->blib, 'runview.bat');
323 0 0         open my $fh, '>', $bat or croak "Unable to open $bat for writing: $!";
324 0           print $fh "call $cmd1\r\n";
325 0           print $fh "$cmd2\r\n";
326 0           print $fh "echo 'done running $cmd2'\r\n";
327 0           print $fh "exit\r\n";
328 0           close $fh;
329 0 0         system($bat) == 0 or croak "Failure while executing '$bat': $!";
330             } else {
331 0 0         system("source $cmd1 && $cmd2") == 0 or croak "Failure while executing '$cmd1 && $cmd2': $!";
332             }
333             }
334              
335             sub _exec_fuse($$$) {
336 0     0     my ($self, $prj, $exe, $topname) = @_;
337 0           my $cwd = Cwd::cwd();
338 0           chdir $self->blib;
339              
340 0           my $cmd1 = $self->xilinx_settings32;
341 0 0         $cmd1 = $self->xilinx_settings64 if $Config{archname} =~ /x86_64|x64/;
342 0           my $cmd2 = "fuse -incremental $topname -prj $prj -o $exe";
343 0 0         print "Loading settings from $cmd1 and running $cmd2\n" if $self->verbose;
344 0 0         if ($self->is_windowsish()) {
345 0           my $bat = 'runfuse.bat';
346 0 0         open my $fh, '>', $bat or croak "Unable to open $bat for writing: $!";
347 0           print $fh "call $cmd1\r\n";
348 0           print $fh "$cmd2\r\n";
349 0           print $fh "echo 'done running $cmd2'\r\n";
350 0           print $fh "exit\r\n";
351 0           close $fh;
352 0 0         system($bat) == 0 or croak "Failure while executing '$bat': $!";
353             } else {
354 0 0         system("source $cmd1 && $cmd2") == 0 or croak "Failure while executing '$cmd1 && $cmd2': $!";
355             }
356 0           chdir $cwd;
357             }
358              
359             sub _exec_simulation($$$$) {
360 0     0     my ($self, $exe, $cmd, $wdb, $log) = @_;
361 0           my $cwd = Cwd::cwd();
362 0           chdir $self->blib;
363              
364 0           my $cmd1 = $self->xilinx_settings32;
365 0 0         $cmd1 = $self->xilinx_settings64 if $Config{archname} =~ /x86_64|x64/;
366 0           my $cmd2 = "$exe -tclbatch $cmd -wdb $wdb -log $log";
367 0 0         print "Loading settings from $cmd1 and running $cmd2\n" if $self->verbose;
368 0 0         if ($self->is_windowsish()) {
369 0           my $bat = 'runsim.bat';
370 0 0         open my $fh, '>', $bat or croak "Unable to open $bat for writing: $!";
371 0           print $fh "call $cmd1\r\n";
372 0           print $fh ".\\$cmd2\r\n";
373 0           print $fh "echo 'done running $cmd2'\r\n";
374 0           print $fh "exit\r\n";
375 0           close $fh;
376 0 0         system($bat) == 0 or croak "Failure while executing '$bat': $!";
377             } else {
378 0 0         system("source $cmd1 && ./$cmd2") == 0 or croak "Failure while executing '$cmd1 && $cmd2': $!";
379             }
380 0           chdir $cwd;
381             }
382              
383             sub _exec_impact($) {
384 0     0     my ($self, $device) = @_;
385 0           my $cwd = Cwd::cwd();
386 0           chdir $self->blib;
387              
388 0           my $cmd1 = $self->xilinx_settings32;
389 0 0         $cmd1 = $self->xilinx_settings64 if $Config{archname} =~ /x86_64|x64/;
390 0           my $pcmd = File::Spec->catfile(File::Spec->curdir(), 'program_device.cmd');
391 0           my $projipf = File::Spec->catfile(File::Spec->curdir(), $self->proj_name . ".ipf");
392              
393 0           my $cmd2 = "impact -batch $pcmd";
394 0 0         print "Loading settings from $cmd1 and running $cmd2\n" if $self->verbose;
395 0 0         open my $fh, '>', $pcmd or croak "Unable to write to $pcmd: $!";
396 0           my $data = << 'PROGDATA';
397             setLog -file program_device.log
398             setPreference -pref UserLevel:Novice
399             setPreference -pref ConfigOnFailure:Stop
400             setMode -bscan
401             setCable -port auto
402             identify
403             PROGDATA
404 0           print $fh $data;
405 0           my $i = 0;
406 0           my @bitfiles = <*.bit>;
407             ## assign the bit files to a tag $i
408 0           foreach (@bitfiles) {
409 0           $i++;
410 0           my $line = << "LINEBIT";
411             assignFile -p $i -file \"$_\"
412             LINEBIT
413 0           print $fh $line;
414             }
415             ## program all the tags
416 0           $i = 0;
417 0           foreach (@bitfiles) {
418 0           $i++;
419 0           my $line = << "LINEBIT";
420             program -p $i
421             LINEBIT
422 0           print $fh $line;
423             }
424 0           $data = << "PROGDATA";
425             checkIntegrity
426             saveprojectfile -file \"$projipf\"
427             quit
428             PROGDATA
429 0           print $fh $data;
430 0           close $fh;
431 0 0         if ($self->is_windowsish()) {
432 0           my $bat = 'runprog.bat';
433 0 0         open my $fh, '>', $bat or croak "Unable to open $bat for writing: $!";
434 0           print $fh "call $cmd1\r\n";
435 0           print $fh "$cmd2\r\n";
436 0           print $fh "echo 'done running $cmd2'\r\n";
437 0           print $fh "exit\r\n";
438 0           close $fh;
439 0 0         system($bat) == 0 or croak "Failure while executing '$bat': $!";
440             } else {
441 0 0         system("source $cmd1 && $cmd2") == 0 or croak "Failure while executing '$cmd1 && $cmd2': $!";
442             }
443 0           chdir $cwd;
444             }
445              
446             sub ACTION_psetup {
447 0     0 0   my $self = shift;
448 0           $self->ACTION_build(@_);
449 0           return $self->_exec_tcl_script('-setup');
450             }
451              
452             sub ACTION_pclean {
453 0     0 0   my $self = shift;
454 0           $self->ACTION_build(@_);
455 0           return $self->_exec_tcl_script('-clean');
456             }
457              
458             sub ACTION_pbuild {
459 0     0 0   my $self = shift;
460 0           $self->ACTION_psetup(@_);
461 0           return $self->_exec_tcl_script('-build');
462             }
463              
464             sub ACTION_test {
465 0     0 0   return shift->ACTION_simulate(@_);
466             }
467              
468             sub ACTION_simulate {
469 0     0 0   my $self = shift;
470             # manage multiple views. how does one update runtime_params ? hence we just
471             # re-run the Build as needed.
472 0           $self->ACTION_build(@_);
473 0           my $tb_data = $self->testbench;
474 0   0       my $simfiles = $self->SUPER::args('sim_files') || [keys %$tb_data];
475 0 0         $simfiles = [$simfiles] unless ref $simfiles eq 'ARRAY';
476 0 0         if (scalar @$simfiles) {
477 0 0         if ($self->verbose) {
478 0           local $Data::Dumper::Terse = 1;
479 0           print "Running tests for the following: ", Dumper($simfiles);
480             }
481 0           my $blib = $self->blib;
482 0           my $flag = File::Spec->catfile($blib, '.done_build');
483 0 0         croak "You need to run 'Build pbuild' before running simulate" unless -e $flag;
484 0           foreach my $vf (@$simfiles) {
485 0 0         $vf =~ s:\\:/:g if $self->is_windowsish();# convert windows paths out
486 0           $vf =~ s:^\./::g; # remove ./ from the beginning
487 0 0         unless (exists $tb_data->{$vf}) {
488 0           carp "$vf is not a valid testbench file";
489 0           next;
490             }
491 0           my $prj = $tb_data->{$vf}->{prj};
492 0           my $exe = $tb_data->{$vf}->{exe};
493 0           my $cmd = $tb_data->{$vf}->{cmd};
494 0           my $wdb = $tb_data->{$vf}->{wdb};
495 0           my $topname = $tb_data->{$vf}->{srclib} . '.' . $tb_data->{$vf}->{toplevel};
496 0           my $log = $exe;
497 0           $log =~ s/\.exe$/\.log/g;
498 0           my $cmdfile = File::Spec->catfile($blib, $cmd);
499 0 0         open my $fh, '>', $cmdfile or croak "Unable to open $cmdfile for writing: $!";
500 0           my $tclcode = << 'CMDEOF';
501             onerror {resume}
502             wave add /
503             run all
504             quit -f
505             CMDEOF
506 0           print $fh $tclcode;
507 0           close $fh;
508 0 0         print "Done creating $cmdfile\n" if $self->verbose;
509             ## will do a chdir into $blib
510 0           $self->_exec_fuse($prj, $exe, $topname);
511             ## will do a chdir into $blib
512 0           $self->_exec_simulation($exe, $cmd, $wdb, $log);
513             }
514             # create .done_simulate
515 0           my $ds = File::Spec->catfile($blib, '.done_simulate');
516 0 0         open my $dsf, '>', $ds or carp "Unable to create $ds: $!";
517 0           print $dsf "1\n";
518 0           close $dsf;
519             } else {
520 0           print "No tests were run since no testbenches were found.\n";
521             }
522             }
523              
524             sub ACTION_view {
525 0     0 0   my $self = shift;
526             # manage multiple views. how does one update runtime_params ? hence we just
527             # re-run the Build as needed.
528 0           $self->ACTION_build(@_);
529 0           my $tb_data = $self->testbench;
530 0   0       my $simfiles = $self->SUPER::args('sim_files') || [keys %$tb_data];
531 0 0         $simfiles = [$simfiles] unless ref $simfiles eq 'ARRAY';
532 0 0         if (scalar @$simfiles) {
533 0 0         if ($self->verbose) {
534 0           local $Data::Dumper::Terse = 1;
535 0           print "Running views for the following: ", Dumper($simfiles);
536             }
537 0           foreach my $vf (@$simfiles) {
538 0 0         $vf =~ s:\\:/:g if $self->is_windowsish();# convert windows paths out
539 0           $vf =~ s:^\./::g; # remove ./ from the beginning
540 0 0 0       if (exists $tb_data->{$vf} and defined $tb_data->{$vf}->{wdb}) {
541 0           my $wdb = File::Spec->catfile($self->blib, $tb_data->{$vf}->{wdb});
542 0 0         unless (-e $wdb) {
543 0           carp "$wdb has not been created. You need to run ./Build simulate first";
544 0           next;
545             }
546             ## we do NOT chdir into the blib directory
547 0           $self->_exec_isimgui($wdb);
548 0           print "Finished viewing the output of $vf\n";
549             } else {
550 0           carp "$vf is not a valid testbench file";
551             }
552             }
553             } else {
554 0           print "No tests were run since no testbenches were found.\n";
555             }
556             }
557              
558             sub ACTION_program {
559 0     0 0   my $self = shift;
560 0           my $device = $self->SUPER::args('device');
561 0 0         carp "Guessing which device to use for programming." unless defined $device;
562 0 0 0       print "Programming the $device\n" if ($self->verbose and defined $device);
563 0           $self->ACTION_build(@_);
564 0           return $self->_exec_impact($device);
565             }
566              
567             sub _tcl_functions {
568 0     0     return << 'TCLFUNC';
569             proc add_parameter {param value} {
570             puts stderr "INFO: Setting $param to $value"
571             if {[catch {xilinx::project set $param $value} err]} then {
572             puts stderr "WARN: Unable to set $param to $value\n$err"
573             return 1
574             }
575             return 0
576             }
577              
578             proc add_parameters {plist} {
579             array set params $plist
580             foreach idx [lsort [array names params]] {
581             set param [lindex $params($idx) 0]
582             set value [lindex $params($idx) 1]
583             add_parameter $param $value
584             }
585             return 0
586             }
587             # we have a separate function for adding source and testbench
588             proc add_source_file {ff} {
589             if {[file exists $ff]} then {
590             set found [xilinx::search $ff -regexp -type file]
591             if {[xilinx::collection sizeof $found] == 0} then {
592             puts stderr "INFO: Adding $ff"
593             if {[catch {xilinx::xfile add $ff} err]} then {
594             puts stderr "ERROR: Unable to add $ff\n$err"
595             exit 1
596             }
597             } else {
598             puts stderr "INFO: $ff already in project"
599             }
600             } else {
601             puts stderr "WARN: $ff does not exist"
602             }
603             }
604              
605             proc add_testbench_file {ff} {
606             set viewname Simulation
607             if {[file exists $ff]} then {
608             set found [xilinx::search $ff -regexp -type file]
609             if {[xilinx::collection sizeof $found] == 0} then {
610             puts stderr "INFO: Adding $ff to $viewname"
611             if {[catch {xilinx::xfile add $ff -view $viewname} err]} then {
612             puts stderr "ERROR: Unable to add $ff\n$err"
613             exit 1
614             }
615             } else {
616             puts stderr "INFO: $ff already in project"
617             }
618             } else {
619             puts stderr "WARN: $ff does not exist"
620             }
621             }
622              
623             proc process_run_task {task} {
624             if {[catch {xilinx::process run $task} err]} then {
625             puts stderr "ERROR: Unable to run $task\n$err"
626             return 1
627             }
628             set rc [xilinx::process get $task status]
629             puts stderr "INFO: Status of $task: $rc\n"
630             if {[string compare $rc "errors"] == 0 ||
631             [string compare $rc "aborted"] == 0 } then {
632             puts stderr "ERROR: Unable to run $task: $rc\n"
633             return 1
634             }
635             return 0
636             }
637              
638             proc simulation_create {prj exe topname} {
639             if {[catch {exec fuse -incremental $topname -prj $prj -o $exe} err]} then {
640             puts stderr "ERROR: Unable to run fuse for $prj\n$err"
641             return 1
642             }
643             return 0
644             }
645              
646             proc simulation_run {exe cmd wdb logfile} {
647             if {[catch {exec $exe -tclbatch $cmd -wdb $wdb -log $logfile} err]} then {
648             puts stderr "ERROR: Unable to run $exe with $cmd\n$err"
649             return 1
650             }
651             return 0
652             }
653              
654             proc simulation_view {wdb} {
655             if {[catch {exec isimgui -view $wdb} err]} then {
656             puts stderr "ERROR: Unable to view $wdb\n$err"
657             return 1
658             }
659             return 0
660             }
661              
662             proc program_device {bitfiles ipf cmdfile} {
663             set cmdfile program_device.cmd
664             if {[catch {set fd [open $cmdfile w]} err]} then {
665             puts stderr "ERROR: Unable to open $cmdfile for writing\n$err"
666             return 1
667             }
668             puts $fd "setLog -file program_device.log"
669             puts $fd "setPreference -pref UserLevel:Novice"
670             puts $fd "setPreference -pref ConfigOnFailure:Stop"
671             puts $fd "setMode -bscan"
672             puts $fd "setCable -port auto"
673             puts $fd "identify"
674             for {set idx 0} {$idx < [llength $bitfiles]} {incr idx} {
675             set bitf [lindex $bitfiles $idx]
676             set ii [expr $idx + 1]
677             # we use assignFile over addDevice since it allows over-writing
678             puts $fd "assignFile -p $ii -file \"$bitf\""
679             }
680             for {set idx 0} {$idx < [llength $bitfiles]} {incr idx} {
681             set ii [expr $idx + 1]
682             puts $fd "program -p $ii"
683             }
684             puts $fd "checkIntegrity"
685             puts $fd "saveprojectfile -file \"$ipf\""
686             puts $fd "quit"
687             catch {close $fd}
688             if {[catch {exec impact -batch "./program_device.cmd"} err]} then {
689             #TODO: check log here for errors
690             puts stderr "ERROR: Unable to run impact to program the device"
691             return 1
692             }
693             return 0
694             }
695              
696             proc cleanup_and_exit {xise bdir errcode} {
697             if {[catch {xilinx::project close} err]} then {
698             puts stderr "WARN: error closing $xise\n$err"
699             exit 1
700             } else {
701             puts stderr "INFO: Closed $xise"
702             }
703             cd $bdir
704             exit $errcode
705             }
706              
707             proc open_project {projfile projname} {
708             if {[file exists $projfile]} then {
709             if {[catch {xilinx::project open $projname} err]} then {
710             puts stderr "ERROR: Could not open $projfile for reading\n$err"
711             exit 1
712             }
713             puts stderr "INFO: Opened $projfile"
714             } else {
715             if {[catch {xilinx::project new $projname} err]} then {
716             puts stderr "ERROR: Unable to create $projfile\n$err"
717             exit 1
718             }
719             puts stderr "INFO: Created $projfile"
720             }
721             }
722              
723             # separate tasks that should not be called together
724             proc clean_project {projfile} {
725             if {[catch {xilinx::project clean} err]} then {
726             puts stderr "WARN: Unable to clean $projfile\n$err"
727             } else {
728             puts stderr "INFO: cleaned project $projfile"
729             }
730             }
731              
732             proc print_usage {appname} {
733             puts stderr "$appname \[OPTIONS\]\n"
734             puts stderr "OPTIONS are any or all of the following:"
735             puts stderr "-setup\t\t\tCreates/Opens the project and adds parameters, files"
736             puts stderr "-build\t\t\tBuilds the project and generates bitstream"
737             puts stderr "-simulate\t\tSimulates the generated bitstream"
738             puts stderr "-view\t\t\tView the simulation output using isimgui"
739             puts stderr "-all\t\t\tAlias for '-clean -setup -build -simulate'"
740             puts stderr "-clean\t\t\tCleans the project. Has highest precedence"
741             puts stderr "-program \[dev\]\t\tProgram the device given"
742             exit 1
743             }
744              
745             proc create_file {ff} {
746             if {[catch {set fd [open $ff w]} err]} then {
747             puts stderr "ERROR: Unable to open $ff for writing\n$err"
748             return 1
749             }
750             puts $fd "1"
751             catch {close $fd}
752             }
753              
754             TCLFUNC
755             }
756              
757             sub _dump_tcl_code {
758 0     0     my $self = shift;
759 0           my $projext = $self->proj_ext;
760 0           my $projname = $self->proj_name;
761 0           my $dir_build = $self->blib;
762 0           my $src_files = join(' ', @{$self->source_files});
  0            
763 0           my $tb_files = join(' ', @{$self->testbench_files});
  0            
764 0           my $tbsrc_files = join(' ', @{$self->testbenchsrc_files});
  0            
765 0           my @tbfiles = (); # ordered tb matching
766 0           my @prjs = ();
767 0           my @exes = ();
768 0           my @toplevels = ();
769 0           my @srclibs = ();
770 0           my @cmds = ();
771 0           my @wdbs = ();
772              
773 0           my $tb_data = $self->testbench;
774 0           foreach my $f (keys %$tb_data) {
775 0           my $hh = $tb_data->{$f};
776             # we assume these have to be defined
777 0           push @tbfiles, $f;
778 0           push @prjs, $hh->{prj};
779 0           push @cmds, $hh->{cmd};
780 0           push @wdbs, $hh->{wdb};
781 0           push @exes, $hh->{exe};
782 0           push @toplevels, $hh->{toplevel};
783 0           push @srclibs, $hh->{srclib};
784             }
785 0           my $total_files = scalar @prjs + scalar @cmds + scalar @wdbs + scalar @exes
786             + scalar @toplevels + scalar @srclibs;
787 0 0         carp "There is a mismatch in the count of internal files" if
788             (6 * scalar @prjs) != $total_files;
789 0           $total_files /= 6;
790 0           my $prj_files = join(' ', @prjs);
791 0           my $exe_files = join(' ', @exes);
792 0           my $toplevels_ = join(' ', @toplevels);
793 0           my $srclibs_ = join(' ', @srclibs);
794 0           my $cmd_files = join(' ', @cmds);
795 0           my $wdb_files = join(' ', @wdbs);
796 0           my %pp = %{$self->proj_params};
  0            
797 0   0       $pp{family} = $pp{family} || 'spartan3a';
798 0   0       $pp{device} = $pp{device} || 'xc3s700a';
799 0   0       $pp{package} = $pp{package} || 'fg484';
800 0   0       $pp{speed} = $pp{speed} || '-4';
801 0   0       $pp{language} = $pp{language} || 'N/A';
802 0   0       $pp{devboard} = $pp{devboard} || 'None Specified';
803 0           my $vars = << "TCLVARS";
804             # input parameters start here
805             set projext {$projext}
806             set projname {$projname}
807             set dir_build $dir_build
808             # Tcl arrays are associative arrays. We need these parameters set in order hence
809             # we use integers as keys to the parameters
810             # the following can be retrieved by running the command partgen -arch spartan3a
811             # this allows the same UCF file used in multiple projects as long as the
812             # constraint names stay the same
813             array set projparams {
814             0 {family $pp{family}}
815             1 {device $pp{device}}
816             2 {package $pp{package}}
817             3 {speed $pp{speed}}
818             4 {"Preferred Language" $pp{language}}
819             5 {"Evaluation Development Board" "$pp{devboard}"}
820             6 {"Allow Unmatched LOC Constraints" true}
821             7 {"Write Timing Constraints" true}
822             }
823             # test bench file names matter ! Refer \$Xilinx/data/projnav/xil_tb_patterns.txt
824             # it has to end in _tb/_tf or should be named testbench
825             # the constraint file and test bench go together for simulation purposes
826             set src_files [list $src_files]
827             set tb_files [list $tb_files]
828             set tbsrc_files [list $tbsrc_files]
829             set prj_files [list $prj_files]
830             set exe_files [list $exe_files]
831             set toplevels [list $toplevels_]
832             set srclibs [list $srclibs_]
833             set cmd_files [list $cmd_files]
834             set wdb_files [list $wdb_files]
835             set tb_count $total_files
836              
837             TCLVARS
838 0           my $functions = $self->_tcl_functions;
839 0           my $basecode = << 'TCLBASE';
840             # main code starts here
841             #
842             set mode_setup 0
843             set mode_build 0
844             set mode_simulate 0
845             set mode_view 0
846             set mode_program 0
847             set mode_clean 0
848             set device_name ""
849              
850             if { $argc > 0 } then {
851             for {set idx 0} {$idx < $argc} {incr idx} {
852             set opt [lindex $argv $idx]
853             if {$opt == "-setup"} then {
854             set mode_setup 1
855             } elseif {$opt == "-build"} then {
856             set mode_build 1
857             } elseif {$opt == "-simulate"} then {
858             set mode_simulate 1
859             } elseif {$opt == "-view"} then {
860             set mode_view 1
861             } elseif {$opt == "-clean"} then {
862             set mode_clean 1
863             } elseif {$opt == "-all"} then {
864             set mode_clean 1
865             set mode_setup 1
866             set mode_build 1
867             set mode_simulate 1
868             } elseif {$opt == "-program"} then {
869             set mode_program 1
870             incr idx
871             if {$idx < $argc} then {
872             set device_name [lindex $argv $idx]
873             } else {
874             puts stderr "WARN: device name not given."
875             }
876             } else {
877             print_usage $argv0
878             }
879             }
880             } else {
881             print_usage $argv0
882             }
883              
884             set projfile $projname$projext
885             set basedir [pwd]
886             set builddir $basedir/$dir_build
887             set srcdir $basedir
888             set tbdir $basedir
889             catch {exec mkdir $builddir}
890             cd $builddir
891             puts stderr "INFO: In $builddir"
892             # this is necessary after the chdir
893             set projipf [pwd]/$projname.ipf
894              
895             open_project $projfile $projname
896             if {$mode_clean == 1} then {
897             clean_project $projfile
898             file delete -force .done_setup .done_build .done_simulate
899             }
900             # check if other options need to be set
901             if {![file exists .done_simulate] && $mode_view == 1} then {
902             puts stderr "INFO: No .done_simulate found in $builddir so running simulate"
903             set mode_simulate 1
904             }
905             if {![file exists .done_build] && ($mode_simulate == 1 || $mode_view == 1 || $mode_program == 1)} then {
906             puts stderr "INFO: No .done_build found $builddir so running build"
907             set mode_build 1
908             }
909             if {![file exists .done_setup] && $mode_build == 1} then {
910             puts stderr "INFO: No .done_setup found in $builddir so running setup"
911             set mode_setup 1
912             }
913              
914             TCLBASE
915              
916 0           my $single_setup = << 'TCLSETUP0';
917             if {$mode_setup == 1} then {
918             # perform setting of the project parameters
919             add_parameters [array get projparams]
920             foreach fname $src_files {
921             set ff $srcdir/$fname
922             add_source_file $ff
923             }
924             foreach fname $tb_files {
925             set ff $tbdir/$fname
926             add_testbench_file $ff
927             }
928             foreach fname $tbsrc_files {
929             set ff $tbdir/$fname
930             add_testbench_file $ff
931             }
932             add_parameter {iMPACT Project File} $projipf
933             TCLSETUP0
934 0           for (my $i = 0; $i < scalar @prjs; ++$i) {
935 0           my $tb_prj = $prjs[$i];
936 0           my $tb_lib = $srclibs[$i];
937 0           my $tb_f = $tbfiles[$i];
938 0           $single_setup .= << "TCL_PRJ_ADD1";
939             if {1} then {
940             set tb_prj $tb_prj
941             set tb_lib $tb_lib
942             set tb_idx $i
943             set tb_f $tb_f
944             TCL_PRJ_ADD1
945 0           $single_setup .= << 'TCL_PRJ_ADD2';
946             # also create the prj file for simulation later
947             if {[catch {set fd [open $tb_prj w]} err]} then {
948             puts stderr "ERROR: Unable to open $tb_prj for writing\n$err"
949             cleanup_and_exit $projfile $basedir 1
950             }
951             foreach fname $src_files {
952             set ff $srcdir/$fname
953             if {[string match *.ucf $fname]} then {
954             puts stderr "INFO: Not adding $ff to $tb_prj"
955             } elseif {[string match *.vhd $fname]} then {
956             puts $fd "vhdl $tb_lib \"$ff\""
957             } elseif {[string match *.vhdl $fname]} then {
958             puts $fd "vhdl $tb_lib \"$ff\""
959             } else {
960             puts $fd "verilog $tb_lib \"$ff\""
961             }
962             }
963             foreach fname $tbsrc_files {
964             set ff $tbdir/$fname
965             if {[string match *.ucf $fname]} then {
966             puts stderr "INFO: Not adding $ff to $tb_prj"
967             } elseif {[string match *.vhd $fname]} then {
968             puts $fd "vhdl $tb_lib \"$ff\""
969             } elseif {[string match *.vhdl $fname]} then {
970             puts $fd "vhdl $tb_lib \"$ff\""
971             } else {
972             puts $fd "verilog $tb_lib \"$ff\""
973             }
974             }
975             if {[string match *.vhd $tb_f]} then {
976             puts $fd "vhdl $tb_lib \"$tbdir/$tb_f\""
977             } elseif {[string match *.vhdl $tb_f]} then {
978             puts $fd "vhdl $tb_lib \"$tbdir/$tb_f\""
979             } else {
980             puts $fd "verilog $tb_lib \"$tbdir/$tb_f\""
981             }
982             catch {close $fd}
983             }
984             TCL_PRJ_ADD2
985             } ## end of for loop
986 0           $single_setup .= << 'TCLSETUP1';
987             create_file .done_setup
988             }
989             TCLSETUP1
990 0           my $build_code = << 'TCLBUILD';
991             if {$mode_build == 1} then {
992             if {[process_run_task "Check Syntax"]} then {
993             cleanup_and_exit $projfile $basedir 1
994             }
995             if {[process_run_task "Implement Design"]} then {
996             cleanup_and_exit $projfile $basedir 1
997             }
998             if {[process_run_task "Generate Programming File"]} then {
999             cleanup_and_exit $projfile $basedir 1
1000             }
1001             create_file .done_build
1002             }
1003             TCLBUILD
1004              
1005 0           my $sim_code = << 'TCLSIM0';
1006             if {$mode_simulate == 1} then {
1007             TCLSIM0
1008 0           my $view_code = '';
1009 0           for (my $i = 0; $i < scalar @prjs; ++$i) {
1010 0           my $tb_prj = $prjs[$i];
1011 0           my $tb_lib = $srclibs[$i];
1012 0           my $tb_top = $toplevels[$i];
1013 0           my $tb_exe = $exes[$i];
1014 0           my $tb_cmd = $cmds[$i];
1015 0           my $tb_wdb = $wdbs[$i];
1016 0           my $tb_log = $tb_exe . '.log';
1017 0           $tb_log =~ s/\.exe//g;
1018 0           $sim_code .= << "TCLSIM1";
1019             if {1} then {
1020             set tb_prj $tb_prj
1021             set tb_lib $tb_lib
1022             set tb_top $tb_top
1023             set tb_exe $tb_exe
1024             set tb_cmd $tb_cmd
1025             set tb_wdb $tb_wdb
1026             set tb_idx $i
1027             set tb_log $tb_log
1028             TCLSIM1
1029 0           $sim_code .= << 'TCLSIM2';
1030             # create the simulation executable
1031             set topname $tb_lib.$tb_top
1032             if {[simulation_create $tb_prj $tb_exe $topname]} then {
1033             cleanup_and_exit $projfile $basedir 1
1034             }
1035             # create the simulation command file
1036             if {[catch {set fd [open $tb_cmd w]} err]} then {
1037             puts stderr "ERROR: Unable to open $tb_cmd for writing\n$err"
1038             cleanup_and_exit $projfile $basedir 1
1039             }
1040             puts $fd "onerror \{resume\}"
1041             puts $fd "wave add /"
1042             puts $fd "run all"
1043             puts $fd "quit -f"
1044             catch {close $fd}
1045             set path2exe [pwd]/$tb_exe
1046             if {[simulation_run $path2exe $tb_cmd $tb_wdb $tb_log]} then {
1047             cleanup_and_exit $projfile $basedir 1
1048             }
1049             puts stderr "INFO: simulation($tb_idx) complete"
1050             }
1051             TCLSIM2
1052 0           $view_code .= << "TCLVIEW0";
1053             if {\$mode_view == 1} then {
1054             set tb_wdb $tb_wdb
1055             TCLVIEW0
1056 0           $view_code .= << 'TCLVIEW1';
1057             if {[simulation_view $tb_wdb]} then {
1058             cleanup_and_exit $projfile $basedir 1
1059             }
1060             }
1061             TCLVIEW1
1062             } ## end of for loop
1063 0           $sim_code .= << 'TCLSIM2';
1064             create_file .done_simulate
1065             }
1066             TCLSIM2
1067              
1068 0           my $prog_code .= << 'TCLPROG';
1069             if {$mode_program == 1} then {
1070             puts stderr "INFO: will try to program device $device_name"
1071             set ipf [pwd]/$projname.ipf
1072             set bit_files [glob -nocomplain -tails -directory $builddir *.bit]
1073             set cmdfile program_device.cmd
1074             if {[program_device $bit_files $ipf $cmdfile]} then {
1075             cleanup_and_exit $projfile $basedir 1
1076             }
1077             # we should set the {iMPACT Project File} value
1078             add_parameter {iMPACT Project File} $ipf
1079             puts stderr "INFO: Done programming device $device_name"
1080             }
1081             TCLPROG
1082 0           return << "TCLCODE";
1083             ### -- THIS PROGRAM IS AUTO GENERATED -- DO NOT EDIT -- ###
1084             $vars
1085             $functions
1086             $basecode
1087             $single_setup
1088             $build_code
1089             $sim_code
1090             $view_code
1091             $prog_code
1092             # ok now cleanup and exit with 0
1093             cleanup_and_exit \$projfile \$basedir 0
1094              
1095             TCLCODE
1096             }
1097              
1098             1;
1099             __END__