#!/usr/bin/perl -w
########################################################
#  This script was built to prepare the output of parseDbg.pl
# for the Chipsim storm stand alone configuration. This process is needed only
# for SEM-FAST special line RH (record handlers).
# more details can be found in the user manual.
#
########################################################


# Finstruct
# [5:0] start_rp_foc3; [11:6] start_rp_foc2;  [17:12] start_rp_foc1; [23:18] start_rp_foc0; [29:24] end_rp_foc3;
# [35:30] end_rp_foc2; [41:36] end_rp_foc1; [47:42] end_rp_foc0; [53:48] lowest rp; [59:54] highest rp; [65:60] store start rp;
# [71:66] store end rp; [77:72] load start rp; [83:78] load end rp; [85:84] priority; [101:86] pram address; [102] pas;
# [103] foc3; [104] foc2; [105] foc1; [106] foc0; [107] release; [108] fin;

use strict;
use English;
use warnings;
use File::Path;
use File::Basename;
use Cwd;
use Switch;
my $PB_TYPE = 7;
my %STORM_NAME_TO_NUM = ('T' => 0, 'X' => 1, 'U' => 2, 'C' => 3);
my %INTERFACES = ('IOR' => 1, 'GPRE' => 2, 'RAM' => 3, 'EXTLOAD' =>4,'PB' => 5, 'CAM' => 6,
		  'MISC' => 7);
my %TYPES= (0=>'FIC', 1=>'FIN', 2=>'LOAD', 3=>'CSwitch',4=>'STORE', 5=>'STORE',6=>'STORE',7=>'PB');
my %SIZE= ('LOAD'=> 3, 'FIN'=>4, 'CSwitch'=>1, 'STORE' => 3);
my %INT_ADDR = (1 => 'EXTLOAD',2=>'CAM',3=> 'MISC', 4=>'RAM0', 5=>'RAM1');
my @STORM_NAMES= ('TSTORM','XSTORM','USTORM','CSTORM');

my @SET_THREAD;
my @ACTIVE_SET;

my @DRA;
my @DRA_LEN;

my $MEM_FILE;
my $OUTPUT_FILE;

# used as a two dimensions arrays, first dimension is the storm according to the storm number (see above STORM_NAME_TO NUM) and second is the thread number.
# when a hanlder is thread less is thread index is 16+set_id.
# data is for the standalone output and fmt is for the fmt output.
# repeated is for represnting a thread which return twice without fin (must be an error - if it happened once it might be because a filter was initiated).
my @DATA_PER_STORM;
my @FMT_PER_STORM;
my @REPEATED_PER_STORM;
my $LINE;
my $MODE;
my $LASTHANDLER="";
my $HANDLER_COUNTER=0;
my $MSGC=-1;
my $NORH=0;

