#!/usr/bin/perl -w
|
#!/usr/bin/perl -w
|
|
|
|
use lib "../perl_lib";
|
|
|
|
use List::MoreUtils qw(uniq);
|
use Proc::Background;
|
use Proc::Background;
|
use File::Path qw( rmtree );
|
use File::Path qw( rmtree );
|
|
use Cwd 'abs_path';
|
|
|
my $script_path = dirname(__FILE__);
|
my $script_path = dirname(__FILE__);
|
my $dirname = "$script_path/..";
|
my $dirname = "$script_path/..";
|
my $root = "$dirname/../..";
|
my $root = "$dirname/../..";
|
|
|
my $rtl_dir = "$ENV{PRONOC_WORK}/verify/rtl";
|
my $rtl_dir = "$ENV{PRONOC_WORK}/verify/rtl";
|
my $work = "$ENV{PRONOC_WORK}/verify/work";
|
my $work = "$ENV{PRONOC_WORK}/verify/work";
|
my $src_verilator = "$root/src_verilator";
|
my $src_verilator = "$root/src_verilator";
|
my $src_c = "$root/src_c";
|
my $src_c = "$root/src_c";
|
my $src = "$script_path";
|
my $src = "$script_path";
|
my $report = "$dirname/report";
|
my $report = "$dirname/report";
|
|
|
require "$root/perl_gui/lib/perl/common.pl";
|
#require "$root/perl_gui/lib/perl/common.pl";
|
require "$root/perl_gui/lib/perl/topology.pl";
|
require "$root/perl_gui/lib/perl/topology.pl";
|
|
|
use strict;
|
use strict;
|
use warnings;
|
use warnings;
|
|
|
my $pp;
|
my $pp;
|
$pp= do "$src/deafult_noc_param";
|
$pp= do "$src/deafult_noc_param";
|
die "Error reading: $@" if $@;
|
die "Error reading: $@" if $@;
|
|
|
my $param = $pp->{'noc_param'};
|
my $param = $pp->{'noc_param'};
|
my %default_noc_param=%{$param};
|
my %default_noc_param=%{$param};
|
my @params=object_get_attribute_order($pp,'noc_param');
|
my @params=object_get_attribute_order($pp,'noc_param');
|
|
|
|
|
|
|
#read default param
|
#read default param
|
|
|
|
|
sub recompile_synful {
|
sub recompile_synful {
|
|
|
#recmpile synful
|
#recmpile synful
|
my $cmd ="cd $src_c/synfull/traffic-generator/src; make; wait;\n";
|
my $cmd ="cd $src_c/synfull/traffic-generator/src; make; wait;\n";
|
#run command in terminal
|
#run command in terminal
|
print "*******************compile synful******************\n$cmd\n";
|
print "*******************compile synful******************\n$cmd\n";
|
my $proc1 = Proc::Background->new($cmd);
|
my $proc1 = Proc::Background->new($cmd);
|
$proc1->alive;
|
$proc1->alive;
|
$proc1->wait;
|
$proc1->wait;
|
$proc1->die;
|
$proc1->die;
|
|
|
}
|
}
|
|
|
sub gen_noc_param_h{
|
sub gen_noc_param_h{
|
my $mpsoc=shift;
|
my $mpsoc=shift;
|
my $param_h="\n\n//NoC parameters\n";
|
my $param_h="\n\n//NoC parameters\n";
|
|
|
my $topology = $mpsoc->object_get_attribute('noc_param','TOPOLOGY');
|
my $topology = $mpsoc->object_get_attribute('noc_param','TOPOLOGY');
|
$topology =~ s/"//g;
|
$topology =~ s/"//g;
|
$param_h.="\t#define IS_${topology}\n";
|
$param_h.="\t#define IS_${topology}\n";
|
|
|
my ($NE, $NR, $RAw, $EAw, $Fw) = get_topology_info($mpsoc);
|
my ($NE, $NR, $RAw, $EAw, $Fw) = get_topology_info($mpsoc);
|
|
|
my @params=$mpsoc->object_get_attribute_order('noc_param');
|
my @params=$mpsoc->object_get_attribute_order('noc_param');
|
my $custom_topology = $mpsoc->object_get_attribute('noc_param','CUSTOM_TOPOLOGY_NAME');
|
my $custom_topology = $mpsoc->object_get_attribute('noc_param','CUSTOM_TOPOLOGY_NAME');
|
foreach my $p (@params){
|
foreach my $p (@params){
|
my $val=$mpsoc->object_get_attribute('noc_param',$p);
|
my $val=$mpsoc->object_get_attribute('noc_param',$p);
|
next if($p eq "CUSTOM_TOPOLOGY_NAME");
|
next if($p eq "CUSTOM_TOPOLOGY_NAME");
|
$val=$custom_topology if($p eq "TOPOLOGY" && $val eq "\"CUSTOM\"");
|
$val=$custom_topology if($p eq "TOPOLOGY" && $val eq "\"CUSTOM\"");
|
if($p eq "MCAST_ENDP_LIST" || $p eq "ESCAP_VC_MASK"){
|
if($p eq "MCAST_ENDP_LIST" || $p eq "ESCAP_VC_MASK"){
|
$val="$NE".$val if($p eq 'MCAST_ENDP_LIST');
|
$val="$NE".$val if($p eq 'MCAST_ENDP_LIST');
|
$val =~ s/\'/\\\'/g;
|
$val =~ s/\'/\\\'/g;
|
$val="\"$val\"";
|
$val="\"$val\"";
|
}
|
}
|
$param_h=$param_h."\t#define $p\t$val\n";
|
$param_h=$param_h."\t#define $p\t$val\n";
|
|
|
#print "$p:$val\n";
|
#print "$p:$val\n";
|
|
|
}
|
}
|
|
|
|
|
my $v=$mpsoc->object_get_attribute('noc_param',"V")-1;
|
my $v=$mpsoc->object_get_attribute('noc_param',"V")-1;
|
my $escape=$mpsoc->object_get_attribute('noc_param',"ESCAP_VC_MASK");
|
my $escape=$mpsoc->object_get_attribute('noc_param',"ESCAP_VC_MASK");
|
if (! defined $escape){
|
if (! defined $escape){
|
#add_text_to_string (\$param_h,"\tlocalparam [$v :0] ESCAP_VC_MASK=1;\n");
|
#add_text_to_string (\$param_h,"\tlocalparam [$v :0] ESCAP_VC_MASK=1;\n");
|
#add_text_to_string (\$pass_param,".ESCAP_VC_MASK(ESCAP_VC_MASK),\n");
|
#add_text_to_string (\$pass_param,".ESCAP_VC_MASK(ESCAP_VC_MASK),\n");
|
}
|
}
|
#add_text_to_string (\$param_h," \tlocalparam CVw=(C==0)? V : C * V;\n");
|
#add_text_to_string (\$param_h," \tlocalparam CVw=(C==0)? V : C * V;\n");
|
#add_text_to_string (\$pass_param,".CVw(CVw)\n");
|
#add_text_to_string (\$pass_param,".CVw(CVw)\n");
|
|
|
#remove 'b and 'h
|
#remove 'b and 'h
|
#$param_h =~ s/\d\'b/ /g;
|
#$param_h =~ s/\d\'b/ /g;
|
#$param_h =~ s/\'h/ /g;
|
#$param_h =~ s/\'h/ /g;
|
|
|
|
|
return $param_h;
|
return $param_h;
|
}
|
}
|
|
|
|
|
sub gen_sim_parameter_h {
|
sub gen_sim_parameter_h {
|
my ($param_h,$includ_h,$ne,$nr,$router_p,$fifow)=@_;
|
my ($param_h,$includ_h,$ne,$nr,$router_p,$fifow)=@_;
|
|
|
$param_h =~ s/\d\'b/ /g;
|
$param_h =~ s/\d\'b/ /g;
|
my $text= "
|
my $text= "
|
#ifndef INCLUDE_PARAM
|
#ifndef INCLUDE_PARAM
|
#define INCLUDE_PARAM \n \n
|
#define INCLUDE_PARAM \n \n
|
|
|
$param_h
|
$param_h
|
|
|
#define NE $ne
|
#define NE $ne
|
#define NR $nr
|
#define NR $nr
|
#define ROUTER_P_NUM $router_p
|
#define ROUTER_P_NUM $router_p
|
|
|
extern Vtraffic *traffic[NE];
|
extern Vtraffic *traffic[NE];
|
extern Vpck_inj *pck_inj[NE];
|
extern Vpck_inj *pck_inj[NE];
|
extern int reset,clk;
|
extern int reset,clk;
|
|
|
//simulation parameter
|
//simulation parameter
|
#define MAX_RATIO 1000
|
#define MAX_RATIO 1000
|
#define AVG_LATENCY_METRIC \"HEAD_2_TAIL\"
|
#define AVG_LATENCY_METRIC \"HEAD_2_TAIL\"
|
#define TIMSTMP_FIFO_NUM $fifow
|
#define TIMSTMP_FIFO_NUM $fifow
|
|
|
$includ_h
|
$includ_h
|
\n \n \#endif" ;
|
\n \n \#endif" ;
|
return $text;
|
return $text;
|
}
|
}
|
|
|
|
|
sub extract_and_update_noc_sim_statistic {
|
sub extract_and_update_noc_sim_statistic {
|
my ($stdout)=@_;
|
my ($stdout)=@_;
|
my $avg_latency =capture_number_after("average packet latency =",$stdout);
|
my $avg_latency =capture_number_after("average packet latency =",$stdout);
|
my $avg_flit_latency =capture_number_after("average flit latency =",$stdout);
|
my $avg_flit_latency =capture_number_after("average flit latency =",$stdout);
|
my $sd_latency =capture_number_after("standard_dev =",$stdout);
|
my $sd_latency =capture_number_after("standard_dev =",$stdout);
|
my $avg_thput =capture_number_after("Avg throughput is:",$stdout);
|
my $avg_thput =capture_number_after("Avg throughput is:",$stdout);
|
my $total_time =capture_number_after("simulation clock cycles:",$stdout);
|
my $total_time =capture_number_after("simulation clock cycles:",$stdout);
|
my $latency_perhop = capture_number_after("average latency per hop =",$stdout);
|
my $latency_perhop = capture_number_after("average latency per hop =",$stdout);
|
my %packet_rsvd_per_core = capture_cores_data("total number of received packets:",$stdout);
|
my %packet_rsvd_per_core = capture_cores_data("total number of received packets:",$stdout);
|
my %worst_rsvd_delay_per_core = capture_cores_data('worst-case-delay of received packets \(clks\):',$stdout);
|
my %worst_rsvd_delay_per_core = capture_cores_data('worst-case-delay of received packets \(clks\):',$stdout);
|
my %packet_sent_per_core = capture_cores_data("total number of sent packets:",$stdout);
|
my %packet_sent_per_core = capture_cores_data("total number of sent packets:",$stdout);
|
my %worst_sent_delay_per_core = capture_cores_data('worst-case-delay of sent packets \(clks\):',$stdout);
|
my %worst_sent_delay_per_core = capture_cores_data('worst-case-delay of sent packets \(clks\):',$stdout);
|
|
|
|
|
}
|
}
|
|
|
sub get_model_parameter {
|
sub get_model_parameter {
|
my $model =shift;
|
my $model =shift;
|
my $o;
|
my $o;
|
$o= do $model;
|
$o= do $model;
|
my %new_param=%{$o};
|
my %new_param=%{$o};
|
die "Error reading: $@" if $@;
|
die "Error reading: $@" if $@;
|
my %temp;
|
my %temp;
|
foreach my $p (@params){
|
foreach my $p (@params){
|
$temp{$p} = $default_noc_param{$p};
|
$temp{$p} = $default_noc_param{$p};
|
}
|
}
|
foreach my $p (sort keys %new_param){
|
foreach my $p (sort keys %new_param){
|
$temp{$p} = $new_param{$p};
|
$temp{$p} = $new_param{$p};
|
}
|
}
|
return %temp;
|
return %temp;
|
}
|
}
|
|
|
sub gen_noc_localparam_v {
|
sub gen_noc_localparam_v {
|
my ($m,$ref) = @_;
|
my ($m,$ref) = @_;
|
my %model = %{$ref};
|
my %model = %{$ref};
|
my %temp;
|
my %temp;
|
|
|
|
|
foreach my $p (@params){
|
foreach my $p (@params){
|
$temp{$p} = $default_noc_param{$p};
|
$temp{$p} = $default_noc_param{$p};
|
$m->{noc_param}{$p}=$default_noc_param{$p};
|
$m->{noc_param}{$p}=$default_noc_param{$p};
|
}
|
}
|
foreach my $p (sort keys %model){
|
foreach my $p (sort keys %model){
|
$temp{$p} = $model{$p};
|
$temp{$p} = $model{$p};
|
$m->{noc_param}{$p}=$model{$p};
|
$m->{noc_param}{$p}=$model{$p};
|
}
|
}
|
|
|
object_add_attribute_order($m,'noc_param',@params);
|
object_add_attribute_order($m,'noc_param',@params);
|
|
|
my $param_v="`ifdef NOC_LOCAL_PARAM \n";
|
my $param_v="`ifdef NOC_LOCAL_PARAM \n";
|
foreach my $p (@params){
|
foreach my $p (@params){
|
$param_v.="localparam $p = $temp{$p};\n";
|
$param_v.="localparam $p = $temp{$p};\n";
|
}
|
}
|
$param_v.="`endif\n";
|
$param_v.="`endif\n";
|
|
|
my ($nr,$ne,$router_p,$ref_tops,$includ_h) = get_noc_verilator_top_modules_info($m);
|
my ($nr,$ne,$router_p,$ref_tops,$includ_h) = get_noc_verilator_top_modules_info($m);
|
my %tops = %{$ref_tops};
|
my %tops = %{$ref_tops};
|
$tops{Vtraffic} = "--top-module traffic_gen_top";
|
$tops{Vtraffic} = "--top-module traffic_gen_top";
|
$tops{Vpck_inj} = "--top-module packet_injector_verilator";
|
$tops{Vpck_inj} = "--top-module packet_injector_verilator";
|
|
|
|
|
|
|
|
|
my $param_h=gen_noc_param_h($m);
|
my $param_h=gen_noc_param_h($m);
|
$includ_h = gen_sim_parameter_h($param_h,$includ_h,$ne,$nr,$router_p,'16');
|
$includ_h = gen_sim_parameter_h($param_h,$includ_h,$ne,$nr,$router_p,'16');
|
|
|
return ($param_v,$includ_h,\%tops);
|
return ($param_v,$includ_h,\%tops);
|
|
|
}
|
}
|
|
|
|
|
sub copy_src_files{
|
sub copy_src_files{
|
|
|
if(defined $ENV{PRONOC_WORK}){
|
if(defined $ENV{PRONOC_WORK}){
|
rmtree("$rtl_dir");
|
rmtree("$rtl_dir");
|
unless (-d "$rtl_dir"){
|
unless (-d "$rtl_dir"){
|
print "make a working directory inside $rtl_dir\n";
|
print "make a working directory inside $rtl_dir\n";
|
mkdir("$rtl_dir", 0700);
|
mkdir("$rtl_dir", 0700);
|
|
|
}
|
}
|
}else{
|
}else{
|
print "Please set PRONOC_WORK variable first!";
|
print "Please set PRONOC_WORK variable first!";
|
exit;
|
exit;
|
}
|
}
|
|
|
dircopy("$root/rtl/src_noc" , "$rtl_dir/src_noc" ) or die("$!\n") unless (-d "$rtl_dir/src_noc" );
|
dircopy("$root/rtl/src_noc" , "$rtl_dir/src_noc" ) or die("$!\n") unless (-d "$rtl_dir/src_noc" );
|
dircopy("$root/rtl/src_topolgy", "$rtl_dir/src_topolgy") or die("$!\n") unless (-d "$rtl_dir/src_topolgy");
|
dircopy("$root/rtl/src_topolgy", "$rtl_dir/src_topolgy") or die("$!\n") unless (-d "$rtl_dir/src_topolgy");
|
|
|
unlink "$rtl_dir/src_noc/noc_localparam.v";
|
unlink "$rtl_dir/src_noc/noc_localparam.v";
|
for my $file (glob "$root/rtl/*.v") {
|
for my $file (glob "$root/rtl/*.v") {
|
copy $file, "$rtl_dir" or die $! ;
|
copy $file, "$rtl_dir" or die $! ;
|
}
|
}
|
|
|
|
|
|
|
}
|
}
|
|
|
|
|
|
|
|
|
sub gen_file_list{
|
sub gen_file_list{
|
my $path=shift;
|
my $path=shift;
|
my $f="+incdir+$rtl_dir/
|
my $f="+incdir+$rtl_dir/
|
+incdir+$rtl_dir/src_noc/
|
+incdir+$rtl_dir/src_noc/
|
+incdir+$path
|
+incdir+$path
|
";
|
";
|
|
|
my @files = File::Find::Rule->file()
|
my @files = File::Find::Rule->file()
|
->name( '*.v','*.V','*.sv' )
|
->name( '*.v','*.V','*.sv' )
|
->in( "$rtl_dir" );
|
->in( "$rtl_dir" );
|
|
|
#make sure source files have key word 'module'
|
#make sure source files have key word 'module'
|
my @sources;
|
my @sources;
|
foreach my $p (@files){
|
foreach my $p (@files){
|
push (@sources,$p) if(check_file_has_string($p,'endpackage'));
|
push (@sources,$p) if(check_file_has_string($p,'endpackage'));
|
}
|
}
|
foreach my $p (@files){
|
foreach my $p (@files){
|
push (@sources,$p) if(check_file_has_string($p,'module'));
|
push (@sources,$p) if(check_file_has_string($p,'module'));
|
}
|
}
|
my $files = join ("\n",@sources);
|
my $files = join ("\n",@sources);
|
$f.=$files;
|
$f.=$files;
|
|
|
|
|
open(FILE, ">$path/file_list.f") || die "Can not open: $!";
|
open(FILE, ">$path/file_list.f") || die "Can not open: $!";
|
print FILE $f;
|
print FILE $f;
|
close FILE;
|
close FILE;
|
}
|
}
|
|
|
sub gen_verilator_sh{
|
sub gen_verilator_sh{
|
my ($ref,$file)=@_;
|
my ($ref,$file)=@_;
|
my %tops = %{$ref};
|
my %tops = %{$ref};
|
my $make_lib="";
|
my $make_lib="";
|
my $jobs=0;
|
my $jobs=0;
|
my $cmd= '#!/bin/bash
|
my $cmd= '#!/bin/bash
|
SCRPT_FULL_PATH=$(realpath ${BASH_SOURCE[0]})
|
SCRPT_FULL_PATH=$(realpath ${BASH_SOURCE[0]})
|
SCRPT_DIR_PATH=$(dirname $SCRPT_FULL_PATH)
|
SCRPT_DIR_PATH=$(dirname $SCRPT_FULL_PATH)
|
|
|
cmn="-O3 -CFLAGS -O3"
|
cmn="-O3 -CFLAGS -O3"
|
currentver=$(verilator --version | head -n1 | cut -d" " -f2)
|
currentver=$(verilator --version | head -n1 | cut -d" " -f2)
|
requiredver="4.0.0"
|
requiredver="4.0.0"
|
if [ "$(printf \'%s\n\' "$requiredver" "$currentver" | sort -V | head -n1)" = "$requiredver" ]; then
|
if [ "$(printf \'%s\n\' "$requiredver" "$currentver" | sort -V | head -n1)" = "$requiredver" ]; then
|
echo "Verilator vesrion Greater than or equal to ${requiredver}, compile with -Wno-TIMESCALEMOD flag"
|
echo "Verilator vesrion Greater than or equal to ${requiredver}, compile with -Wno-TIMESCALEMOD flag"
|
cmn=" $cmn -Wno-TIMESCALEMOD";
|
cmn=" $cmn -Wno-TIMESCALEMOD";
|
else
|
else
|
echo "Verilator vesrion is Less than ${requiredver}"
|
echo "Verilator vesrion is Less than ${requiredver}"
|
fi
|
fi
|
';
|
';
|
|
|
foreach my $top (sort keys %tops) {
|
foreach my $top (sort keys %tops) {
|
$cmd.= "verilator -f \$SCRPT_DIR_PATH/file_list.f --cc $tops{$top} --prefix \"$top\" \$cmn & \n";
|
$cmd.= "verilator -f \$SCRPT_DIR_PATH/file_list.f --cc $tops{$top} --prefix \"$top\" \$cmn & \n";
|
}
|
}
|
$cmd.="wait\n";
|
$cmd.="wait\n";
|
foreach my $top (sort keys %tops) {
|
foreach my $top (sort keys %tops) {
|
|
|
$cmd.="
|
$cmd.="
|
if ! [ -f \$SCRPT_DIR_PATH/obj_dir/$top.cpp ]; then
|
if ! [ -f \$SCRPT_DIR_PATH/obj_dir/$top.cpp ]; then
|
echo \"Failed to generate: \$SCRPT_DIR_PATH/obj_dir/$top.cpp \"
|
echo \"Failed to generate: \$SCRPT_DIR_PATH/obj_dir/$top.cpp \"
|
exit 1
|
exit 1
|
fi\n";
|
fi\n";
|
$make_lib.="make lib$jobs &\n";
|
$make_lib.="make lib$jobs &\n";
|
$jobs++;
|
$jobs++;
|
}
|
}
|
|
|
|
|
$cmd.="
|
$cmd.="
|
cd \$SCRPT_DIR_PATH/obj_dir/
|
cd \$SCRPT_DIR_PATH/obj_dir/
|
$make_lib
|
$make_lib
|
wait
|
wait
|
|
|
make sim
|
make sim
|
";
|
";
|
save_file("$file",$cmd);
|
save_file("$file",$cmd);
|
|
|
}
|
}
|
|
|
sub get_model_names {
|
sub get_model_names {
|
my ($mref,$inref) = @_;
|
my ($mref,$inref) = @_;
|
my @models = @{$mref};
|
my @models = @{$mref};
|
my ($paralel_run,$MIN,$MAX,$STEP,$model_dir)=@{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP,$model_dir)=@{$inref};
|
my $full_path;
|
my $full_path;
|
$full_path = "$model_dir" if (-d "$model_dir");
|
$full_path = "$model_dir" if (-d "$model_dir");
|
$full_path = "$dirname/$model_dir" if (-d "$dirname/$model_dir");
|
$full_path = "$dirname/$model_dir" if (-d "$dirname/$model_dir");
|
if (!defined $full_path){
|
if (!defined $full_path){
|
die "Error the model directory $model_dir or $dirname/$model_dir is not found\n";
|
die "Error the model directory $model_dir or $dirname/$model_dir is not found\n";
|
}
|
}
|
my @m;
|
my @m;
|
if(scalar @models == 0){
|
if(scalar @models == 0){
|
@m = glob("$full_path/*");
|
@m = glob("$full_path/*");
|
return @m;
|
return @m;
|
}
|
}
|
foreach my $p (@models) {
|
foreach my $p (@models) {
|
push (@m,"$full_path/$p");
|
push (@m,"$full_path/$p");
|
}
|
}
|
return @m;
|
return @m;
|
}
|
}
|
|
|
|
|
|
sub check_models_are_exsited {
|
|
my ($mref, $inref) = @_;
|
|
my @models = get_model_names(@_);
|
|
foreach my $m (@models){
|
|
unless (-f $m ){
|
|
die "Error: no such file $m";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
sub gen_models {
|
sub gen_models {
|
my ($mref, $inref) = @_;
|
my ($mref, $inref) = @_;
|
my @models = get_model_names(@_);
|
my @models = get_model_names(@_);
|
|
|
|
|
mkdir("$work", 0700);
|
mkdir("$work", 0700);
|
foreach my $m (@models){
|
foreach my $m (@models){
|
print "$m\n";
|
print "$m\n";
|
|
unless (-f $m ){
|
|
die "Error: no such file $m";
|
|
}
|
#make noc localparam
|
#make noc localparam
|
my $o;
|
my $o;
|
$o= do $m;
|
$o= do $m;
|
die "Error reading: $@" if $@;
|
die "Error reading: $@" if $@;
|
my $param = $o->{'noc_param'};
|
my $param = $o->{'noc_param'};
|
my ($fname,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
my ($fname,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
|
|
|
|
my $name = $fname;
|
my $name = $fname;
|
my $make =$o->{'makefile'};
|
my $make =$o->{'makefile'};
|
|
|
|
|
my ($param_v,$include_h,$tops)= gen_noc_localparam_v( $o,$param);
|
my ($param_v,$include_h,$tops)= gen_noc_localparam_v( $o,$param);
|
|
|
mkdir("$work/$name", 0700);
|
mkdir("$work/$name", 0700);
|
rmtree("$work/$name/obj_dir");
|
rmtree("$work/$name/obj_dir");
|
mkdir("$work/$name/obj_dir", 0700);
|
mkdir("$work/$name/obj_dir", 0700);
|
save_file("$work/$name/noc_localparam.v",$param_v);
|
save_file("$work/$name/noc_localparam.v",$param_v);
|
|
|
#generate file list
|
#generate file list
|
gen_file_list("$work/$name");
|
gen_file_list("$work/$name");
|
gen_verilator_sh($tops,"$work/$name/verilator.sh");
|
gen_verilator_sh($tops,"$work/$name/verilator.sh");
|
|
|
|
|
|
|
#copy C files
|
#copy C files
|
my @files = File::Find::Rule->file()
|
my @files = File::Find::Rule->file()
|
->name( '*.h' )
|
->name( '*.h' )
|
->in( "$src_verilator" );
|
->in( "$src_verilator" );
|
foreach my $p (@files){
|
foreach my $p (@files){
|
copy $p, "$work/$name/obj_dir/";
|
copy $p, "$work/$name/obj_dir/";
|
}
|
}
|
copy "$src_verilator/simulator.cpp", "$work/$name/obj_dir/testbench.cpp";
|
copy "$src_verilator/simulator.cpp", "$work/$name/obj_dir/testbench.cpp";
|
|
|
#copy nettrace & synful
|
#copy nettrace & synful
|
dircopy("$src_c/netrace-1.0","$work/$name/obj_dir/netrace-1.0");
|
dircopy("$src_c/netrace-1.0","$work/$name/obj_dir/netrace-1.0");
|
dircopy("$src_c/synfull","$work/$name/obj_dir/synful");
|
dircopy("$src_c/synfull","$work/$name/obj_dir/synful");
|
|
|
|
|
|
|
|
|
#generate make file
|
#generate make file
|
gen_verilator_makefile($tops,"$work/$name/obj_dir/Makefile");
|
gen_verilator_makefile($tops,"$work/$name/obj_dir/Makefile");
|
#generate param.h file
|
#generate param.h file
|
|
|
|
|
save_file("$work/$name/obj_dir/parameter.h",$include_h);
|
save_file("$work/$name/obj_dir/parameter.h",$include_h);
|
|
|
|
|
}
|
}
|
|
|
}
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
sub compile_models{
|
sub compile_models{
|
my($self,$inref,$mref)=@_;
|
my($self,$inref,$mref)=@_;
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
|
|
|
|
my @models = get_model_names($mref,$inref);
|
my @models = get_model_names($mref,$inref);
|
|
|
#generate compile command
|
#generate compile command
|
my $i=0;
|
my $i=0;
|
my $cmd;
|
my $cmd;
|
foreach my $m (@models){
|
foreach my $m (@models){
|
my ($fname,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
my ($fname,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
$cmd.=" cd $work/$fname; bash verilator.sh > $work/$fname/out.log 2>&1 &\n";
|
$cmd.=" cd $work/$fname; bash verilator.sh > $work/$fname/out.log 2>&1 &\n";
|
$i++;
|
$i++;
|
$cmd.="wait\n" if(($i % $paralel_run)==0) ;
|
$cmd.="wait\n" if(($i % $paralel_run)==0) ;
|
}
|
}
|
$cmd.="wait\n" if(($i % $paralel_run)!=0) ;
|
$cmd.="wait\n" if(($i % $paralel_run)!=0) ;
|
#run command in terminal
|
#run command in terminal
|
print "*******************compile models******************\n$cmd\n";
|
print "*******************compile models******************\n$cmd\n";
|
my $proc1 = Proc::Background->new($cmd);
|
my $proc1 = Proc::Background->new($cmd);
|
$proc1->alive;
|
$proc1->alive;
|
$proc1->wait;
|
$proc1->wait;
|
$proc1->die;
|
$proc1->die;
|
|
|
}
|
}
|
sub check_compilation_log {
|
sub check_compilation_log {
|
my ($name,$ref,$inref) = @_;
|
my ($name,$ref,$inref) = @_;
|
my @log_report_match =@{$ref};
|
my @log_report_match =@{$ref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my $logfile = "$work/$name/out.log";
|
my $logfile = "$work/$name/out.log";
|
|
|
my @found;
|
my @found;
|
foreach my $m (@log_report_match){
|
foreach my $m (@log_report_match){
|
open my $INPUT, '<', $logfile;
|
open my $INPUT, '<', $logfile;
|
push(@found , grep ( /$m/, <$INPUT>)) ;
|
push(@found , grep ( /$m/, <$INPUT>)) ;
|
close($INPUT);
|
close($INPUT);
|
}
|
}
|
|
|
foreach my $line (@found) {
|
foreach my $line (@found) {
|
append_text_to_file($report,"\t $line\n");
|
append_text_to_file($report,"\t $line\n");
|
}
|
}
|
}
|
}
|
|
|
|
|
|
|
|
|
|
|
sub check_compilation {
|
sub check_compilation {
|
my ($self,$ref1,$inref,$mref)=@_;
|
my ($self,$ref1,$inref,$mref)=@_;
|
|
|
my @models = get_model_names($mref,$inref);
|
my @models = get_model_names($mref,$inref);
|
|
|
foreach my $m (@models){
|
foreach my $m (@models){
|
my ($name,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
my ($name,$fpath,$fsuffix) = fileparse("$m",qr"\..[^.]*$");
|
append_text_to_file($report,"****************************$name : Compile *******************************:\n");
|
append_text_to_file($report,"****************************$name : Compile *******************************:\n");
|
#check if testbench is generated successfully
|
#check if testbench is generated successfully
|
if(-f "$work/$name/obj_dir/testbench"){
|
if(-f "$work/$name/obj_dir/testbench"){
|
append_text_to_file($report,"\t model is generated successfully.\n");
|
append_text_to_file($report,"\t model is generated successfully.\n");
|
check_compilation_log($name,$ref1,$inref);
|
check_compilation_log($name,$ref1,$inref);
|
|
|
}else{
|
}else{
|
append_text_to_file($report,"\t model generation is FAILED.\n");
|
append_text_to_file($report,"\t model generation is FAILED.\n");
|
check_compilation_log($name,$ref1,$inref);
|
check_compilation_log($name,$ref1,$inref);
|
}
|
}
|
|
|
}
|
}
|
}
|
}
|
|
|
|
|
sub run_all_models {
|
sub run_all_models {
|
my ($self,$inref,$mref) =@_;
|
my ($self,$inref,$mref) =@_;
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my @models = get_model_names($mref,$inref);
|
my @models = get_model_names($mref,$inref);
|
foreach my $m (@models){
|
foreach my $m (@models){
|
run_traffic ($self,$m,'random',$inref);
|
run_traffic ($self,$m,'random',$inref);
|
}
|
}
|
foreach my $m (@models){
|
foreach my $m (@models){
|
run_traffic ($self,$m,'transposed 1',$inref);
|
run_traffic ($self,$m,'transposed 1',$inref);
|
}
|
}
|
}
|
}
|
|
|
|
|
|
|
sub run_traffic {
|
sub run_traffic {
|
my ($self,$model,$traffic,$inref)=@_;
|
my ($self,$model,$traffic,$inref)=@_;
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($name,$fpath,$fsuffix) = fileparse("$model",qr"\..[^.]*$");
|
my ($name,$fpath,$fsuffix) = fileparse("$model",qr"\..[^.]*$");
|
|
|
my %param = get_model_parameter($model);
|
my %param = get_model_parameter($model);
|
my $min_pck = $param{'MIN_PCK_SIZE'};
|
my $min_pck = $param{'MIN_PCK_SIZE'};
|
|
|
append_text_to_file($report,"****************************$name : $traffic traffic *******************************:\n");
|
append_text_to_file($report,"****************************$name : $traffic traffic *******************************:\n");
|
unless (-f "$work/$name/obj_dir/testbench"){
|
unless (-f "$work/$name/obj_dir/testbench"){
|
append_text_to_file($report,"\t failed. Simulation model is not avaialable\n");
|
append_text_to_file($report,"\t failed. Simulation model is not avaialable\n");
|
return;
|
return;
|
}
|
}
|
|
|
|
|
|
|
my $file_name="${traffic}_results";
|
my $file_name="${traffic}_results";
|
$file_name =~ s/\s+//g;
|
$file_name =~ s/\s+//g;
|
|
|
mkdir("$work/$name/$file_name/", 0700);
|
mkdir("$work/$name/$file_name/", 0700);
|
|
|
my $i=0;
|
my $i=0;
|
my $cmd;
|
my $cmd;
|
|
|
|
|
for (my $inject=$MIN; $inject<=$MAX; $inject+=$STEP){
|
for (my $inject=$MIN; $inject<=$MAX; $inject+=$STEP){
|
$cmd.="$work/$name/obj_dir/testbench -t \"$traffic\" -m \"R,$min_pck,10\" -n 20000 -c 10000 -i $inject -p \"100,0,0,0,0\" > $work/$name/$file_name/sim$inject 2>&1 &\n";
|
$cmd.="$work/$name/obj_dir/testbench -t \"$traffic\" -m \"R,$min_pck,10\" -n 20000 -c 10000 -i $inject -p \"100,0,0,0,0\" > $work/$name/$file_name/sim$inject 2>&1 &\n";
|
$i++;
|
$i++;
|
$cmd.="wait\n" if(($i % $paralel_run)==0) ;
|
$cmd.="wait\n" if(($i % $paralel_run)==0) ;
|
}
|
}
|
$cmd.="wait\n" if(($i % $paralel_run)!=0) ;
|
$cmd.="wait\n" if(($i % $paralel_run)!=0) ;
|
#run command in terminal
|
#run command in terminal
|
print "*******************run models******************\n$cmd\n";
|
print "*******************run models******************\n$cmd\n";
|
my $proc1 = Proc::Background->new($cmd);
|
my $proc1 = Proc::Background->new($cmd);
|
$proc1->alive;
|
$proc1->alive;
|
$proc1->wait;
|
$proc1->wait;
|
$proc1->die;
|
$proc1->die;
|
|
|
check_sim_results($self,$name,$traffic,$inref);
|
check_sim_results($self,$name,$traffic,$inref);
|
|
|
}
|
}
|
|
|
|
|
sub extract_result {
|
sub extract_result {
|
my ($self,$file,$filed)=@_;
|
my ($self,$file,$filed)=@_;
|
|
|
my @r = unix_grep($file,$filed);
|
my @r = unix_grep($file,$filed);
|
my $string = $r[0];
|
my $string = $r[0];
|
$string =~ s/[^0-9.]+//g;
|
$string =~ s/[^0-9.]+//g;
|
return $string;
|
return $string;
|
|
|
}
|
}
|
|
|
sub get_zero_load_and_saturation{
|
sub get_zero_load_and_saturation{
|
my ($self,$name,$traffic,$path)=@_;
|
my ($self,$name,$traffic,$path)=@_;
|
my %results;
|
my %results;
|
my $ref = $self->{'name'}{"$name"}{'traffic'}{$traffic}{"packet_latency"};
|
my $ref = $self->{'name'}{"$name"}{'traffic'}{$traffic}{"packet_latency"};
|
return if !defined $ref;
|
return if !defined $ref;
|
%results = %{$ref};
|
%results = %{$ref};
|
|
|
my $zero_latency=9999999;
|
my $zero_latency=9999999;
|
my $saturat_inject=100;
|
my $saturat_inject=100;
|
my $zero_inject;
|
my $zero_inject;
|
my $saturat_latency='-';
|
my $saturat_latency='-';
|
|
|
my $txt = "#name:$name\n";
|
my $txt = "#name:$name\n";
|
|
|
foreach my $inj (sort {$a <=> $b} keys %results){
|
foreach my $inj (sort {$a <=> $b} keys %results){
|
$txt.="$inj $results{$inj}\n";
|
$txt.="$inj $results{$inj}\n";
|
if ($zero_latency > $results{$inj}) {
|
if ($zero_latency > $results{$inj}) {
|
$zero_latency = $results{$inj};
|
$zero_latency = $results{$inj};
|
$zero_inject = $inj;
|
$zero_inject = $inj;
|
}
|
}
|
}
|
}
|
# assum saturation happens when the latency is 5 times of zero load
|
# assum saturation happens when the latency is 5 times of zero load
|
foreach my $inj (sort {$a <=> $b} keys %results){
|
foreach my $inj (sort {$a <=> $b} keys %results){
|
if($results{$inj} >= 5 * $zero_latency ) {
|
if($results{$inj} >= 5 * $zero_latency ) {
|
if($saturat_inject > $inj){
|
if($saturat_inject > $inj){
|
$saturat_inject =$inj;
|
$saturat_inject =$inj;
|
$saturat_latency=$results{$inj};
|
$saturat_latency=$results{$inj};
|
}
|
}
|
}
|
}
|
}
|
}
|
$txt.="\n";
|
$txt.="\n";
|
save_file("$path/packet_latency.sv",$txt);
|
save_file("$path/packet_latency.sv",$txt);
|
|
|
|
|
return ($zero_inject,$zero_latency, $saturat_inject,$saturat_latency);
|
return ($zero_inject,$zero_latency, $saturat_inject,$saturat_latency);
|
}
|
}
|
|
|
|
|
|
|
|
|
sub check_sim_results{
|
sub check_sim_results{
|
my ($self,$name,$traffic,$inref)=@_;
|
my ($self,$name,$traffic,$inref)=@_;
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my ($paralel_run,$MIN,$MAX,$STEP) = @{$inref};
|
my $file_name="${traffic}_results";
|
my $file_name="${traffic}_results";
|
$file_name =~ s/\s+//g;
|
$file_name =~ s/\s+//g;
|
my $results_path = "$work/$name/$file_name";
|
my $results_path = "$work/$name/$file_name";
|
|
|
#my @results = glob("$results_path/*");
|
#my @results = glob("$results_path/*");
|
#check for error
|
#check for error
|
|
|
for (my $inject=$MIN; $inject<=$MAX; $inject+=$STEP){
|
for (my $inject=$MIN; $inject<=$MAX; $inject+=$STEP){
|
my $file = "$results_path/sim$inject";
|
my $file = "$results_path/sim$inject";
|
|
|
my @errors = unix_grep("$file","ERROR:");
|
my @errors = unix_grep("$file","ERROR:");
|
if (scalar @errors ){
|
if (scalar @errors ){
|
append_text_to_file($report,"\t Error in running simulation: @errors \n");
|
append_text_to_file($report,"\t Error in running simulation: @errors \n");
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="Failed";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="Failed";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'message'}="@errors";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'message'}="@errors";
|
return;
|
return;
|
}
|
}
|
my @r = unix_grep($file,"\ttotal,");
|
my @r = unix_grep($file,"\ttotal,");
|
my $string = $r[0];
|
my $string = $r[0];
|
my @fileds=split(',',$string);
|
my @fileds=split(',',$string);
|
my $val=$fileds[11];
|
my $val=$fileds[11];
|
$val =~ s/[^0-9.]+//g;
|
$val =~ s/[^0-9.]+//g;
|
# my $val = extract_result($self,$file,"average packet latency");
|
# my $val = extract_result($self,$file,"average packet latency");
|
if(length $val ==0){
|
if(length $val ==0){
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="Failed";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="Failed";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'message'}="The average packet latency is undefined for $inject";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'message'}="The average packet latency is undefined for $inject";
|
return;
|
return;
|
}
|
}
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{"packet_latency"}{$inject}="$val";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{"packet_latency"}{$inject}="$val";
|
|
|
}
|
}
|
my ($z,$zl, $s,$sl) = get_zero_load_and_saturation ($self,$name,$traffic,$results_path);
|
my ($z,$zl, $s,$sl) = get_zero_load_and_saturation ($self,$name,$traffic,$results_path);
|
print "($z,$zl, $s,$sl)\n";
|
print "($z,$zl, $s,$sl)\n";
|
|
|
#save results in a text file
|
#save results in a text file
|
|
|
|
|
|
|
append_text_to_file($report,"\t Passed: zero load ($z,$zl) saturation ($s,$sl)\n");
|
append_text_to_file($report,"\t Passed: zero load ($z,$zl) saturation ($s,$sl)\n");
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="passed";
|
$self->{'name'}{"$name"}{'traffic'}{$traffic}{'overal_result'}="passed";
|
}
|
}
|
|
|
|
|
No newline at end of file
|
No newline at end of file
|
|
sub object_get_attribute_order{
|
|
my ($self,$attribute)=@_;
|
|
return unless(defined $self->{parameters_order}{$attribute});
|
|
my @order=@{$self->{parameters_order}{$attribute}};
|
|
return uniq(@order)
|
|
}
|
|
|
|
sub save_file {
|
|
my ($file_path,$text)=@_;
|
|
open my $fd, ">$file_path" or die "could not open $file_path: $!";
|
|
print $fd $text;
|
|
close $fd;
|
|
}
|
|
|
|
sub object_add_attribute_order{
|
|
my ($self,$attribute,@param)=@_;
|
|
my $r = $self->{'parameters_order'}{$attribute};
|
|
my @a;
|
|
@a = @{$r} if(defined $r);
|
|
push (@a,@param);
|
|
@a=uniq(@a);
|
|
$self->{'parameters_order'}{$attribute} =\@a;
|
|
}
|
|
|
|
sub append_text_to_file {
|
|
my ($file_path,$text)=@_;
|
|
open(my $fd, ">>$file_path") or die "could not open $file_path: $!";
|
|
print $fd $text;
|
|
close $fd;
|
|
}
|
|
|
|
sub object_add_attribute{
|
|
my ($self,$attribute1,$attribute2,$value)=@_;
|
|
if(!defined $attribute2){$self->{$attribute1}=$value;}
|
|
else {$self->{$attribute1}{$attribute2}=$value;}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub object_get_attribute{
|
|
my ($self,$attribute1,$attribute2)=@_;
|
|
if(!defined $attribute2) {return $self->{$attribute1};}
|
|
return $self->{$attribute1}{$attribute2};
|
|
}
|
|
|
|
sub powi{ # x^y
|
|
my ($x,$y)=@_; # compute x to the y
|
|
my $r=1;
|
|
for (my $i = 0; $i < $y; ++$i ) {
|
|
$r *= $x;
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
sub sum_powi{ # x^(y-1) + x^(y-2) + ...+ 1;
|
|
my ($x,$y)=@_; # compute x to the y
|
|
my $r = 0;
|
|
for (my $i = 0; $i < $y; $i++){
|
|
$r += powi( $x, $i );
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
sub log2{
|
|
my $num=shift;
|
|
my $log=($num <=1) ? 1: 0;
|
|
while( (1<< $log) < $num) {
|
|
$log++;
|
|
}
|
|
return $log;
|
|
}
|
|
|
|
|
|
sub remove_not_hex {
|
|
my $s=shift;
|
|
$s =~ s/[^0-9a-fA-F]//g;
|
|
return $s;
|
|
}
|
|
|
|
sub remove_not_number {
|
|
my $s=shift;
|
|
$s =~ s/[^0-9]//g;
|
|
return $s;
|
|
|
|
}
|
|
|
|
sub check_file_has_string {
|
|
my ($file,$string)=@_;
|
|
my $r;
|
|
open(FILE,$file);
|
|
if (grep{/$string/} <FILE>){
|
|
$r= 1; #print "word found\n";
|
|
}else{
|
|
$r= 0; #print "word not found\n";
|
|
}
|
|
close FILE;
|
|
return $r;
|
|
}
|
|
|
|
|
|
sub gen_verilator_makefile{
|
|
my ($top_ref,$target_dir) =@_;
|
|
my %tops = %{$top_ref};
|
|
my $p='';
|
|
my $q='';
|
|
my $h='';
|
|
my $l;
|
|
my $lib_num=0;
|
|
my $all_lib="";
|
|
foreach my $top (sort keys %tops) {
|
|
$p = "$p ${top}__ALL.a ";
|
|
$q = $q."lib$lib_num:\n\t\$(MAKE) -f ${top}.mk\n";
|
|
$h = "$h ${top}.h ";
|
|
$l = $top;
|
|
$all_lib=$all_lib." lib$lib_num";
|
|
$lib_num++;
|
|
}
|
|
|
|
my $make= "
|
|
|
|
default: sim
|
|
|
|
|
|
|
|
include $l.mk
|
|
|
|
lib: $all_lib
|
|
|
|
$q
|
|
|
|
|
|
#######################################################################
|
|
# Compile flags
|
|
|
|
CPPFLAGS += -DVL_DEBUG=1
|
|
ifeq (\$(CFG_WITH_CCWARN),yes) # Local... Else don't burden users
|
|
CPPFLAGS += -DVL_THREADED=1
|
|
CPPFLAGS += -W -Werror -Wall
|
|
endif
|
|
|
|
SLIB =
|
|
HLIB =
|
|
ifneq (\$(wildcard synful/synful.a),)
|
|
SLIB += synful/synful.a
|
|
HLIB += synful/synful.h
|
|
endif
|
|
|
|
#######################################################################
|
|
# Linking final exe -- presumes have a sim_main.cpp
|
|
|
|
|
|
sim: testbench.o \$(VK_GLOBAL_OBJS) $p \$(SLIB)
|
|
\$(LINK) \$(LDFLAGS) -g \$^ \$(LOADLIBES) \$(LDLIBS) -o testbench \$(LIBS) -Wall -O3 -lpthread 2>&1 | c++filt
|
|
|
|
testbench.o: testbench.cpp $h \$(HLIB)
|
|
|
|
clean:
|
|
rm *.o *.a testbench
|
|
";
|
|
|
|
save_file ($target_dir,$make);
|
|
|
|
}
|
|
|
|
|
|
sub get_project_dir{ #mpsoc directory address
|
|
my $dir = Cwd::getcwd();
|
|
my @p= split('/perl_gui',$dir);
|
|
@p= split('/Integration_test',$p[0]);
|
|
my $d = abs_path("$p[0]/../");
|
|
|
|
return $d;
|
|
}
|
|
|
|
#return lines containig pattern in a givn file
|
|
sub unix_grep {
|
|
my ($file,$pattern)=@_;
|
|
open(FILE,$file);
|
|
my @arr = <FILE>;
|
|
my @lines = grep /$pattern/, @arr;
|
|
return @lines;
|
|
}
|
|
|
|
|
|
sub regen_object {
|
|
my $path=shift;
|
|
$path = get_full_path_addr($path);
|
|
my $pp= eval { do $path };
|
|
my $r= ($@ || !defined $pp);
|
|
return ($pp,$r,$@);
|
|
}
|
|
|
|
sub get_full_path_addr{
|
|
my $file=shift;
|
|
my $dir = Cwd::getcwd();
|
|
my $full_path = "$dir/$file";
|
|
return $full_path if -f ($full_path );
|
|
return $file;
|
|
}
|
|
|
No newline at end of file
|
No newline at end of file
|