diff --git a/README.md b/README.md
index d1d2ecd..b7fccbf 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,143 @@
-# obj2hex
-PDP-11 object file translator / linker
+tu58em is a ...
+
+If run with no options, it prints a usage screen:
+
+```
+obj2hex.pl v1.5 by Don North (perl 5.022)
+Usage: ./obj2hex.pl [options...] arguments
+ --help output manpage and exit
+ --debug enable debug mode
+ --verbose verbose status reporting
+ --boot M9312 boot prom
+ --console M9312 console/diagnostic prom
+ --binary binary program load image
+ --ascii ascii m9312 program load image
+ --bytes=N bytes per block on output
+ --nocrc inhibit output of CRC-16 in hex format
+ --logfile=LOGFILE logging message file
+ --objfile=OBJFILE macro11 object .obj file
+ --outfile=OUTFILE output .hex/.txt/.bin file
+Aborted due to command line errors.
+```
+
+If run with the --help option it prints a longer manual page:
+
+```
+NAME
+ obj2hex.pl - Convert a Macro-11 program image to PROM/load format
+
+SYNOPSIS
+ obj2hex.pl [--help] [--debug] [--verbose] [--boot] [--console] [--binary]
+ [--ascii] [--bytes=N] [--nocrc] [--logfile=LOGFILE] --objfile=OBJFILE
+ --outfile=BINFILE
+
+DESCRIPTION
+ Converts a Macro-11 object file to various output formats, including M9312
+ boot and console PROM, straight binary records, ASCII format for M9312
+ console load commands, and loadable absolute binary program images (.BIN)
+ files.
+
+ Currently the program is limited to a single object input file that can be
+ output in the selected format. Multiple .psect/.asect ops are supported,
+ as well as all local (non-global) relocation directory entries. Multiple
+ object files are (not yet) supported.
+
+OPTIONS
+ The following options are available:
+
+ --help
+ Output this manpage and exit the program.
+
+ --debug
+ Enable debug mode; print input file records as parsed.
+
+ --verbose
+ Verbose status; output status messages during processing.
+
+ --boot
+ Generate a hex PROM file image suitable for programming into an M9312
+ boot prom (512x4 geometry, only low half used).
+
+ --console
+ Generate a hex PROM file image suitable for programming into an M9312
+ console/diagnostic prom (1024x4 geometry).
+
+ --binary
+ Generate binary format load records of the program image (paper tape
+ format) for loading into SIMH or compatible simulators. These files
+ can also be copied onto XXDP filesystems to generate runnable program
+ images (used to write custom diaqnostics).
+
+ --ascii
+ Generate a a sequence of 'L addr' / 'D data' commands for downloading
+ a program via a terminal emulator thru the M9312 user command
+ interface. Suitable only for really small test programs.
+
+ Exactly ONE of --boot, --console, --binary, or --ascii must be
+ specified.
+
+ --bytes=N
+ For hex format output files, output N bytes per line (default 16).
+
+ --nocrc
+ For hex format output files, don't automatically stuff the computed
+ CRC-16 as the last word in the ROM.
+
+ --logfile=FILENAME
+ Generate debug output into this file.
+
+ --objfile=FILENAME
+ Input objject file in .obj format.
+
+ --outfile=FILENAME
+ Output binary file in format selected by user option.
+
+ERRORS
+ The following diagnostic error messages can be produced on STDERR. The
+ meaning should be fairly self explanatory.
+
+ "Aborted due to command line errors" -- bad option or missing file(s)
+
+ "Can't open input file '$file'" -- bad filename or unreadable file
+
+ "Error: Improper object file format (1)" -- valid record must start with
+ 0x01
+
+ "Error: Improper object file format (2)" -- second byte must be 0x00
+
+ "Error: Improper object file format (3)" -- third byte is low byte of
+ record length
+
+ "Error: Improper object file format (4)" -- fourth byte is high byte of
+ record length
+
+ "Error: Improper object file format (5)" -- bytes five thru end-1 are data
+ bytes
+
+ "Error: Improper object file format (6)" -- last byte is checksum
+
+ "Error: Bad checksum exp=0x%02X rcv=0x%02X" -- compare rcv'ed checksum vs
+ exp'ed checksum
+
+EXAMPLES
+ Some examples of common usage:
+
+ obj2hex.pl --help
+
+ obj2hex.pl --verbose --boot --in 23-751A9.obj --out 23-751A9.hex
+
+ obj2hex.pl --verbose --binary --in memtest.obj --out memtest.bin
+
+AUTHOR
+ Don North - donorth
+
+HISTORY
+ Modification history:
+
+ 2005-05-05 v1.0 donorth - Initial version.
+ 2016-01-15 v1.1 donorth - Added RLD(IR) processing, moved sub's to end.
+ 2016-01-18 v1.2 donorth - Added GSD processing, improved debug output.
+ 2016-01-20 v1.3 donorth - Initial support for linking multiple PSECTs.
+ 2016-01-22 v1.4 donorth - Added objfile/outfile/logfile switches vs stdio.
+ 2016-01-28 v1.5 donorth - Added RLD processing, especially complex.
+```
diff --git a/obj2hex.pl b/obj2hex.pl
new file mode 100644
index 0000000..7ced521
--- /dev/null
+++ b/obj2hex.pl
@@ -0,0 +1,1059 @@
+#!/usr/bin/perl -w
+#!/usr/local/bin/perl -w
+
+# Copyright (c) 2005-2016 Don North
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# o Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# o Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# o Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+require 5.008;
+
+=head1 NAME
+
+obj2hex.pl - Convert a Macro-11 program image to PROM/load format
+
+=head1 SYNOPSIS
+
+obj2hex.pl
+S<[--help]>
+S<[--debug]>
+S<[--verbose]>
+S<[--boot]>
+S<[--console]>
+S<[--binary]>
+S<[--ascii]>
+S<[--bytes=N]>
+S<[--nocrc]>
+S<[--logfile=LOGFILE]>
+S<--objfile=OBJFILE>
+S<--outfile=BINFILE>
+
+=head1 DESCRIPTION
+
+Converts a Macro-11 object file to various output formats,
+including M9312 boot and console PROM, straight binary records,
+ASCII format for M9312 console load commands, and loadable absolute
+binary program images (.BIN) files.
+
+Currently the program is limited to a single object input file that
+can be output in the selected format. Multiple .psect/.asect ops are
+supported, as well as all local (non-global) relocation directory
+entries. Multiple object files are (not yet) supported.
+
+=head1 OPTIONS
+
+The following options are available:
+
+=over
+
+=item B<--help>
+
+Output this manpage and exit the program.
+
+=item B<--debug>
+
+Enable debug mode; print input file records as parsed.
+
+=item B<--verbose>
+
+Verbose status; output status messages during processing.
+
+=item B<--boot>
+
+Generate a hex PROM file image suitable for programming into
+an M9312 boot prom (512x4 geometry, only low half used).
+
+=item B<--console>
+
+Generate a hex PROM file image suitable for programming into
+an M9312 console/diagnostic prom (1024x4 geometry).
+
+=item B<--binary>
+
+Generate binary format load records of the program image (paper
+tape format) for loading into SIMH or compatible simulators.
+These files can also be copied onto XXDP filesystems to generate
+runnable program images (used to write custom diaqnostics).
+
+=item B<--ascii>
+
+Generate a a sequence of 'L addr' / 'D data' commands for downloading
+a program via a terminal emulator thru the M9312 user command interface.
+Suitable only for really small test programs.
+
+Exactly ONE of B<--boot>, B<--console>, B<--binary>, or B<--ascii>
+must be specified.
+
+=item B<--bytes=N>
+
+For hex format output files, output N bytes per line (default 16).
+
+=item B<--nocrc>
+
+For hex format output files, don't automatically stuff the computed
+CRC-16 as the last word in the ROM.
+
+=item B<--logfile=FILENAME>
+
+Generate debug output into this file.
+
+=item B<--objfile=FILENAME>
+
+Input objject file in .obj format.
+
+=item B<--outfile=FILENAME>
+
+Output binary file in format selected by user option.
+
+=back
+
+=head1 ERRORS
+
+The following diagnostic error messages can be produced on STDERR.
+The meaning should be fairly self explanatory.
+
+C -- bad option or missing file(s)
+
+C -- bad filename or unreadable file
+
+C -- valid record must start with 0x01
+
+C -- second byte must be 0x00
+
+C -- third byte is low byte of record length
+
+C -- fourth byte is high byte of record length
+
+C -- bytes five thru end-1 are data bytes
+
+C -- last byte is checksum
+
+C -- compare rcv'ed checksum vs exp'ed checksum
+
+=head1 EXAMPLES
+
+Some examples of common usage:
+
+ obj2hex.pl --help
+
+ obj2hex.pl --verbose --boot --in 23-751A9.obj --out 23-751A9.hex
+
+ obj2hex.pl --verbose --binary --in memtest.obj --out memtest.bin
+
+=head1 AUTHOR
+
+Don North - donorth
+
+=head1 HISTORY
+
+Modification history:
+
+ 2005-05-05 v1.0 donorth - Initial version.
+ 2016-01-15 v1.1 donorth - Added RLD(IR) processing, moved sub's to end.
+ 2016-01-18 v1.2 donorth - Added GSD processing, improved debug output.
+ 2016-01-20 v1.3 donorth - Initial support for linking multiple PSECTs.
+ 2016-01-22 v1.4 donorth - Added objfile/outfile/logfile switches vs stdio.
+ 2016-01-28 v1.5 donorth - Added RLD processing, especially complex.
+
+=cut
+
+# options
+use strict;
+
+# external standard modules
+use Getopt::Long;
+use Pod::Text;
+use FindBin;
+use FileHandle;
+
+# external local modules search path
+BEGIN { unshift(@INC, $FindBin::Bin);
+ unshift(@INC, $ENV{PERL5LIB}) if defined($ENV{PERL5LIB}); # cygwin bugfix
+ unshift(@INC, '.'); }
+
+# external local modules
+
+# generic defaults
+my $VERSION = 'v1.5'; # version of code
+my $HELP = 0; # set to 1 for man page output
+my $DEBUG = 0; # set to 1 for debug messages
+my $VERBOSE = 0; # set to 1 for verbose messages
+
+# specific defaults
+my $crctype = 'CRC-16'; # type of crc calc to do
+my $memsize; # number of instruction bytes allowed
+my $memfill; # memory fill pattern
+my %excaddr; # words to be skipped in rom crc calc
+my $rombase; # base address of rom image
+my $romsize; # number of rom addresses
+my $romfill; # rom fill pattern
+my $romtype = 'NONE'; # default rom type
+my $bytesper = -1; # bytes per block in output file
+my $nocrc = 0; # output CRC16 as last word unless set
+my $objfile = undef; # input filename
+my $outfile = undef; # output filename
+my $logfile = undef; # log filename
+
+# process command line arguments
+my $NOERROR = GetOptions( "help" => \$HELP,
+ "debug" => \$DEBUG,
+ "verbose" => \$VERBOSE,
+ "boot" => sub { $romtype = 'BOOT'; },
+ "console" => sub { $romtype = 'DIAG'; },
+ "binary" => sub { $romtype = 'BINA'; },
+ "ascii" => sub { $romtype = 'ASC9'; },
+ "bytes=i" => \$bytesper,
+ "nocrc" => \$nocrc,
+ "objfile=s" => \$objfile,
+ "outfile=s" => \$outfile,
+ "logfile=s" => \$logfile,
+ );
+
+# init
+$VERBOSE = 1 if $DEBUG; # debug implies verbose messages
+
+# output the documentation
+if ($HELP) {
+ # output a man page if we can
+ if (ref(Pod::Text->can('new')) eq 'CODE') {
+ # try the new way if appears to exist
+ my $parser = Pod::Text->new(sentence=>0, width=>78);
+ printf STDOUT "\n"; $parser->parse_from_file($0);
+ } else {
+ # else must use the old way
+ printf STDOUT "\n"; Pod::Text::pod2text(-78, $0);
+ };
+ exit(1);
+}
+
+# check for correct arguments present, print usage if errors
+unless ($NOERROR
+ && scalar(@ARGV) == 0
+ && defined($objfile)
+ && defined($outfile)
+ && $romtype ne 'NONE'
+ ) {
+ printf STDERR "obj2hex.pl %s by Don North (perl %g)\n", $VERSION, $];
+ print STDERR "Usage: $0 [options...] arguments\n";
+ print STDERR <<"EOF";
+ --help output manpage and exit
+ --debug enable debug mode
+ --verbose verbose status reporting
+ --boot M9312 boot prom
+ --console M9312 console/diagnostic prom
+ --binary binary program load image
+ --ascii ascii m9312 program load image
+ --bytes=N bytes per block on output
+ --nocrc inhibit output of CRC-16 in hex format
+ --logfile=LOGFILE logging message file
+ --objfile=OBJFILE macro11 object .obj file
+ --outfile=OUTFILE output .hex/.txt/.bin file
+EOF
+ # exit if errors...
+ die "Aborted due to command line errors.\n";
+}
+
+# setup log file as a file, defaults to STDERR if not supplied
+my $LOG = defined($logfile) ? FileHandle->new("> ".$logfile) : FileHandle->new_from_fd(fileno(STDERR),"w");
+
+#----------------------------------------------------------------------------------------------------
+
+# subroutine prototypes
+
+sub trim ($);
+sub chksum (@);
+sub rad2asc (@);
+sub crc (%);
+sub read_rec ($);
+
+#----------------------------------------------------------------------------------------------------
+
+# fill in the parameters of the device
+
+if ($romtype eq 'BOOT') {
+
+ # M9312 512x4 boot prom
+ %excaddr = ( 024=>1, 025=>1 ); # bytes to be skipped in rom crc calc
+ $memsize = 128; # number of instruction bytes allowed
+ $memfill = 0x00; # memory fill pattern
+ $romsize = 512; # number of rom addresses (must be a power of two)
+ $romfill = 0x00; # rom fill pattern
+ $rombase = 0173000; # base address of rom
+
+} elsif ($romtype eq 'DIAG') {
+
+ # M9312 1024x4 diagnostic/console prom
+ %excaddr = ( ); # bytes to be skipped in rom crc calc
+ $memsize = 512; # number of instruction bytes allowed
+ $memfill = 0x00; # memory fill pattern
+ $romsize = 1024; # number of rom addresses (must be a power of two)
+ $romfill = 0x00; # rom fill pattern
+ $rombase = 0165000; # base address of rom
+
+} elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') {
+
+ # program load image ... 56KB address space maximum
+ %excaddr = ( ); # bytes to be skipped in rom crc calc
+ $memsize = 7*8192; # number of instruction bytes allowed
+ $memfill = 0x00; # memory fill pattern
+ $romsize = 8*8192; # number of rom addresses (must be a power of two)
+ $romfill = 0x00; # image fill pattern
+ $rombase = 0; # base address of binary image
+
+} else {
+
+ # unknown ROM type code
+ die "ROM type '$romtype' is not supported!\n";
+
+}
+
+if ($VERBOSE) {
+ printf $LOG "ROM type is '%s'\n", $romtype;
+ printf $LOG "ROM space is %d. bytes\n", $memsize;
+ printf $LOG "ROM length is %d. addresses\n", $romsize;
+ printf $LOG "ROM base address is 0%06o\n", $rombase;
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# read/process the input object file records
+
+# real pdp11 memory data words in boot prom
+my @mem = ((0) x $memsize);
+
+# min/max address limits in object file
+my ($adrmin,$adrmax) = ('','');
+
+# state variables in processing object records
+my $rommsk = ($romsize-1)>>1; # address bit mask
+my $adrmsk = 0xFFFF; # 16b addr mask
+my $datmsk = 0xFFFF; # 16b data mask
+my $memmsk = 0xFF; # 8b memory data mask
+
+# open the input .obj file, die if error
+my $OBJ = FileHandle->new("< ".$objfile);
+die "Error: can't open input object file '$objfile'\n" unless defined $OBJ;
+
+# databases
+my %gblsym = ();
+my %psect = ();
+my @psect = ();
+my %program = ();
+my $psectname = '. ABS.';
+my $psectaddr = 0;
+my $psectnumb = -1;
+my $textaddr = 0;
+
+# program defaults
+$program{START}{VALUE} = 1;
+$program{START}{PSECT} = '. ABS.';
+
+# now parse all the records
+while (my @rec = &read_rec($OBJ)) {
+
+ # type is first byte of record
+ my $key = $rec[0];
+
+ if ($key == 001) { # GSD
+
+ # iterate over GSD subrecords
+ for (my $i = 2; $i < @rec; ) {
+ # GSD records are fixed 8B length all in the same format
+ my $nam = &rad2asc(($rec[$i+1]<<8)|($rec[$i+0]<<0), ($rec[$i+3]<<8)|($rec[$i+2]<<0));
+ my $flg = $rec[$i+4];
+ my $ent = $rec[$i+5];
+ my $val = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ my @ent = ('MODULE','CSECT','INTSYM','XFRADR','GBLSYM','PSECT','IDENT','VSECT');
+ if ($ent == 3) {
+ # XFRADR
+ $program{START}{PSECT} = $nam;
+ $program{START}{VALUE} = $val;
+ } elsif ($ent == 4) {
+ # GBLSYM flags
+ $gblsym{$nam}{FLG}{$flg&(1<<0) ? "WEA" : "STR"}++;
+ $gblsym{$nam}{FLG}{$flg&(1<<3) ? "DEF" : "REF"}++;
+ $gblsym{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++;
+ $gblsym{$nam}{PSECT} = $psectname;
+ $gblsym{$nam}{VALUE} = $val;
+ } elsif ($ent == 5) {
+ # PSECT flags
+ $psect[++$psectnumb] = $nam;
+ $psect{$nam}{NUMBER} = $psectnumb;
+ $psect{$nam}{FLG}{$flg&(1<<0) ? "GBL" : $flg&(1<<6) ? "GBL" : "LCL"}++;
+ $psect{$nam}{FLG}{$flg&(1<<2) ? "OVR" : "CAT"}++;
+ $psect{$nam}{FLG}{$flg&(1<<4) ? "R/O" : "R/W"}++;
+ $psect{$nam}{FLG}{$flg&(1<<5) ? "REL" : "ABS"}++;
+ $psect{$nam}{FLG}{$flg&(1<<7) ? "D" : "I/D"}++;
+ if ($psect{$nam}{FLG}{CAT}) {
+ $psect{$nam}{LENGTH} = $val;
+ $psect{$nam}{START} = $psectaddr;
+ $psectname = $nam;
+ $psectaddr += $val;
+ } elsif ($psect{$nam}{FLG}{ABS}) {
+ $psect{$nam}{LENGTH} = $val;
+ $psect{$nam}{START} = 0;
+ $psectname = $nam;
+ }
+ }
+ if ($DEBUG) {
+ printf $LOG "..GSD: type='%-6s'(%03o) name='%s' value=%06o", $ent[$ent], $ent, $nam, $val;
+ printf $LOG " flags=%s", join(",", sort(keys(%{$gblsym{$nam}{FLG}}))) if $ent == 4;
+ printf $LOG " flags=%s", join(",", sort(keys(%{$psect{$nam}{FLG}}))) if $ent == 5;
+ printf $LOG "\n";
+ }
+ $i += 8;
+ }
+
+ } elsif ($key == 002) { # ENDGSD
+
+ # just say we saw it
+ printf $LOG "..ENDGSD\n" if $DEBUG;
+
+ $program{END}{ADDRESS} = 0;
+ foreach my $nam (sort(keys(%psect))) {
+ my $start = $psect{$nam}{START};
+ my $length = $psect{$nam}{LENGTH};
+ my $end = $length ? $start + $length - 1 : $start;
+ $program{END}{ADDRESS} = $end if $end > $program{END}{ADDRESS};
+ printf $LOG "..PSECT[%d](%s) START=%06o END=%06o LENGTH=%06o\n",
+ $psect{$nam}{NUMBER}, $nam, $start, $end, $length if $length && $DEBUG;
+ }
+ $program{START}{ADDRESS} = $program{START}{VALUE} + $psect{$program{START}{PSECT}}{START};
+ printf $LOG "..PROG(ADDRESS) START=%06o END=%06o\n",
+ $program{START}{ADDRESS}, $program{END}{ADDRESS} if $DEBUG;
+
+ } elsif ($key == 003) { # TXT
+
+ # process text record
+ my $off = ($rec[3]<<8)|($rec[2]<<0);
+ my $len = @rec-4;
+ my $base = $psect{$psectname}{START};
+ my $adr = ($base + $off) & $adrmsk;
+ foreach my $i (1..$len) { $mem[$adr+$i-1] = $rec[4+$i-1]; }
+ if ($DEBUG) {
+ printf $LOG "..TXT OFFSET=%06o LENGTH=%o BASE=%06o PSECTNAME='%s'\n", $off, $len, $base, $psectname;
+ for (my $i = 0; $i < $len; $i += 2) {
+ printf $LOG " %06o: ", ($adr+$i)&~1 if $i%8 == 0;
+ printf $LOG " %03o...", $mem[$adr+$i++] if ($adr+$i)&1;
+ printf $LOG " %06o", ($mem[$adr+$i+1]<<8)|($mem[$adr+$i+0]<<0) if $i < $len-1;
+ printf $LOG " ...%03o", $mem[$adr+$i] if $i == $len-1;
+ printf $LOG "\n" if $i%8 >= 6 && $i < $len-2;
+ }
+ printf $LOG "\n";
+ }
+ $adrmin = $adr if $adrmin eq '' || $adr < $adrmin;
+ $adrmax = $adr+$len-1 if $adrmax eq '' || $adr+$len-1 > $adrmax;
+ $textaddr = $adr;
+
+ } elsif ($key == 004) { # RLD
+
+ # iterate over RLD subrecords
+ for (my $i = 2; $i < @rec; ) {
+ # first byte is entry type and flags
+ my $ent = $rec[$i+0] & 0x7F; # entry type
+ my $flg = $rec[$i+0] & 0x80; # modification flag (0=word, 1=byte)
+ # process an entry
+ if ($ent == 001) {
+ # internal relocation ... OK
+ my $dis = $rec[$i+1];
+ my $con = ($rec[$i+3]<<8)|($rec[$i+2]<<0);
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($psect{$psectname}{START} + $con);
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(IR): adr=%06o val=%06o ; dis=%06o con=%06o\n",
+ $adr, $val, $dis, $con if $DEBUG;
+ $i += 4;
+ } elsif ($ent == 002) {
+ # global relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ # process
+ printf $LOG "..RLD(GR): dis=%06o nam='%s'\n",
+ $dis, $nam if $DEBUG;
+ $i += 6;
+ } elsif ($ent == 003) {
+ # internal displaced ... OK
+ my $dis = $rec[$i+1];
+ my $con = ($rec[$i+3]<<8)|($rec[$i+2]<<0);
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($con - ($adr+2));
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(ID): adr=%06o val=%06o ; dis=%06o con=%06o\n",
+ $adr, $val, $dis, $con if $DEBUG;
+ $i += 4;
+ } elsif ($ent == 004) {
+ # global displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ # process
+ printf $LOG "..RLD(GDR): dis=%06o nam='%s'\n",
+ $dis, $nam if $DEBUG;
+ $i += 6;
+ } elsif ($ent == 005) {
+ # global additive relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ my $con = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ # process
+ printf $LOG "..RLD(GAR): dis=%06o con=%06o nam='%s'\n",
+ $dis, $con, $nam if $DEBUG;
+ $i += 8;
+ } elsif ($ent == 006) {
+ # global additive displaced relocation ... TBD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ my $con = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ # process
+ printf $LOG "..RLD(GADR): dis=%06o con=%06o nam='%s'\n",
+ $dis, $con, $nam if $DEBUG;
+ $i += 8;
+ } elsif ($ent == 007) {
+ # location counter definition ... OK
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ my $con = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ # process
+ $psectname = $nam;
+ $textaddr = $datmsk & ($con);
+ printf $LOG "..RLD(LCD): adr=%06o ; dis=%06o con=%06o nam='%s'\n",
+ $textaddr, $dis, $con, $nam if $DEBUG;
+ $i += 8;
+ } elsif ($ent == 010) {
+ # location counter modification ... OK
+ my $dis = $rec[$i+1];
+ my $con = ($rec[$i+3]<<8)|($rec[$i+2]<<0);
+ # process
+ $textaddr = $datmsk & ($con);
+ printf $LOG "..RLD(LCM): adr=%06o ; dis=%06o con=%06o\n",
+ $textaddr, $dis, $con if $DEBUG;
+ $i += 4;
+ } elsif ($ent == 011) {
+ # program limits ... OK, mostly
+ my $dis = $rec[$i+1];
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ( 01000 ); # make this up, no easy way to compute it
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(LIM1): adr=%06o val=%06o ; dis=%06o\n",
+ $adr, $val, $dis if $DEBUG;
+ $dis += 2;
+ $adr += 2;
+ $val = $datmsk & ($program{END}{ADDRESS});
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(LIM2): adr=%06o val=%06o ; dis=%06o\n",
+ $adr, $val, $dis if $DEBUG;
+ $i += 2;
+ } elsif ($ent == 012) {
+ # psect relocation ... OK
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($psect{$nam}{START});
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(PR): adr=%06o val=%06o ; dis=%06o nam='%s'\n",
+ $adr, $val, $dis, $nam if $DEBUG;
+ $i += 6;
+ } elsif ($ent == 014) {
+ # psect displaced relocation ... OK
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($psect{$nam}{START} - ($adr+2));
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(PDR): adr=%06o val=%06o ; dis=%06o nam='%s'\n",
+ $adr, $val, $dis, $nam if $DEBUG;
+ $i += 6;
+ } elsif ($ent == 015) {
+ # psect additive relocation ... OK
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ my $con = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($psect{$nam}{START} + $con);
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(PAR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n",
+ $adr, $val, $dis, $con, $nam if $DEBUG;
+ $i += 8;
+ } elsif ($ent == 016) {
+ # psect additive displaced relocation ... OK
+ my $dis = $rec[$i+1];
+ my $nam = &rad2asc(($rec[$i+3]<<8)|($rec[$i+2]<<0), ($rec[$i+5]<<8)|($rec[$i+4]<<0));
+ my $con = ($rec[$i+7]<<8)|($rec[$i+6]<<0);
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $val = $datmsk & ($psect{$nam}{START} + $con - ($adr+2));
+ $mem[($adr+0)&$adrmsk] = $memmsk & ($val>>0);
+ $mem[($adr+1)&$adrmsk] = $memmsk & ($val>>8);
+ printf $LOG "..RLD(PADR): adr=%06o val=%06o ; dis=%06o con=%06o nam='%s'\n",
+ $adr, $val, $dis, $con, $nam if $DEBUG;
+ $i += 8;
+ } elsif ($ent == 017) {
+ # complex relocation ... OK
+ my $dis = $rec[$i+1];
+ my $nam = '. ABS.';
+ my $con = 0;
+ # process
+ my $adr = $adrmsk & ($textaddr + $dis - 4);
+ my $loc = 0;
+ my $val = 0;
+ my $opc = "";
+ my @stk = ();
+ my $dun = 0;
+ for ($i += 2; !$dun; $i += 1) {
+ if ($rec[$i] == 000) {
+ $opc = "NOP";
+ } elsif ($rec[$i] == 001) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] + $arg[1]);
+ $opc = "ADD";
+ } elsif ($rec[$i] == 002) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] - $arg[1]);
+ $opc = "SUB";
+ } elsif ($rec[$i] == 003) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] * $arg[1]);
+ $opc = "MUL";
+ } elsif ($rec[$i] == 004) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[1] == 0 ? 0 : int($arg[0] / $arg[1]));
+ $opc = "DIV";
+ } elsif ($rec[$i] == 005) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] & $arg[1]);
+ $opc = "AND";
+ } elsif ($rec[$i] == 006) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] | $arg[1]);
+ $opc = "IOR";
+ } elsif ($rec[$i] == 007) {
+ my @arg = splice(@stk,-2,2);
+ push(@stk, $arg[0] ^ $arg[1]);
+ $opc = "XOR";
+ } elsif ($rec[$i] == 010) {
+ my @arg = splice(@stk,-1,1);
+ push(@stk, -$arg[0]);
+ $opc = "NEG";
+ } elsif ($rec[$i] == 011) {
+ my @arg = splice(@stk,-1,1);
+ push(@stk, ~$arg[0]);
+ $opc = "COM";
+ } elsif ($rec[$i] == 012) {
+ my @arg = splice(@stk,-1,1);
+ $val = $arg[0];
+ $opc = "STO";
+ $dun = 1;
+ } elsif ($rec[$i] == 013) {
+ ############## may need tweaking ################
+ my @arg = splice(@stk,-1,1);
+ $val = $arg[0];
+ $opc = "STO+DIS";
+ $dun = 1;
+ } elsif ($rec[$i] == 016) {
+ ############## may need tweaking ################
+ $nam = &rad2asc(($rec[$i+2]<<8)|($rec[$i+1]<<0),
+ ($rec[$i+4]<<8)|($rec[$i+3]<<0));
+ $con = $gblsym{$nam}{VALUE};
+ push(@stk, $con);
+ $opc = sprintf("GLB[%s]=(%o)", &trim($nam), $con);
+ $i += 4;
+ } elsif ($rec[$i] == 017) {
+ $nam = $psect[$rec[$i+1]];
+ $con = ($rec[$i+3]<<8) | ($rec[$i+2]<<0);
+ $loc = $psect{$nam}{START} + $con;
+ push(@stk, $loc);
+ $opc = sprintf("FET[%s+%o]=(%o)", &trim($nam), $con, $loc);
+ $i += 3;
+ } elsif ($rec[$i] == 020) {
+ $con = ($rec[$i+2]<<8) | ($rec[$i+1]<<0);
+ push(@stk, $con);
+ $opc = "CON";
+ $i += 2;
+ }
+ $stk[-1] = $datmsk & $stk[-1] if @stk;
+ printf $LOG "....OPC=%-20s STK=(%s)\n", $opc, join(",",map(sprintf("%o",$_),@stk)) if $DEBUG;
+ }
+ printf $LOG "..RLD(CMPX): adr=%06o val=%06o ; dis=%06o\n", $adr, $val, $dis if $DEBUG;
+ } else {
+ die sprintf("Error: Unknown RLD entry 0%o (%d)", $ent, $ent);
+ }
+ }
+
+ } elsif ($key == 005) { # ISD
+
+ # ignore
+ printf $LOG "..ISD: ignored\n" if $DEBUG;
+
+ } elsif ($key == 006) { # ENDMOD
+
+ # just say we saw it
+ printf $LOG "..ENDMOD\n\n\n" if $DEBUG;
+
+ } elsif ($key == 007) { # LIBHDR
+
+ # ignore
+ printf $LOG "..LIBHDR: ignored\n" if $DEBUG;
+
+ } elsif ($key == 010) { # LIBEND
+
+ # ignore
+ printf $LOG "..LIBEND: ignored\n" if $DEBUG;
+
+ } else { # unknown
+
+ # invalid record type in the object file
+ die sprintf("Error: unknown record type 0%o (%d)", $key, $key);
+
+ }
+
+}
+
+# done with object file
+$OBJ->close;
+
+#----------------------------------------------------------------------------------------------------
+
+# compute CRC if required, copy memory image to output buffer
+
+my @buf = ($romfill) x $romsize; # physical PROM data bytes, filled background pattern
+
+# only compute CRC on M9312 ROMs
+if ($romtype eq 'BOOT' || $romtype eq 'DIAG') {
+
+ # compute CRC-16 of the prom contents (except exception words) and store at last location
+ my $crctab = &crc(-name=>$crctype, -new=>1);
+ my $crc = &crc(-name=>$crctype, -init=>1);
+ for (my $adr = 0; $adr < $memsize-2; $adr += 1) {
+ next if exists($excaddr{$adr}); # skip these addresses
+ $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]);
+ $crc = &crc(-name=>$crctype, -table=>$crctab, -crc=>$crc, -byte=>$mem[$rombase+$adr]);
+ }
+ $crc = &crc(-name=>$crctype, -crc=>$crc, -last=>1);
+ unless ($nocrc) {
+ # output computed CRC-16 as last word in the ROM file
+ $mem[$rombase+$memsize-2] = ($crc>>0)&0xFF;
+ $mem[$rombase+$memsize-1] = ($crc>>8)&0xFF;
+ }
+ printf $LOG "ROM %s is %06o (0x%04X)\n", $crctype, ($crc) x 2 if $VERBOSE;
+
+ # process data words to actual PROM byte data
+ # put 4bit nibble in low 4b of each 8b data byte, zero the upper 4b
+ # only copy the above instruction portion over
+ for (my $idx = 0; $idx < $memsize<<1; $idx += 4) {
+ my $dat = ($mem[$rombase+($idx>>1)+1]<<8) | ($mem[$rombase+($idx>>1)+0]<<0);
+ $buf[$idx+0] = ($dat&0xE)|(($dat>>8)&0x1); # bits 3 2 1 8
+ $buf[$idx+1] = ($dat>>4)&0xF; # bits 7 6 5 4
+ $buf[$idx+2] = ((($dat>>8)&0xE)|($dat&0x1))^0xC; # bits ~11 ~10 9 0
+ $buf[$idx+3] = (($dat>>12)&0xF)^0x1; # bits 15 14 13 ~12
+ }
+
+} elsif ($romtype eq 'BINA' || $romtype eq 'ASC9') {
+
+ # only copy the above instruction portion over
+ for (my $adr = 0; $adr < $memsize; $adr += 1) {
+ $mem[$rombase+$adr] = $memfill unless defined($mem[$rombase+$adr]);
+ $buf[$adr] = $mem[$rombase+$adr];
+ }
+
+}
+
+if ($VERBOSE) {
+
+ # print checksum of entire device
+ my $chksum = 0; map($chksum += $_, @buf);
+ printf $LOG "ROM checksum is %06o (0x%04X)\n", $chksum, $chksum;
+
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# output the linked/processed binary file image in the desired format
+
+my $OUT = FileHandle->new("> ".$outfile);
+die "Error: can't open output file '$outfile'\n" unless defined $OUT;
+
+if ($romtype eq 'BOOT' || $romtype eq 'DIAG') {
+
+ # output the entire PROM buffer as an intel hex file
+
+ $bytesper = 16 if $bytesper <= 0;
+
+ for (my $idx = 0; $idx < $romsize; $idx += $bytesper) {
+ my $cnt = $idx+$bytesper <= $romsize ? $bytesper : $romsize-$idx; # N bytes or whatever is left
+ my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data
+ my $dat = join('', map(sprintf("%02X",$_),@dat)); # map to ascii text
+ printf $OUT ":%02X%04X%02X%s%02X\n", $cnt, $idx, 0x00, $dat, &chksum($cnt, $idx>>0, $idx>>8, 0x00, @dat);
+ }
+
+ printf $OUT ":%02X%04X%02X%s%02X\n", 0x00, 0x0000, 0x01, '', &chksum(0x0, 0x0000>>0, 0x0000>>8, 0x01);
+
+} elsif ($romtype eq 'BINA') {
+
+ # Loader format consists of blocks, optionally preceded, separated, and
+ # followed by zeroes. Each block consists of:
+ #
+ # 001 ---
+ # 000 |
+ # lo(length) |
+ # hi(length) |
+ # lo(address) > 'length' bytes
+ # hi(address) |
+ # databyte1 |
+ # : |
+ # databyteN ---
+ # checksum
+ #
+ # If the byte length is exactly six, the block is the last on the tape, and
+ # there is no checksum. If the origin is not 000001, then the origin is
+ # the PC at which to start the program.
+
+ $bytesper = 128 if $bytesper <= 0;
+
+ my $start = $program{START}{ADDRESS};
+
+ sub m ($) { $_[0] & 0xFF; }
+
+ # output the entire PROM buffer as a binary loader file
+ for (my $idx = $adrmin; $idx < $adrmax+1; $idx += $bytesper) {
+ my $cnt = $idx+$bytesper <= $adrmax+1 ? $bytesper : $adrmax+1-$idx; # N bytes or whatever is left
+ my @dat = @buf[$idx..($idx+$cnt-1)]; # get the data
+ my $len = $cnt+6;
+ my @rec = (0x01, 0x00, &m($len>>0), &m($len>>8), &m($idx>>0), &m($idx>>8), @dat);
+ print $OUT pack("C*", @rec, &chksum(@rec));
+ }
+ my @end = (0x01, 0x00, 0x06, 0x00, &m($start>>0), &m($start>>8));
+ print $OUT pack("C*", @end, &chksum(@end));
+
+} elsif ($romtype eq 'ASC9') {
+
+ # ascii interface to M9312 console emulator
+
+ sub n ($) { $_[0] & 0xFF; }
+
+ # start program load here
+ printf $OUT "L %o\r\n", $adrmin;
+
+ # output the PROM buffer as an ascii load file
+ for (my $idx = $adrmin; $idx < $adrmax+1; $idx += 2) {
+ printf $OUT "D %06o\r\n", (&n($buf[$idx+1])<<8) | &n($buf[$idx+0]);
+ }
+
+ # start program exec here
+ printf $OUT "L %o\r\nS\r\n", $adrmin;
+
+}
+
+# all done
+$OUT->close;
+
+#----------------------------------------------------------------------------------------------------
+
+# really done
+$LOG->close;
+exit;
+
+#----------------------------------------------------------------------------------------------------
+
+# trim leading/trailing spaces on a string
+
+sub trim ($) {
+
+ my ($str) = @_;
+
+ $str =~ s/\s+$//;
+ $str =~ s/^\s+//;
+
+ return $str;
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# compute checksum (twos complement of the sum of bytes)
+
+sub chksum (@) {
+
+ my $sum = 0;
+
+ map($sum += $_, @_);
+
+ return (-$sum) & 0xFF;
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# RAD50 to ASCII decode
+
+sub rad2asc (@) {
+
+ my @str = split(//, ' ABCDEFGHIJKLMNOPQRSTUVWXYZ$.%0123456789'); # RAD50 character subset
+
+ my $ascii = "";
+ foreach my $rad50 (@_) {
+ $ascii .= $str[int($rad50/1600)%40] . $str[int($rad50/40)%40] . $str[$rad50%40];
+ }
+
+ return $ascii;
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# crc computation routine
+
+sub crc (%) {
+
+ # pass all args by name
+ my %args = @_;
+
+ # all the crcs we know how to compute
+ my %crcdat = ( 'CRC-16' => [ 0xA001, 2, 0x0000, 0x0000 ],
+ 'CRC-32' => [ 0xEDB88320, 4, 0xFFFFFFFF, 0xFFFFFFFF ] );
+
+ # run next byte thru crc computation, return updated crc
+ return $args{-table}[($args{-crc}^$args{-byte}) & 0xFF]^($args{-crc}>>8) if exists($args{-byte});
+
+ # return initial crc value
+ return $crcdat{$args{-name}}->[2] if exists($args{-init});
+
+ # return final crc value xored with xorout
+ return $args{-crc} ^ $crcdat{$args{-name}}->[3] if exists($args{-last});
+
+ # compute the crc lookup table, return a pointer to it
+ if (exists($args{-new})) {
+ my $crctab = [];
+ my $poly = $crcdat{$args{-name}}->[0];
+ foreach my $byte (0..255) {
+ my $data = $byte;
+ foreach (1..8) { $data = ($data>>1) ^ ($data&1 ? $poly : 0); }
+ $$crctab[$byte] = $data;
+ }
+ return $crctab;
+ }
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# read a record from the object file
+
+sub read_rec ($) {
+
+ my ($fh) = @_;
+
+ my ($buf, $cnt, $len, $err) = (0,0,0,0);
+ my @pre = ();
+ my @dat = ();
+ my @suf = ();
+
+ # Object file format consists of blocks, optionally preceded, separated, and
+ # followed by zeroes. Each block consists of:
+ #
+ # 001 ---
+ # 000 |
+ # lo(length) |
+ # hi(length) > 'length' bytes
+ # databyte1 |
+ # : |
+ # databyteN ---
+ # checksum
+ #
+
+ # skip over strings of 0x00; exit OK if hit EOF
+ do { return () unless $cnt = read($fh, $buf, 1); } while (ord($buf) == 0);
+
+ # valid record starts with (1)
+ $err = 1 unless $cnt == 1 && ord($buf) == 1;
+ push(@pre, ord($buf));
+
+ # second byte must be (0)
+ $cnt = read($fh, $buf, 1);
+ $err = 2 unless $cnt == 1 && ord($buf) == 0;
+ push(@pre, ord($buf));
+
+ # third byte is low byte of record length
+ $cnt = read($fh, $buf, 1);
+ $err = 3 unless $cnt == 1;
+ $len = ord($buf);
+ push(@pre, ord($buf));
+
+ # fourth byte is high byte of record length
+ $cnt = read($fh, $buf, 1);
+ $err = 4 unless $cnt == 1;
+ $len += ord($buf)<<8;
+ push(@pre, ord($buf));
+
+ # bytes five thru end-1 are data bytes
+ $cnt = read($fh, $buf, $len-4);
+ $err = 5 unless $cnt == $len-4 && $len >= 4;
+ @dat = unpack("C*", $buf);
+
+ # last byte is checksum
+ $cnt = read($fh, $buf, 1);
+ $err = 6 unless $cnt == 1;
+ my $rcv = ord($buf);
+ push(@suf, ord($buf));
+
+ # output the record if debugging
+ if ($DEBUG) {
+ my $fmt = "%03o";
+ my $n = 16;
+ my $pre = sprintf("RECORD: [%s] ",join(" ",map(sprintf($fmt,$_),@pre)));
+ printf $LOG "\n\n%s", $pre;
+ my $k = length($pre);
+ my @tmp = @dat;
+ while (@tmp > $n) {
+ printf $LOG "%s\n%*s", join(" ",map(sprintf($fmt,$_),splice(@tmp,0,$n))), $k, '';
+ }
+ printf $LOG "%s", join(" ",map(sprintf($fmt,$_),@tmp)) if @tmp;
+ printf $LOG " [%s]\n\n", join(" ",map(sprintf($fmt,$_),@suf));
+ }
+
+ # check we have a well formatted record
+ die sprintf("Error: invalid object file record format (%d)", $err) if $err;
+
+ # compare rcv'ed checksum vs exp'ed checksum
+ my $exp = &chksum(0x01, $len>>0, $len>>8, @dat);
+ die sprintf("Error: Bad checksum exp=0x%02X rcv=0x%02X", $exp, $rcv) unless $exp == $rcv;
+
+ # all is well, return the record
+ return @dat;
+}
+
+#----------------------------------------------------------------------------------------------------
+
+# the end