#!/usr/bin/perl -w

# exe2memfile.pl
# David_Harris@hmc.edu 26 November 2020
# Converts an executable file to a series of 32-bit hex instructions
# to read into a Verilog simulation with $readmemh

use File::stat;
use IO::Handle;

if ($#ARGV == -1) {
    die("Usage: $0 executable_file");
}

# array to hold contents of memory file
my @memfilebytes = (0)*16384*4;
my $maxaddress = 0;

STDOUT->autoflush(1);
# *** Ross Thompson I think there is a bug here needs to be +1
print ("Processing $#ARGV memfiles: \n");
my $frac = $#ARGV/10;
for(my $i=0; $i<=$#ARGV; $i++) {
    if ($i < 10 || $i % $frac == 0) { print ("$i ") };
    my $fname = $ARGV[$i];
#    print "fname = $fname";
    my $ofile = $fname.".objdump";
    my $memfile = $fname.".memfile";

    my $needsprocessing = 0;
    if (!-e $memfile) { $needsprocessing = 1; } # create memfile if it doesn't exist
    else {
        my $osb = stat($ofile) || die("Can't stat $ofile");
        my $msb = stat($memfile) || die("Can't stat $memfile");
        my $otime = $osb->mtime;
        my $mtime = $msb->mtime;
        if ($otime > $mtime) { $needsprocessing = 1; } # is memfile out of date?
    }

    if ($needsprocessing == 1) {
        open(FILE, $ofile) || die("Can't read $ofile");
        my $mode = 0; # parse for code
	my $section = "";
	my $data = "";
        my $address;
	my $first = 0;
	my $firstAddress;

    # initialize to all zeros;
	# *** need to fix the zeroing range.  Not always 64K
        for (my $i=0; $i < 65536*4; $i++) {
            $memfilebytes[$i] = "00";
        }

        while(<FILE>) {
	    # objdump fill is divided into several .sections of which only some we want to actually process.
	    # In general we want everything except the .comment and .*attributes
	    if (/Disassembly of section (.*):/) {
		$section = $1;
		print ("setting section to $section\n");
	    } else {
		# now check if the section is one we are interested in processing.
		#if ($section ne ".comment" && $section ne ".riscv.attributes" && $section =~ /\.debug.*/) {
		if ($section =~ "\.init|\.text|\..*data|\..*bss") {
		    # the structure is: possible space(s) hex number: possible space(s) hex number space(s) junk
		    # there are also lines we need to skip: possible  space(s) hex number <string>:
		    if (/^\s*([0-9A-Fa-f]{1,16}):\s+([0-9A-Fa-f]+).*$/) {
			$address = &fixadr($1);			
			if ($first == 0) {
			    $first = 1;
			    $firstAddress = $address;
			}
			$data = $2;
			&emitData($address, $data);
			# my $len = length($data);
			# for (my $i=0; $i<$len/2; $i++) {
			#     $memfilebytes[$address+$i] = substr($data, $len-2-2*$i, 2);
			# }
#			print ("Addr $address $data\n");
#		    } elsif (/^\s*\.\.\./) {
#			print ("Got ...\n");
#		    } else {
#			print ("No match\n");
		    }
		}
	    }
# 	    # *** this mode stuff does not work if a section is missing or reordered.
#             if ($mode == 0) { # Parse code
# #	    print("Examining $_\n");
# 		if (/^\s*(\S{1,16}):\s+(\S+)\s+/) {
#                     $address = &fixadr($1);
#                     my $instr = $2;
#                     my $len = length($instr);
#                     for (my $i=0; $i<$len/2; $i++) {
#                         $memfilebytes[$address+$i] = substr($instr, $len-2-2*$i, 2);
#                     }
#                     print ("address $address $instr\n");
# 		}
# 		if (/Disassembly of section .data:/) { $mode = 1;}
#             } elsif ($mode == 1) { # Parse data segment
#                 if (/^\s*(\S{1,16}):\s+(.*)/) {
#                     $address = &fixadr($1);
# #		        print "addresss $address maxaddress $maxaddress\n";
# 		    if ($address > $maxaddress) { $maxaddress = $address; }
#                     my $line = $2;
#                     # merge chunks with spaces
# 		    # *** might need to change
#                     $line =~ s/(\S)\s(\S)/$1$2/g;
#                     # strip off comments
#                     $line =~ /^(\S*)/;
#                     $payload = $1;
#                     &emitData($address, $payload);
#                 }
#                 if (/Disassembly of section .comment:/) { $mode = 2; }
# 	    } elsif ($mode == 2) { # parse the comment section
#                 if (/Disassembly of section .riscv.attributes:/) { $mode = 3; }
#             }
        }
        close(FILE);
        $maxaddress = $address + 32; # pad some zeros at the end

        # print to memory file
	# *** this is a problem
        if ($fname =~ /rv32/) {
        open(MEMFILE, ">$memfile") || die("Can't write $memfile");
        for (my $i=$firstAddress; $i<= $maxaddress; $i = $i + 4) {
            for ($j=3; $j>=0; $j--) {
		no warnings 'uninitialized';
		my $value = $memfilebytes[$i+$j];
		if ($value eq ""){
		    print MEMFILE "00";
		} else {
		    print MEMFILE "$memfilebytes[$i+$j]";
		}
            }
            print MEMFILE "\n";
        }
        close(MEMFILE);
        } else {
        open(MEMFILE, ">$memfile") || die("Can't write $memfile");
        for (my $i=$firstAddress; $i<= $maxaddress; $i = $i + 8) {
            for ($j=7; $j>=0; $j--) {
		no warnings 'uninitialized';
		my $value = $memfilebytes[$i+$j];
		if ($value eq ""){
		    print MEMFILE "00";
		} else {
		    print MEMFILE "$memfilebytes[$i+$j]";
		}
            }
            print MEMFILE "\n";
        }
        close(MEMFILE);
        }
    }
}
print("\n");

sub emitData {
    # print the data portion of the ELF into a memroy file, including 0s for empty stuff
    # deal with endianness
    my $address = shift;
    my $payload = shift;

#    print("Emitting data.  address = $address payload = $payload\n");

    my $len = length($payload);
    if ($len <= 8) { 
        # print word or halfword
        for(my $i=0; $i<$len/2; $i++) {
            my $adr = $address+$i;
            my $b = substr($payload, $len-2-2*$i, 2);
            $memfilebytes[$adr] = $b;
#            print(" $adr $b\n");
        }
    }  elsif ($len == 12) {
        # weird case of three halfwords on line
        &emitData($address, substr($payload, 0, 4));
        &emitData($address+2, substr($payload, 4, 4));
        &emitData($address+4, substr($payload, 8, 4));
    } else {
        &emitData($address, substr($payload, 0, 8));
        &emitData($address+4, substr($payload, 8, $len-8));
    }
}

sub fixadr {
    # strip off leading 8 from address and convert to decimal
    # if the leading 8 is not present don't remove.
    my $adr = shift;
    #print "addr $adr\n";
    return hex($adr);
}