# Used for mode 6 (Recording Handlers)
# The SEMI output in every cycle 64 bit of data and 64 bit of address (the addr is actually the 13 lsb bits [12:0]).
# The script seperate each 2 lines to one message for the merge script.
# the output is "DATA <storm_letter>STORM_WR_BUS" "<address>(16 bit - 3 zero +address)) <DATA> <byte_enable all ones>.
sub read_cur_msg {
    my ($lastLine)=@_;
    my ($sem_letter,$sem_num);
    my ($header,$type);
    my @lines;
    
    ## DEBUG DATA to recognize the problematic msg
    $MSGC++;
    ###########\
    
    $lastLine=~/\s(.)STORM:/;
    $sem_letter=$1;
    $sem_num=$STORM_NAME_TO_NUM{$sem_letter};
    if (!defined($DRA[$sem_num])) {
            my @empty;
           $DRA[$sem_num]=\@empty;
    }
    my $draRef=$DRA[$sem_num];
    
    while (<SRC>) {
	chop($_);
        if ($_ eq "") {
	    last;
	}
	@lines=(@lines,$_);
    }
    $header=$lines[$#lines];    
    $type=ord(pack("B8",("0"x4).substr($header,0,4)));
    ## DEBUG - to recognize wrong types
    die "Wrong type for msg $MSGC, input data has errors.\n" unless ($type<$PB_TYPE);
    #####
    
    if ('FIC' eq $TYPES{$type})	{
        @lines=(@$draRef,@lines);
        undef $DRA[$sem_num];     
        if (substr($lines[0],11,1)) {
            my $thread=substr($lines[0],0,8); 
            if (defined($DATA_PER_STORM[$sem_num][ord(pack("B8",substr($lines[0],0,8)))]{'pbLdEnd'})) {
                    $DRA_LEN[$sem_num]=$DATA_PER_STORM[$sem_num][ord(pack("B8",substr($lines[0],0,8)))]{'pbLdEnd'}*2+2;
            } else {
                $DRA_LEN[$sem_num]=$#lines;
                if (!$NORH) {
                    $type=-1;
                    # DEBUG - problematic spots
                    #print STDERR "PB $MSGC\n$FIRST\n";
                    ###########
                }
            }
	} else {
            $DRA_LEN[$sem_num]=ord(pack("B8",substr($lines[0],16,8)))*2;
            if ($#lines-$DRA_LEN[$sem_num]>2 || $#lines<$DRA_LEN[$sem_num]) {
                $type=-1;
                #print STDERR "FIC size: ".$#lines." and dra_len: ".$DRA_LEN[$sem_num]." for storm $sem_letter $MSGC\n";
            }
        }
      
        splice(@lines,$DRA_LEN[$sem_num],$#lines-$DRA_LEN[$sem_num]);
        
        undef $DRA_LEN[$sem_num];   

    } else {
	if ($#lines>=$SIZE{$TYPES{$type}}) {
	  
           my @tmpArr=splice(@lines,0,$#lines+1-$SIZE{$TYPES{$type}});
           @$draRef=(@$draRef,@tmpArr);

	} elsif ($#lines+1!=$SIZE{$TYPES{$type}}) {
            #die "The data includes messages with the wrong size" unless !defined($DATA_PER_STORM[$sem_num][255]);
            return (-1,-1,@lines);
        }
    }
    
    if ($type==0 && substr($lines[0],11,1)) {
		$type=$PB_TYPE;
    }
 
    
    return ($sem_letter,$type,@lines);
	
}

sub patch {
    my ($sem_letter,$type,@lines)=@_;
    my $thread;
    my $i;
    my $sem_num=$STORM_NAME_TO_NUM{$sem_letter};
    my $onlyFmt=0;
        
    if ($type==-1) {
        #print STDERR #"type is -1 for ".join("\n",@lines).
		#"\n$HANDLER_COUNTER $MSGC\n";
	return;
    }
    if (defined($ACTIVE_SET[$sem_num])) {
	$thread=$SET_THREAD[$sem_num][$ACTIVE_SET[$sem_num]];
        
        #print STDERR "$MSGC type: $TYPES{$type} thread: $thread\n";
    } elsif (!($TYPES{$type} eq 'FIC' || $TYPES{$type} eq 'CSwitch' || $TYPES{$type} eq 'PB')) {
	$onlyFmt=1;
    }
    
    switch ($TYPES{$type}) {
	case ('FIC')	{           my $header=pop(@lines);
                                    my $set=substr($header,31,1);
                                    $thread=ord(pack("B8",substr($lines[0],0,8)));
                                    
                                    if ($thread==255) {
                                        $thread=16+$set;
                                    }
                                    # bit [31] is the set bit
									
				    $SET_THREAD[$sem_num][$set]=$thread;
                                    
                                    if (defined($FMT_PER_STORM[$sem_num][$thread]) && !$NORH) {
										if (!defined($REPEATED_PER_STORM[$sem_num][$thread])) {
											print "# This handler was only partly recored (probably because of activating filter during its run\n";
											print "# doesn't exists in the chipsim\n";
											$REPEATED_PER_STORM[$sem_num][$thread][0]=$FMT_PER_STORM[$sem_num][$thread]{'count'};
											$REPEATED_PER_STORM[$sem_num][$thread][1]=$HANDLER_COUNTER+1;
											print $FMT_PER_STORM[$sem_num][$thread]{'data'};
											delete $FMT_PER_STORM[$sem_num][$thread]{'data'};
										} else {
											print "# This handler was only partly recorded and this is the second time a theard is being override without being release\n";
											print "# You probably have some error in the record data.\n";
											print "# Before handler $REPEATED_PER_STORM[$sem_num][$thread][1] run over $REPEATED_PER_STORM[$sem_num][$thread][0]\n";
											print "# And now handler ".($HANDLER_COUNTER+1)." override $FMT_PER_STORM[$sem_num][$thread]{'count'}\n";
											print $FMT_PER_STORM[$sem_num][$thread]{'data'};
											delete $FMT_PER_STORM[$sem_num][$thread]{'data'};
											
											print "# The last overriding handler is:\n";
											print "DATA $sem_letter"."SEMI_FIC0 # handler ".($HANDLER_COUNTER+1)."\n";
											print join("\n",@lines)."\n\n";
											die "A thread was overrided twice without being released. check fmt file for more details.";
										}
				    }
									
									#### debug
                                        #print STDERR "A thread has started again without being released, in handler ".($HANDLER_COUNTER+1)." msg $MSGC running over
                                        #$FMT_PER_STORM[$sem_num][$thread]{'count'}"
                                        #unless !defined($FMT_PER_STORM[$sem_num][$thread]) || $NORH;
                                        #print STDERR $FMT_PER_STORM[$sem_num][$thread]{'data'}
                                        #unless !defined($FMT_PER_STORM[$sem_num][$thread]) || $NORH;
                                        #print STDERR join("\n",@lines)
                                        #unless !defined($FMT_PER_STORM[$sem_num][$thread]) || $NORH;
										#print $FMT_PER_STORM[$sem_num][$thread]{'data'} 
										#unless !defined($FMT_PER_STORM[$sem_num][$thread]) || $NORH;;
                                        #die "END" unless !defined($FMT_PER_STORM[$sem_num][$thread]) || $NORH;
                                    #############
                                    
                                    $FMT_PER_STORM[$sem_num][$thread]{'count'}=++$HANDLER_COUNTER;
                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}="DATA $sem_letter"."SEMI_FIC0 # handler $HANDLER_COUNTER\n";
                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}.=join("\n",@lines)."\n\n";
				    # add comment before handler
				    if ($NORH) {
                                        print $FMT_PER_STORM[$sem_num][$thread]{'data'};
                                        delete $FMT_PER_STORM[$sem_num][$thread]{'data'};
                                        return;
                                    }
				    my $event_id=ord(pack("B8",substr($lines[0],24,8)));
                                    
                                    $DATA_PER_STORM[$sem_num][$thread]{'data'}="comment handler $HANDLER_COUNTER\ninvoke $sem_num $event_id\n\n";
				    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="data $INTERFACES{'IOR'} 0\n";
				    
                                    for ($i=0;$i<=$#lines;$i+=2) {
					$DATA_PER_STORM[$sem_num][$thread]{'data'}.=unpack("H8",pack("B32",$lines[$i+1])).unpack("H8",pack("B32",$lines[$i]))."\t\t".("F"x16)."\n";
				    }
                                    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
				    #print ord(pack("B8",("0"x4).substr($lines[$#lines],24,4)))."\naaa\n";
				    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="\ndata $INTERFACES{'GPRE'} 0\n".
					("0"x15).ord(pack("B8",("0"x4).substr($header,24,4)))."\t".("0"x15)."F\n\n";				    ;
				    
				    
                        }   
	case ('LOAD')	{
				    my $offset=unpack("n",pack("B16",("0"x3).substr($lines[$#lines],19)));
				    my $interface=ord(pack("B8",("0"x5).substr($lines[$#lines],16,3)));
				    my $data=unpack("H16",pack("B64",$lines[1].$lines[0]));
                                    my $comment;
                                    if ($onlyFmt) {
                                        $comment=" # load for partial handler -doesn't exists in the chipsim";
                                    } else {
                                        die "load after fin" unless defined $FMT_PER_STORM[$sem_num][$thread]{'count'};
                                        $comment=" # handler $FMT_PER_STORM[$sem_num][$thread]{'count'}"
                                    }
                                    my $fmt_data="DATA $sem_letter"."STORM_RD_BUS $comment\n".$lines[1].$lines[0]."\t".substr($lines[$#lines],16)."\n\n";
                                    if ($onlyFmt) {
                                        print "$fmt_data";
                                        return;
                                    }
        
                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}.=$fmt_data;
                                    
				    die "load from interface 0, is an unknown interface, check the original code"
					unless $interface;
				    if (($INT_ADDR{$interface})=~/RAM(\d)/) {
										#we should fix the offset, since in the chipsim, the rams are one after the other
										if ($1 == 1) {
											$offset = $offset + 0x2000;
										}
                                        if (!defined($DATA_PER_STORM[$sem_num][$thread]{'LOADS'}{$offset})) {
                                            $DATA_PER_STORM[$sem_num][$thread]{'LOADS'}{$offset}=$data;
                                        }
				    } else {
					$DATA_PER_STORM[$sem_num][$thread]{'data'}.="data $INTERFACES{$INT_ADDR{$interface}} $offset\n".
					$data."\t\t".("F"x16)."\n\n";
				    }
				}
	case ('FIN')	{           ### COMBINING DRA_IN WITH FIN..... ######
				    my $fin=substr($lines[$#lines],19).$lines[2].$lines[1].$lines[0];
                                    
                                    my $comment;
                                    if ($onlyFmt) {
                                        $comment=" # fin for partial handler - doesn't exists in the chipsim - thread is not valid";
                                    } else {
                                        $comment=" # handler $FMT_PER_STORM[$sem_num][$thread]{'count'}"
                                    }
                                    if (!defined($thread)) {
                                        $thread=0xf;
                                    }
                                    my $thread_bin=unpack("B4",pack("n",$thread));
                                    my $fmt_data="DATA $sem_letter"."SEMI_FIN_COMMAND$comment\n".$fin."\t".$thread_bin."\n\n";
                                    if ($onlyFmt) {
                                        print "$fmt_data";
                                        return;
                                    }
                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}.=$fmt_data;
				    if (!(substr($fin,0,1))) {
					return;
				    }
					my $hash=$DATA_PER_STORM[$sem_num][$thread]{'LOADS'};
					my $last=-16;

                                        foreach my $offset (sort { $a <=> $b } keys(%$hash)) {
                                                if ($offset-1!=$last) {
                                                    if ($last!=-16) {
                                                        $DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
                                                    }
                                                    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="data $INTERFACES{'RAM'} $offset\n"
                                                } 
                                                $DATA_PER_STORM[$sem_num][$thread]{'data'}.=$$hash{$offset}."\t\t".("F"x16)."\n";
                                                $last=$offset;
					}
                    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
				    
				    
				    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="sync\n\n";
				    delete $DATA_PER_STORM[$sem_num][$thread]{'LOADS'};
                                    delete $SET_THREAD[$sem_num][$ACTIVE_SET[$sem_num]];
				    delete $ACTIVE_SET[$sem_num];
				    # if the fin is fin release then bit [107] is 1.
				    
                                    if (substr($fin,1,1) || $thread>15)
				    {
                                        print OUT $DATA_PER_STORM[$sem_num][$thread]{'data'};
                                        print $FMT_PER_STORM[$sem_num][$thread]{'data'};
                                        delete $FMT_PER_STORM[$sem_num][$thread];
                                        delete $DATA_PER_STORM[$sem_num][$thread];
				    } else {
					$DATA_PER_STORM[$sem_num][$thread]{'pbStEnd'}=ord(pack("B8","00".substr($fin,37,6)));
					$DATA_PER_STORM[$sem_num][$thread]{'pbLdEnd'}=ord(pack("B8","00".substr($fin,25,6)));
				    }
					
				}
	case ('CSwitch')	{
                                    
				    my $set=substr($lines[0],15,1);
                                    my $pram=unpack("H4",pack("B16",("0"x2).substr($lines[0],18,14)));
                                    my $low_high=substr($lines[0],17,1);
                                    if ($low_high) {
                                        $low_high="high";
                                    } else {
                                        $low_high="low";
                                    }
				    if (!(defined($SET_THREAD[$sem_num][$set])) && (!$NORH)) {
					return;
				    }
                                    if ($NORH) {
                                        print "# ".$sem_letter."STORM woke up on pram address: $pram $low_high\n\n";
                                        return;
                                    }
				    
                                    $ACTIVE_SET[$sem_num]=$set;
                                    $thread=$SET_THREAD[$sem_num][$ACTIVE_SET[$sem_num]];

                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}.="# handler $FMT_PER_STORM[$sem_num][$thread]{'count'} woke up on pram address: $pram $low_high\n\n";
                                    
				}
	case ('PB')	{
				    
                                    my $header=pop(@lines);
                                    $thread=ord(pack("B8",substr($lines[0],0,8)));
                                    if ((!$NORH) && !defined($DATA_PER_STORM[$sem_num][$thread])) {
                                        $onlyFmt=1;

                                    } 
                                    my $comment;
                                    if ($NORH) {
                                        $comment=" # Passive Buffer";
                                    } elsif ($onlyFmt) {
                                        $comment=" # PB for partial handler - doesn't exists in the chipsim";
                                    } else {
                                        $comment=" # handler $FMT_PER_STORM[$sem_num][$thread]{'count'}"
                                    }
                                    my $fmt_data="DATA $sem_letter"."storm_PASSIVE_BUFFER_ENTRY $comment\n".join("\n",@lines)."\n\n";
                                    if ($onlyFmt || $NORH) {
                                        print $fmt_data;
                                        return;
                                    } else {
                                        $FMT_PER_STORM[$sem_num][$thread]{'data'}.=$fmt_data;
                                    }
				    
                                    # add comment before handler
				    #my $event_id=unpack("N*",pack("B8",substr($lines[0],56,8)));
				    #$DATA_PER_STORM[$sem_num][$thread]{'data'}="invoke $sem_num $event_id\n\n";
				    my $start=$DATA_PER_STORM[$sem_num][$thread]{'pbStEnd'}+1;
                                    if (($start*2-1)!=$#lines) {
                                        $DATA_PER_STORM[$sem_num][$thread]{'data'}.="data $INTERFACES{'PB'} $start\n";
                                        for ($i=($start*2);$i<$#lines;$i+=2) {
                                            $DATA_PER_STORM[$sem_num][$thread]{'data'}.=unpack("H8",pack("B32",$lines[$i+1])).unpack("H8",pack("B32",$lines[$i]))."\t\t".("F"x16)."\n";
                                        }
                                        $DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
                                    }
				    # bit [31] is the set bit
				    $SET_THREAD[$sem_num][substr($header,31,1)]=$thread;
				    delete $DATA_PER_STORM[$sem_num][$thread]{'pbStEnd'};
				    delete $DATA_PER_STORM[$sem_num][$thread]{'pbLdEnd'};
				}
        case ('STORE')          {
                                    #print STDERR "store in $HANDLER_COUNTER\n";
                                    my $address=substr($lines[$#lines],16);
				    my $data=$lines[1].$lines[0];
                                    my $byteEnable=substr($lines[$#lines],8,8);
                                    my $comment;
                                    if ($onlyFmt) {
                                        $comment=" # store for partial handler -doesn't exists in the chipsim";
                                    } else {
                                        $comment=" # handler $FMT_PER_STORM[$sem_num][$thread]{'count'}"
                                    }
                                    my $fmt_data="DATA $sem_letter"."STORM_WR_BUS $comment\n".$address."\t".$data."\t".$byteEnable."\n\n";
                                    if ($onlyFmt) {
                                        print "$fmt_data";
                                        return;
                                    }
                                    $FMT_PER_STORM[$sem_num][$thread]{'data'}.=$fmt_data;
                                }
    }
}

sub print_printf {
    my ($storm)=@_;
    my @lines;
    while (<SRC>) {
	chop($_);
        if ($_ eq "") {
	    last;
	}
	@lines=(@lines,$_);
    }
    
    if ($#lines<1) {
        print "DATA ".$storm."_UNDEF_PRINTF_MESSAGE\n";
    } else {
        print "DATA ".$storm."_PRINTF_MESSAGE\n";
    }
    print join("\n",@lines);
    print "\n\n";
    
}
    
#################### MAIN ####################

#get command line parameters
die "Format: $PROGRAM_NAME <input file (rh.txt)> <output file (output.gen)> | -c <line name>\n"
    unless ($#ARGV == 1);

$MEM_FILE = $ARGV[0];
$OUTPUT_FILE=$ARGV[1];
if ($ARGV[0] eq "-c") {
    if ($ARGV[1]=~/STORM:(Handler_Rec_with_store|Store)/) {
        exit(3);
    } else {
        exit;
    }
}
my $line;
#open source file
die "Cannot open input file file: $MEM_FILE"
	unless open(SRC, "$MEM_FILE");
die "Cannot open output file: $OUTPUT_FILE"
	unless open(OUT, ">$OUTPUT_FILE");
$DRA[0]=$DRA[1]=$DRA[2]=$DRA[3]=();
my $i=0;
# Identify the line and trigger the appropriate function
while (<SRC>)
{
	if ($_=~/STORM:(Handler_Rec)/){
      
	    patch(read_cur_msg($_));
        } elsif ($_=~/STORM:(Dra-in|Cswitch)/){
            $NORH=1;
	    patch(read_cur_msg($_));
        } elsif ($_=~/(.STORM):printf/) {
            print_printf($1);
        } else {
            print $_;
        }
        
}

for (my $sem_num=0;$sem_num<4;$sem_num++) {
    for (my $thread=0;$thread<18; $thread++) {
	if (defined($DATA_PER_STORM[$sem_num][$thread]{'data'})) {
	    die "Cannot open partial file for handler in storm $sem_num, thread $thread"
		unless open(PART,">".dirname($OUTPUT_FILE)."/".$STORM_NAMES[$sem_num]."_thread".$thread.".rh.partial");
		my $hash=$DATA_PER_STORM[$sem_num][$thread]{'LOADS'};
		my $last=-16;
    
		foreach my $offset (sort { $a <=> $b } keys(%$hash)) {
			if ($offset-1!=$last) {
			    if ($last!=-16) {
				$DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
			    }
			    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="data $INTERFACES{'RAM'} $offset\n"
			} 
			$DATA_PER_STORM[$sem_num][$thread]{'data'}.=$$hash{$offset}."\t\t".("F"x16)."\n";
			$last=$offset;
		}
		$DATA_PER_STORM[$sem_num][$thread]{'data'}.="\n";
	    
	
	    $DATA_PER_STORM[$sem_num][$thread]{'data'}.="sync\n\n";
	    print PART $DATA_PER_STORM[$sem_num][$thread]{'data'};
	    print "#partial handler from $STORM_NAMES[$sem_num] thread $thread\n";
	    print $FMT_PER_STORM[$sem_num][$thread]{'data'};
	    close(PART);
	}
    }
    
}
close(SRC);
close(OUT);
# handle partial handlers
#open(PART,"partial_handler.txt");

#close input file




                       #if (defined($DATA_PER_STORM[$sem_num][$thread]{'data'})) {
                                                    #    print STDERR $FMT_PER_STORM[11111];
                                                    #    print STDERR join("\n",@lines);
                                                    #    print STDERR "\nNOW comes the old data:\n";
                                                    #    die "SOMETHING IS WRONG in $HANDLER_COUNTER $FMT_PER_STORM[$sem_num][$thread]{'data'}".(tell SRC)."\n";
                                                    #}
                             