#!/usr/bin/perl -w # plemieux 14-11-2015,v.3.6 17:38:07 # ******************************************************************************************* # Parse FW project files and build AVR assembler files. # Usage: perl PCOD2asm.pl {filespec} # Files can be specific using directory names and wildcards. # # Assumptions: # @ and * identify labels. # Offset modifiers may appear after a label reference: *label -1 or *label + 1. Byte length. # "." preceeds comments. # "#xyz" is used as a forward reference tag (#nxt, #len, etc). # Data Strings may be embedded in the code, enclosed in double quotes. # # Files created: # {FileSpec}.tmp-> cleaned up source file. # {FileSpec}.lbl-> list of labels. # {FileSpec}.hex-> pre-assembly source file upper case hex bytes. # {FileSpec}.asm-> AVR assembler file. # # ******************************************************************************************* use strict; use warnings; # Initialize. my %labels=(); my %fwdrefs=(); my %offsetmod=(); my $InputFile=(); my $LinkFlag=(); my @files=(); my $HexFile=(); my $Hex=(); my $index=(); my $byte=(); my $org=(); my $block=(); my $ByteCount=(); my @ASM=(); my $FileName=(); my $Answer=(); my $ASMFILE=(); my $asm=(); my @LinkedFiles=(); my @UnLinkedFiles=(); my $param1=(); my $param2=(); # Subroutines. sub InsertHeader { print {$asm} join (""," "x4,";org"," "x4); printf {$asm} "0x%02X\n",$org; print {$asm} join (""," "x4,".db"," "x4); } sub InsertFooter { print {$asm} join ("",";"," "x4,"END","\n"); } sub LinkHexFiles { @ASM=(); foreach $FileName (@LinkedFiles) { ($HexFile=$FileName)=~s/\..*$/.hex/g; chomp $HexFile; open ($Hex, "<",$HexFile) or die "Cannot open '$HexFile' $!"; foreach $byte (<$Hex>){ push (@ASM, $byte); } pop @ASM; # If linking job/case files, the last pointer is redundant. pop @ASM; close ($Hex); } } sub BuildAsmFile { open ($asm,">",$ASMFILE) or die "Could not open file '$ASMFILE' $!"; $org=0; $block=32; $ByteCount=0; push (@ASM,"FF"); # Remove if ending with double FF's not required push (@ASM,"FF"); # Remove if ending with double FF's not required InsertHeader(); foreach $byte (@ASM) { chomp $byte; $ByteCount++; if ($ByteCount < $block and $ByteCount <= $#ASM) {printf {$asm} join ("","0x",$byte,",");} else { printf {$asm} join ("","0x",$byte,"\n"); if ($ByteCount > $#ASM) {InsertFooter;} else { $org=$block; InsertHeader(); $block=$block+32; } } } close($asm); } # Process command line arguments. my ($FileSpec)=@ARGV; if (not defined $FileSpec){die "No input file provided! Aborting.\n";} @files=<@ARGV>; $LinkFlag=$#files; if ($LinkFlag>0) { print "Multiple source files entered. Link (y,n)?"; chomp ($Answer=); $Answer=~s/y/Y/g; if ($Answer eq "Y") { print "Source files will be linked in this order:\n"; print "@files\n"; print " to accept, or re-enter file list:"; chomp ($Answer=); if (length($Answer)>0) {@LinkedFiles=split (" ",$Answer);} else {@LinkedFiles=<@files>}; foreach $FileName (@files) { push @UnLinkedFiles,$FileName unless (grep /$FileName/,@LinkedFiles); } print "ASM file name:"; chomp ($ASMFILE=); } else {$LinkFlag=0;} } # Process source files. foreach $InputFile (@files) { chomp $InputFile; open (my $src,"<",$InputFile) or die "Could not open file '$InputFile' $!"; (my $OutputFile=$InputFile)=~s/\..*$/.tmp/g; chomp $OutputFile; open (my $dst,">",$OutputFile) or die "Could not open file '$OutputFile' $!"; # Clean up text file and populate temp work file. while (my $line=<$src>) { if ($line!~m/^\"/) { $line=~s/\..*$//g; #remove comments $line=~s/[^a-zA-Z0-9\ t\*\#\-\+\@\"]/\ /g; # Keep apha, numeric, *, +, -, @, " and # $line=~s/\-{2,}//g; # Remove multiple dashes $line=~s/\-/\ \-/g; # fix label arithmetic such as *cas1-1 $line=~s/\+/\ \+/g; # fix label arithmetic such as *cas1+1 $line=~s/\ {2,}/\ /g; # Replace multiple spaces with one chomp $line; } # Convert embedded data strings to ASCII hex value stripping the double quotes. if ($line=~m/^\"/) { $line=~s/^\"//g; $line=~s/[\"].*$//g; $line=~s/(.)/sprintf(' %02X',ord($1))/eg; chomp $line; my @DataString=split ' ', $line; foreach $byte (@DataString) {print {$dst} "$byte\n";} } elsif (length $line gt 1) {print {$dst} "$line\n"}; } close ($dst); close ($src); # First pass, find labels and index them with the workfile name before saving them to hash. (my $FileIndex=$InputFile)=~s/\..*$//g; chomp $FileIndex; (my $LabelFile=$InputFile)=~s/\..*$/.lbl/g; chomp $LabelFile; $index=0; open (my $wrk,"<",$OutputFile) or die "Cannot read '$OutputFile' $!"; open (my $lbl,">",$LabelFile) or die "Cannot create '$LabelFile' $!"; my @TempFile=<$wrk>; close ($wrk); foreach (@TempFile) { chomp; my @record = split ' '; if ($record[0]=~m/^\*.|^\@./) { if (not defined $labels{"$record[0]\.$FileIndex"}) { print {$lbl} "label $record[0] at offset $index\n"; $labels{"$record[0]\.$FileIndex"}=$index; } else { $param1=$index+1; $param2=$labels{"$record[0]\.$FileIndex"}+1; print "WARNING!!!\nAttempt to redefine label $record[0] at line $param1 in file $OutputFile\n". "Label $record[0] already defined at line $param2.\nAborting.\n"; exit 1 } } $index++; } close ($lbl); # Second pass calculate offsets to labels and forward references (#nxt, #len, etc). $index=0; %fwdrefs=(); %offsetmod=(); foreach (@TempFile) { chomp ; my @record=split ' '; if ($record[0]=~m/^ff$/ and defined $record[1]) { if (exists $labels{"$record[1]\.$FileIndex"}) { (my $offset=$labels{"$record[1]\.$FileIndex"}-$index); if (defined $record[2] and $record[2]=~m/^\+[0-9]+$|^\-[0-9]+$/) {$offset=$offset+$record[2];} if ($offset <= 127 and $offset >= -128 ) { (my $byte=sprintf (' %02X',$offset%256)); $TempFile[$index]=$byte; } else { $param1=$index+1; print "WARNING!!!\nOffset value $offset to label $record[1] exceeds byte boundary at line $param1 ". "in file $OutputFile\nAborting.\n"; exit 1 } } elsif ($record[1]=~m/^#./) { if (exists $fwdrefs{$record[1]}) { (my $offset=$index-$fwdrefs{$record[1]}+$offsetmod{$record[1]}-2); if ($offset <= 127 and $offset >= -128 ) { (my $byte=sprintf (' %02X',$offset%256)); $TempFile[$fwdrefs{$record[1]}]=$byte; } else { $param1=$fwdrefs{$record[1]}+1; $param2=$index+1; print "WARNING!!!\nOffset value $offset to forward reference $record[1] exceeds byte boundary at line $param1.". "\nNext forward reference found at line $param2 in file $OutputFile\nAborting.\n"; exit 1 } } $fwdrefs{$record[1]}=$index; if (defined $record[2] and $record[2]=~m/^\+[0-9]+$|^\-[0-9]+$/) {$offsetmod{$record[1]}=$record[2];} else {$offsetmod{$record[1]}=0;} } else { $param1=$index+1; print "WARNING!!!\nUndefined label $record[1] referenced at line $param1 in file $OutputFile\nAborting.\n"; exit 1 } } elsif ($record[0]=~m/^ff$/ and not defined $record[1]) { $param1=$index+1; print "WARNING!!!\nNo label or forward reference tag after $record[0] byte at line $param1". " in file $OutputFile\n"; # exit 1 #Uncomment if you want to abort on this warning. } $index++; } # Save hex data prior to building the Atmel assembler file. (my $HexFile=$InputFile)=~s/\..*$/.hex/g; chomp $HexFile; open (my $Hex,">",$HexFile) or die "Cannot create '$HexFile' $!"; foreach (@TempFile) { chomp; my @record=split ' '; if ($record[0]=~m/\*.|\@./) {print {$Hex} "$record[1]\n";} else {print {$Hex} "$record[0]\n";} } close ($Hex); } # Build the ASM file. First, if source files were tagged to be linked, link them then assemble. if ($LinkFlag==0) {@UnLinkedFiles=<@files>;} else { LinkHexFiles(); BuildAsmFile(); } # Next, if other source files were specified on the command line but not tagged to be linked, assemble them separately. if (@UnLinkedFiles) { foreach $FileName (@UnLinkedFiles) { @ASM=(); ($HexFile=$FileName)=~s/\..*$/.hex/g; ($ASMFILE=$FileName)=~s/\..*$/.asm/g; chomp $HexFile; chomp $ASMFILE; open ($Hex, "<",$HexFile) or die "Cannot open '$HexFile' $!"; @ASM=<$Hex>; pop @ASM; pop @ASM; close ($Hex); BuildAsmFile(); } } exit 0