#!/usr/bin/perl # X16Asm: DDP-516/H316 assembler version 0.4 -- date: Jan 14 2010 # # This assembler is a 2 or 3 pass assembler which assembles absolute "load mode" code for # the H316/H516 instruction set and supports a subset of the DAP16 assembly language: # all instructions can be translated, but only a few speudo operations can be used. # The assembler is only meant to generate test programs for the FPGA implementation of the # H316/H516. # The optional 1st pass (pass 0) expands tabs in a file (file extention is .s) to the DAP16 # defined colomns for the label, operation, operand and comment field (result is file with # extention with extention .txt). # Pass 1 processes the .txt file and defines the used symbols in the source file. # Pass 2 assembles the source file and produces a listing (file with extension .lst) and # the object output (file with extension .hex). # # The supported speudo operations are not completely compatible with DAP16; i.e. OCT and # DEC can have only a single value: # OCT: [+|-]['] | [+|-] # DEC: [+|-] | [+|-] # # NUM, not DAP16 compatible, allows a simple numeric expression , including hex numbers. # EAC,EAC*, not DAP16 compatible support, like DAC in normal mode 14 bit addresses, the # definition of 15 bit extend mode addresses # # BCI, BSS, BSZ, DAC, EJCT, END, EQU, LOAD and ORG are recognized, of which BCI allows a # string expression and EQU, BSS, BSZ, DAC, and ORG a simple numeric expression , # which generates a single 16 bit number. # allows octal, decimal ,hexadecimal numbers or symbols separated by + and - operators. # # The output is absolute code in hexadecimal form: "
,\n" for each word to load # in memory, and cannot be linked. # my $version = '0.4'; my $DEBUG = 0; # # Author : Theo Engel # Contact: Info@theoengel.nl # # history: # 0.4 (JAN 14 2010) definition of 15 bit extended addresses supported (EAC EAC*) # 0.3 (JAN 10 2010) more compatibility with DAP16: included LOAD and a subset of OCT and DEC # 0.2 (DEC 13 2009) published version # # Usage: X16Asmxx.pl [-nl] .s..|.txt (where xx is some version number) # # source input fields ##################### # the source input record is 80 characters maximum (punched card image) and # divided in 4 fields: label, operation, operand, comment # # default input record layout: # max symbol length = 4 #!*** !**** !*****************!******************************************! #label operation operand comment # # record layout can be changed by adapting the following settings. # default my $tab1 = 0; # label field my $fl1 = 5; # +1 my $tab2 = 5; # operation field my $fl2 = 6; my $tab3 = 11; # operand field my $fl3 = 15; my $tab4 = 26; # comment field my $fl4 = 46; my $tab5 = $fl1+$fl2+$fl3+$fl4; my $symlength=4; # default max symbol length my $f1; # input field1 char 0-5 (l=6) my $f2; # input field2 char 7-18 (l=12) my $f3; # input field3 char 19-34 (l=16) my $f4; # input field4 char 35-79 (l=45) my $ch; # input character my $line = ""; # expanded input record my $cin = 0; # input record char counter my $cout = 0; # expanded record char counter my $quot = 0; # in quoted string flag my $ll; # input record length my $l; # temp my $i; # temp my $w1; my $conlist=1; # listing by default to concole as well my $fi = shift (@ARGV); if($fi eq '-nl') { $conlist=0; $fi = shift (@ARGV); # source file if ($fi eq "" && $fi !~ /\.txt/ && $fi !~ /\.s/) { print "No filename, or wrong extention: $fi\n"; print "Usage: X16Asmxx.pl [-nl] .s..|.txt\n"; exit 1; } } else { if ($fi eq "" && $fi !~ /\.txt/ && $fi !~ /\.s/) { print "No filename, or wrong extention: $fi\n"; print "Usage: X16Asmxx.pl [-nl] .s..|.txt\n"; exit 1; } } print "X16 cross assembler version $version\n"; ####################################################################### # PASS 0 ####################################################################### # If the extension is .s , pass 0 is executed to expand tabs # The result is a file with expanded records with a .txt extension # In case the provided input file has already an .txt extension, pass 0 is # skipped and pass 1 is entered directly. The .txt file is expected to have # the fields formatted in the required colums if( $fi =~ /\.s/) { ($fn,$ty) = split(/\./, $fi); $fo = $fn . ".txt"; if($DEBUG) {print "Expand tabs in $fi to file $fo\n";} if($DEBUG) {print "Start reading $fi (type is $ty) \n";} open (IN,$fi) || die "cannot open inputfile $fi: $!"; open (OUT,">$fo") || die "cannot open ouputfile $fo: $!"; while() { $line = ""; $cin = 0; $cout = 0; $quot = 0; chomp; $ll = length($_); # expand tabs if(($ll > 0) && (substr($_,$cin,1) ne "*")) { while($cin < $ll) { $ch=substr($_,$cin,1); # get next char from input record $cin=$cin+1; if( $ch =~ /\\/ ) # replace tab by one or more spaces { # replace tab by space $line=$line . " "; # expanded input record $cout=$cout+1; if($cout <= $tab2) { while($cout<$tab2) { $line=$line . " "; # expanded input record $cout=$cout+1; } next; } if($cout <= $tab3) { while($cout<$tab3) { $line=$line . " "; # expanded input record $cout=$cout+1; } next; } if($cout <= $tab4) { while($cout<$tab4) { $line=$line . " "; # expanded input record $cout=$cout+1; } next; } } else { $line=$line . $ch; $cout = $cout + 1; } } } else { if($ll > 0) { $line = $_; # comment line } } if($ll > 0) { print OUT $line . "\n"; } } close (IN) || die "can't close $fi: $!"; close (OUT) || die "can't close $fo: $!"; if($DEBUG) {print "$fi expanded into $fo\n";} $fi = $fo; } ($fn,$ty) = split(/\./, $fi); $fo = $fn . ".hex"; $fl = $fn . ".lst"; print "Assemble $fi to object $fo and listing $fl\n"; if($DEBUG) {print "Start reading $fi (pass1)\n";} open (IN,$fi) || die "cannot open inputfile $fi: $!"; my $ln=0; # linenumber my $header=""; # 1st source line is listing header my $mode="A"; # default = absolute mode my $pass=1; # start with pass 1 my $p=0; # location counter my $nerr=0; # error counter my $symbol; # scanned symbol my %sym; # symbol table (name,value) my %symtype; # symbol type (name,type) A # # symbol types # a symbol can only be defined once and can not be redefined # symbols are either predefined or are being defined during pass 1 # This assembler uses only symbols which represent absolut values: # A absolute ####################################################################### # expression evaluator ####################################################################### my $es; # expression string my $les; # expresion string length my $ei; # expresion string index my $eval; # expression result my $etype; # type result value ("A", OR "!" if error) my $exerr; # 1 if error detected during expression evaluation my %cvdig; # digit conversion $cvdig{'0'}=0x0; $cvdig{'1'}=0x1; $cvdig{'2'}=0x2; $cvdig{'3'}=0x3; $cvdig{'4'}=0x4; $cvdig{'5'}=0x5; $cvdig{'6'}=0x6; $cvdig{'7'}=0x7; $cvdig{'8'}=0x8; $cvdig{'9'}=0x9; $cvdig{'A'}=0xa; $cvdig{'B'}=0xb; $cvdig{'C'}=0xc; $cvdig{'D'}=0xd; $cvdig{'E'}=0xe; $cvdig{'F'}=0xf; ####################################################################### # operand syntax scanner and code generator ####################################################################### my %I; # operand syntax template my %G; # code generator of word # operand syntax template: 2 fields of which 1 is optional # # field1 field2 # "" "" empty, or # m mem address, or # sc shift count (decimal) # da device address # 1 index register (optional and only in case field 1 is m) # # m: memory address # m is represented by expression , resulting in an absolute value for m (for , see the subroutine evalexpr). # # the value of m is tranlated into a value of p (page bit) and sa (sector address). # - if m is < 1000 (octal), p is set to 0 and sa is set to m; # - if m is >= 1000 (octal), p is set to 1 and sa is set to the last 9 bits of m. # sc: shift count # , where # == | # # the value of sc is translated into a value of csc (two complement shift count) # the last 6 bits of two complement value of the specified shift count are inserted in the shift instruction # da: device address # , where # == | # the last 10 bits of the specified da value are inserted into the IO instruction # 1 : index # in case the index is specified, the t (tag) bit is set in the memory reference instruction $I{"JMP"} = 'm,@1'; $G{"JMP"} = '0.t.0001.p.sa'; $I{"JMP*"} = 'm,@1'; $G{"JMP*"} = '1.t.0001.p.sa'; $I{"LDA"} = 'm,@1'; $G{"LDA"} = '0.t.0010.p.sa'; $I{"LDA*"} = 'm,@1'; $G{"LDA*"} = '1.t.0010.p.sa'; $I{"ANA"} = 'm,@1'; $G{"ANA"} = '0.t.0011.p.sa'; $I{"ANA*"} = 'm,@1'; $G{"ANA*"} = '1.t.0011.p.sa'; $I{"STA"} = 'm,@1'; $G{"STA"} = '0.t.0100.p.sa'; $I{"STA*"} = 'm,@1'; $G{"STA*"} = '1.t.0100.p.sa'; $I{"ERA"} = 'm,@1'; $G{"ERA"} = '0.t.0101.p.sa'; $I{"ERA*"} = 'm,@1'; $G{"ERA*"} = '1.t.0101.p.sa'; $I{"ADD"} = 'm,@1'; $G{"ADD"} = '0.t.0110.p.sa'; $I{"ADD*"} = 'm,@1'; $G{"ADD*"} = '1.t.0110.p.sa'; $I{"SUB"} = 'm,@1'; $G{"SUB"} = '0.t.0111.p.sa'; $I{"SUB*"} = 'm,@1'; $G{"SUB*"} = '1.t.0111.p.sa'; $I{"JST"} = 'm,@1'; $G{"JST"} = '0.t.1000.p.sa'; $I{"JST*"} = 'm,@1'; $G{"JST*"} = '1.t.1000.p.sa'; $I{"CAS"} = 'm,@1'; $G{"CAS"} = '0.t.1001.p.sa'; $I{"CAS*"} = 'm,@1'; $G{"CAS*"} = '1.t.1001.p.sa'; $I{"IRS"} = 'm,@1'; $G{"IRS"} = '0.t.1010.p.sa'; $I{"IRS*"} = 'm,@1'; $G{"IRS*"} = '1.t.1010.p.sa'; $I{"IMA"} = 'm,@1'; $G{"IMA"} = '0.t.1011.p.sa'; $I{"IMA*"} = 'm,@1'; $G{"IMA*"} = '1.t.1011.p.sa'; $I{"***"} = 'm,@1'; $G{"***"} = '0.t.0000.p.sa'; $I{"****"} = 'm,@1'; $G{"****"} = '1.t.0000.p.sa'; $I{"PZE"} = 'm,@1'; $G{"PZE"} = '0.t.0000.p.sa'; $I{"PZE*"} = 'm,@1'; $G{"PZE*"} = '1.t.0000.p.sa'; $I{"DAC"} = 'm,@1'; $G{"DAC"} = '0.t.m'; $I{"DAC*"} = 'm,@1'; $G{"DAC*"} = '1.t.m'; $I{"EAC"} = 'm'; $G{"EAC"} = '0.m'; # Extend Mode DAC (15 bit address) $I{"EAC*"} = 'm'; $G{"EAC*"} = '1.m'; # Extend Mode DAC* (15 bit address) $I{"LDX"} = 'm'; $G{"LDX"} = '011101.p.sa'; $I{"LDX*"} = 'm'; $G{"LDX*"} = '111101.p.sa'; $I{"STX"} = 'm'; $G{"STX"} = '001101.p.sa'; $I{"STX*"} = 'm'; $G{"STX*"} = '101101.p.sa'; $I{"LRL"} = 'sc'; $G{"LRL"} = '0100000000.csc'; $I{"LRS"} = 'sc'; $G{"LRS"} = '0100000001.csc'; $I{"LRR"} = 'sc'; $G{"LRR"} = '0100000010.csc'; $I{"LGR"} = 'sc'; $G{"LGR"} = '0100000100.csc'; $I{"ARS"} = 'sc'; $G{"ARS"} = '0100000101.csc'; $I{"ARR"} = 'sc'; $G{"ARR"} = '0100000110.csc'; $I{"LLL"} = 'sc'; $G{"LLL"} = '0100001000.csc'; $I{"LLS"} = 'sc'; $G{"LLS"} = '0100001001.csc'; $I{"LLR"} = 'sc'; $G{"LLR"} = '0100001010.csc'; $I{"LGL"} = 'sc'; $G{"LGL"} = '0100001100.csc'; $I{"ALS"} = 'sc'; $G{"ALS"} = '0100001101.csc'; $I{"ALR"} = 'sc'; $G{"ALR"} = '0100001110.csc'; $I{"HLT"} = ''; $G{"HLT"} = '0000000000000000'; $I{"INK"} = ''; $G{"INK"} = '0000000000100011'; $I{"IAB"} = ''; $G{"IAB"} = '0000000010000001'; $I{"ENB"} = ''; $G{"ENB"} = '0000000100000001'; $I{"INH"} = ''; $G{"INH"} = '0000001000000001'; $I{"SKP"} = ''; $G{"SKP"} = '1000000000000000'; $I{"SRC"} = ''; $G{"SRC"} = '1000000000000001'; $I{"SR4"} = ''; $G{"SR4"} = '1000000000000010'; $I{"SR3"} = ''; $G{"SR3"} = '1000000000000100'; $I{"SR2"} = ''; $G{"SR2"} = '1000000000001000'; $I{"SR1"} = ''; $G{"SR1"} = '1000000000010000'; $I{"SSR"} = ''; $G{"SSR"} = '1000000000011110'; $I{"SZE"} = ''; $G{"SZE"} = '1000000000100000'; $I{"SLZ"} = ''; $G{"SLZ"} = '1000000001000000'; $I{"SPL"} = ''; $G{"SPL"} = '1000000100000000'; $I{"NOP"} = ''; $G{"NOP"} = '1000001000000000'; $I{"SSC"} = ''; $G{"SSC"} = '1000001000000001'; $I{"SS4"} = ''; $G{"SS4"} = '1000001000000010'; $I{"SS3"} = ''; $G{"SS3"} = '1000001000000100'; $I{"SS2"} = ''; $G{"SS2"} = '1000001000001000'; $I{"SS1"} = ''; $G{"SS1"} = '1000001000010000'; $I{"SSS"} = ''; $G{"SSS"} = '1000001000011110'; $I{"SNZ"} = ''; $G{"SNZ"} = '1000001000100000'; $I{"SLN"} = ''; $G{"SLN"} = '1000001001000000'; $I{"SMI"} = ''; $G{"SMI"} = '1000001100000000'; $I{"CHS"} = ''; $G{"CHS"} = '1100000000010100'; $I{"CRA"} = ''; $G{"CRA"} = '1100000000100000'; $I{"SSP"} = ''; $G{"SSP"} = '1100000001000000'; $I{"RCB"} = ''; $G{"RCB"} = '1100000010000000'; $I{"CSA"} = ''; $G{"CSA"} = '1100000011010000'; $I{"CMA"} = ''; $G{"CMA"} = '1100000100000001'; $I{"SSM"} = ''; $G{"SSM"} = '1100000101000000'; $I{"SCB"} = ''; $G{"SCB"} = '1100000110000000'; $I{"CAR"} = ''; $G{"CAR"} = '1100001000100100'; $I{"CAL"} = ''; $G{"CAL"} = '1100001000101000'; $I{"ICL"} = ''; $G{"ICL"} = '1100001001100000'; $I{"AOA"} = ''; $G{"AOA"} = '1100001010000110'; $I{"ACA"} = ''; $G{"ACA"} = '1100001010001110'; $I{"ICR"} = ''; $G{"ICR"} = '1100001010100000'; $I{"ICA"} = ''; $G{"ICA"} = '1100001011100000'; $I{"TCA"} = ''; $G{"TCA"} = '1100000100000111'; #140407 iso 141407 $I{"OCP"} = 'da'; $G{"OCP"} = '001100.da'; $I{"INA"} = 'da'; $G{"INA"} = '101100.da'; $I{"OTA"} = 'da'; $G{"OTA"} = '111100.da'; $I{"SKS"} = 'da'; $G{"SKS"} = '011100.da'; $I{"SMK"} = 'da'; $G{"SMK"} = '111100.da'; $I{"OTK"} = ''; $G{"OTK"} = '1111001000010000'; $I{"EXA"} = ''; $G{"EXA"} = '0000000000001011'; $I{"DXA"} = ''; $G{"DXA"} = '0000000000001001'; # code generation variables my $m; # address field (16 bit) my $sa; # address in sector (9 bit) my $oty; # operand type (must be A) my $sc; # shift count field (6 bit) my $da; # device address (10 bit) my $pagebit; # sector bit my $tag; # index bit ######################## bin code generation ########################## my $som = 0201; #start of codeblock my $eom = 0203; #end of codeblock my $space = 0x20; #space my $rub = 0xFF; #rubout my $prl; #program length in words ####################################################################### # ASCII CONVERSION TABLE my %ch; $ch{' '}=0x20; $ch{'!'}=0x21; $ch{"\""}=0x22; $ch{"\#"}=0x23; $ch{'$'}=0x24; $ch{'%'}=0x25; $ch{'&'}=0x26; $ch{"\'"}=0x27; $ch{'('}=0x28; $ch{')'}=0x29; $ch{'*'}=0x2A; $ch{'+'}=0x2B; $ch{','}=0x2C; $ch{'-'}=0x2D; $ch{'.'}=0x2E; $ch{"\/"}=0x2F; $ch{'0'}=0x30; $ch{'1'}=0x31; $ch{'2'}=0x32; $ch{'3'}=0x33; $ch{'4'}=0x34; $ch{'5'}=0x35; $ch{'6'}=0x36; $ch{'7'}=0x37; $ch{'8'}=0x38; $ch{'9'}=0x39; $ch{"\:"}=0x3A; $ch{"\;"}=0x3B; $ch{'<'}=0x3C; $ch{'='}=0x3D; $ch{'>'}=0x3E; $ch{'?'}=0x3F; $ch{'@'}=0x40; $ch{'A'}=0x41; $ch{'B'}=0x42; $ch{'C'}=0x43; $ch{'D'}=0x44; $ch{'E'}=0x45; $ch{'F'}=0x46; $ch{'G'}=0x47; $ch{'H'}=0x48; $ch{'I'}=0x49; $ch{'J'}=0x4A; $ch{'K'}=0x4B; $ch{'L'}=0x4C; $ch{'M'}=0x4D; $ch{'N'}=0x4E; $ch{'O'}=0x4F; $ch{'P'}=0x50; $ch{'Q'}=0x51; $ch{'R'}=0x52; $ch{'S'}=0x53; $ch{'T'}=0x54; $ch{'U'}=0x55; $ch{'V'}=0x56; $ch{'W'}=0x57; $ch{'X'}=0x58; $ch{'Y'}=0x59; $ch{'Z'}=0x5A; $ch{'['}=0x5B; $ch{"\\"}=0x5C; $ch{']'}=0x5D; $ch{'^'}=0x5E; $ch{'_'}=0x5F; ######################## PASS 1 ########################## # input is a text file (extension is .txt) with all tabs are expanded while() { chomp; $line = $_; if(ord(substr($_,0,1)) == 0) { # strip leader $i=0; $l=length($_); while((ord(substr($_,$i,1)) == 0) && $i < $l) { $i=$i+1; } $line = substr($_,$i,$l-$i); } $ln=$ln+1; # incr line number if($ln == 1) { $header = $line; } next if (substr($_,0,1) eq '*'); # ignore comment lines $f1=substr($line,$tab1,$fl1); # label $f1=rtrim($f1); $f2=substr($line,$tab2,$fl2); # operation $f2=rtrim($f2); $f3=substr($line,$tab3,$fl3); # operand $f3=rtrim($f3); if($DEBUG){print "p1fields.$f1.$f2.$f3.\n";} if($f2 eq 'EJCT') { error("Error: Label not allowed in EJCT") if(label()); next; } if($f2 eq 'LOAD') { error("Error: Label not allowed in LOAD") if(label()); next; } if($f2 eq 'ORG') { error("Error: Label not allowed in ORG") if(label()); error("Error: Operand missing in ORG") if($f3 eq ""); $p = evalexpr($f3); if($DEBUG) { print "ORG:" . proct($p); print "\n"; } next; } if($f2 eq 'END') { error("Error: Label not allowed in END") if(label()); last; } # A label is allowed/required for the following directives if($f2 eq 'NUM') # only 1 value allowed { label(); if($f3 ne "") { $l = evalexpr($f3); $p=$p+1; } else { error("Error: Operand missing in NUM"); } next; } if($f2 eq 'OCT') # only 1 value allowed { label(); if($f3 ne "") { $l = evaloct($f3); $p=$p+1; } else { error("Error: Operand missing in OCT"); } next; } if($f2 eq 'DEC') # only 1 value allowed { label(); if($f3 ne "") { $l = evaldec($f3); $p=$p+1; } else { error("Error: Operand missing in DEC"); } next; } if($f2 eq 'BCI') { label(); if($f3 ne "") { $l=evalexpr($f3); $p=$p+$l; } else { error("Error: Operand missing in BCI"); } next; } if($f2 eq 'BSS' || $f2 eq 'BSZ') { label(); if($f3 ne "") { $l=evalexpr($f3); $p=$p+$l; } else { error("Error: Operand missing"); } next; } if($f2 eq 'EQU') { if(! ($symbol=label())) { error("Error: Label missing, or wrong label in EQU, "); } else { if($f3 ne "") { $l=evalexpr($f3); $sym{$symbol} = $l; # define symbol value $symtype{$symbol} = $etype; # and set type } else { error("Error: Operand missing in EQU"); } } next; } # Scan the non-directive operations label(); if($G{$f2} eq undef) { error("Error: Undefined opcode"); next; } $p=$p+1; } # end of pass1 close (IN) || die "can't close $fi: $!"; print "End of Assembler pass 1, number of detected errors = $nerr\n"; if($nerr != 0) { print "Stop, no object file generated\n"; exit 1; } ########################### PASS 2 ########################## if($DEBUG) {print "Start reading $fi (pass2)\n";} open (IN,$fi) || die "cannot open inputfile $fi: $!"; open (BIN, ">$fo") || die "cannot open outputfile $fo: $!"; open (LIST,">$fl") || die "cannot open outputfile $fl: $!"; $ln=0; # source input line number $prl=0; # program length $pass=2; ################# list page format ############## my $page = 1; # listing page number my $lpmax = 40; # lines per listing page my $lp = 0; # listing page line number ################################################# $p=0; # reset the location counter while() { chomp; $line = $_; if(ord(substr($_,0,1)) == 0) { # strip leader $i=0; $l=length($_); while((ord(substr($_,$i,1)) == 0) && $i < $l) { $i=$i+1; } $line = substr($_,$i,$l-$i); } $ln=$ln+1; if (substr($line,0,1) eq '*') { # list comment line list($ln, spaces(14) . $line); next; } $f1=substr($line,$tab1,$fl1); # label $f1=rtrim($f1); $f2=substr($line,$tab2,$fl2); # operation $f2=rtrim($f2); $f3=substr($line,$tab3,$fl3); # operand $f3=rtrim($f3); $f4=substr($line,$tab4,$fl4); # comment $f4=rtrim($f4); if($DEBUG){print "p2fields.$f1.$f2.$f3.$f4\n";} if($f2 eq 'EJCT') { list($ln, spaces(16) . $line); while($lp <= $lpmax) { print LIST "\n"; $lp=$lp+1; } $lp=0; next; } if($f2 eq 'ORG') { $p = evalexpr($f3); list($ln, spaces(16) . $line); next; } if($f2 eq 'LOAD') { list($ln, spaces(16) . $line); next; } if($f2 eq 'END') { list($ln, spaces(16) . $line); last; } if($f2 eq 'NUM') # only 1 value allowed { $l = evalexpr($f3); list($ln, proct($p) . proct($l) . spaces(2) . $line . spaces(72-24-length($line)-2) . prhex(16,$p) . "," . prhex(16,$l)); print BIN prhex(16,$p) . "," . prhex(16,$l) . "\n"; $p=$p+1; next; } if($f2 eq 'OCT') # only 1 value allowed { $l = evaloct($f3); list($ln, proct($p) . proct($l) . spaces(2) . $line . spaces(72-24-length($line)-2) . prhex(16,$p) . "," . prhex(16,$l)); print BIN prhex(16,$p) . "," . prhex(16,$l) . "\n"; $p=$p+1; next; } if($f2 eq 'DEC') # only 1 value allowed { $l = evaldec($f3); list($ln, proct($p) . proct($l) . spaces(2) . $line . spaces(72-24-length($line)-2) . prhex(16,$p) . "," . prhex(16,$l)); print BIN prhex(16,$p) . "," . prhex(16,$l) . "\n"; $p=$p+1; next; } if($f2 eq 'BCI') { $l=evalexpr($f3.$f4); list($ln, spaces(16) . $line); $i=0; nextchar(); # skip , while($i < $l) { $c=ord(nextchar()); if($c == 0) {$c=ord(' ')}; $w1=$c << 8; $c=ord(nextchar()); if($c == 0) {$c=ord(' ')}; $w1=$w1+$c; $w1=$w1 | 0x8080; # set msb of both ASCII's list(0, spaces(8) . proct($p) . proct($w1) . spaces(2) . spaces(72-24-2) . prhex(16,$p) . "," . prhex(16,$w1)); print BIN prhex(16,$p) . "," . prhex(16,$w1) . "\n"; $i=$i+1; $p=$p+1; } next; } if($f2 eq 'BSS' || $f2 eq 'BSZ') # for BSS, like for BSZ, also "zero" words are generated { list($ln, spaces(16) . $line); $l=evalexpr($f3); $i=0; while($i<$l) { list(0, spaces(8) . proct($p) . proct(0) . spaces(2) . spaces(72-24-2) . prhex(16,$p) . "," . prhex(16,0)); print BIN prhex(16,$p) . "," . prhex(16,0) . "\n"; $p=$p+1; $i=$i+1; } next; } if($f2 eq 'EQU') { list($ln, spaces(16) . $line); next; } # Scan machine operations scan($f2,$I{$f2},$f3); # $f2=opcode, $I{$f2}=template, $f3=string # and generate the code $w1=gen1($G{$f2}); list($ln, proct($p) . proct($w1) . spaces(2) . $line . spaces(72-24-length($line)-2) . prhex(16,$p) . "," . prhex(16,$w1)); print BIN prhex(16,$p) . "," . prhex(16,$w1) . "\n"; $p=$p+1; } # force an EJECT to have the symbol table on a new page while($lp <= $lpmax) { print LIST "\n"; $lp=$lp+1; } $lp=0; list(0, " SYMBOL TABLE\n"); my $sb; my $sl=""; $i=0; foreach my $n (sort keys %sym) { $l = length($n); $sb = " $n" . spaces($symlength-$l+1) . proct($sym{$n}) . " " . $symtype{$n}; $sl = $sl . $sb . " "; $i = $i + 1; if($i == 4) # symbol table over 4 columns { list(0, $sl); $sl = ""; $i=0; } } list(0, $sl); # last line symbol table list(0, "\n X16Asm, version $version. End of assembly, $nerr errors."); if(! $conlist) { print "X16Asm, version $version. End of assembly, $nerr errors.\n" } close (IN) || die "can't close $fi: $!"; close (BIN) || die "can't close $fo: $!"; close (LIST) || die "can't close $fl: $!"; if($nerr == 0) { exit; } else { exit 1; } ############################################################################# sub punch { my($a) = @_; my $byte; $byte = pack("C", $a); print BIN $byte; } sub rtrim { my $s = shift; my $l = length($s); my $i = $l; if ($l > 0) { while($i > 0) { if((substr($s,$i-1,1)) ne " ") { last; } else { $i=$i-1; } } } else { return ""; } if($i == 0) { return ""; } else { return substr($s,0,$i); } } sub prhex { # print hex # $n is number of bits # $v is value my $n = shift; my $v = shift; my $s; if($n == 0) { return ""; } elsif($n<5) { $s = sprintf("%01X", $v); } elsif($n<9) { $s = sprintf("%02X", $v); } elsif($n<13) { $s = sprintf("%03X", $v); } elsif($n<17) { $s = sprintf("%04X", $v); } elsif($n<21) { $s = sprintf("%05X", $v); } elsif($n<25) { $s = sprintf("%06X", $v); } elsif($n<29) { $s = sprintf("%07X", $v); } elsif($n<33) { $s = sprintf("%08X", $v); } # print "$s "; return $s; } sub proct { # convert 16bit number into an octal string my $n=shift; $n=$n & 0xFFFF; my $d1=$n >> 15 + "0"; my $d2= (($n >> 12) & 0x7) + "0"; my $d3= (($n >> 9) & 0x7) + "0"; my $d4= (($n >> 6) & 0x7) + "0"; my $d5= (($n >> 3) & 0x7) + "0"; my $d6= ($n & 0x7) + "0"; return " " . $d1 . $d2 . $d3 . $d4 . $d5 . $d6; } sub prdec { # print number # $n is number of digits (string) # $v is value my $n = shift; my $v = shift; my $s; my $format="\%0" . $n . "d"; $s = sprintf($format, $v); # print "$s "; return " $s "; } sub error { my $es = shift; $nerr = $nerr + 1; if($pass == 1) { print prdec("4", $ln) . "$line *** $es\n"; } else { list(0, prdec("4", $ln) . " ************* $es *"); } } sub label { my $s; return "" if $f1 eq ""; if($s=symbol($f1)) { if($sym{$s} eq undef) { $sym{$s} = $p; # default value of symbol = location address $symtype{$s} = $mode; if($DEBUG) {print "sym:$s. mode:$mode. value:" . proct($sym{$s}) . "\n";} return $s; } else { error("Error: Label field contains a double defined symbol $s ,or is not allowed"); } } else { error("Error: Label $f1 not a valid symbol, or is not allowed"); } return ""; } # scan whether $s contains a valid symbol # 1st char must be alpha # next chars alpha|digit sub symbol { my $s=shift; my $l = length($s); my $c; my $i; my $result=""; if($l > $symlength) { return ""; } $i=0; if($l>=1) # minimal 1 character { $c=substr($s,0,1); # get 1st character if($c =~ /[A-Z]/) { # 1st character OK $result=$result . $c; $i=1; while($i < $l) { $c=substr($s,$i,1); # next character $i=$i+1; if($c =~ /[A-Z0-9]/) #/[A-Z0-9]/ { $result=$result . $c; } else { return ""; # not a valid symbol } } return $result; } } return ""; # not a valid symbol } # output listing record sub list { my $ln = shift; # source input record number my $ls = shift; # string to list my $l=0; my $s; # new page ? output listing header on the new page if($lp == 0) { if($conlist) {print "\n\n\n";} print LIST "\n\n\n"; $l = length($header); $s = spaces(22) . $header . spaces(88-22-$l-19) . "PAGE" . prdec(4,$page); if($conlist) {print $s;} print LIST $s; if($conlist) {print "\n\n\n";} print LIST "\n\n\n"; $page = $page + 1; # page number $lp = 3; } if($ln == 0) { # just output the string $s = $ls; } else { # output an assembled record # $s = prdec(4,$ln) . spaces(14) . $ls; $s = prdec(4,$ln) . $ls; } if($conlist) {print $s;} print LIST $s; if($conlist) {print "\n";} print LIST "\n"; $lp = $lp + 1; if($lp == $lpmax) { $lp = 0; # reset internal line counter to force a new page } } # generate a string of n spaces sub spaces { my $n = shift; my $i = 0; my $s = ""; while($i < $n) { $s = $s . " "; $i = $i + 1; } return $s; } ######################### expression evaluator ############################### # provide next character of the expression string sub nextchar { my $c; if($ei < $les) { $c = substr($es,$ei,1); if($DEBUG) {print "nextchar1:$c.\n";} $ei = $ei + 1; return $c; } else { if($DEBUG) {print "nextchar2: \n";} return " "; } } # peek next character of the expression string sub peekchar { my $c; if($ei < $les) { $c = substr($es,$ei,1); if($DEBUG) {print "peek1:$c.\n";} return $c; } else { if($DEBUG) {print "peek2: .\n";} return " "; } } # convert octal string in expression to binary sub cvoct { my $r=0; my $d=0; my $c; if( ($c = peekchar()) =~ /[0-7]/ ) # at least 1 octal digit required { while (peekchar() =~ /[0-7]/) { $c = nextchar(); $d = $cvdig{$c}; $r = ($r << 3) + $d; } if ($r > 0xFFFF) { $exerr = 1; error("Error: Octal number (too big for 16 bit)"); return 0; } return ($r & 0xFFFF); } else { $exerr = 1; error("Error: No octal number"); return 0; } } # convert hex string in expression to binary sub cvhex { my $r=0; my $d=0; my $c; if( ($c = peekchar()) =~ /[0-9A-F]/ ) # at least 1 hex digit required { while (peekchar() =~ /[0-9A-F]/) { $c = nextchar(); $d = $cvdig{$c}; $r = ($r << 4) + $d; } if ($r > 0xFFFF) { $exerr = 1; error("Error in hex number (too big for 16 bit)"); return 0; } return ($r & 0xFFFF); } else { $exerr = 1; error("Error in hex number"); return 0; } } # convert decimal string in expression to binary sub cvdec { my $r=0; my $c; if( ($c = peekchar()) =~ /[0-9]/ ) # at least 1 digit required { while ( (peekchar()) =~ /[0-9]/) { $c = nextchar(); $c = $cvdig{$c}; $r = $r * 10 + $c; } if ($r > 0xFFFF) { $exerr = 1; error("Error: Decimal number (too big for 16 bit)"); return 0; } return ($r & 0xFFFF); } else { $exerr = 1; error("Error: No decimal number"); return 0; } } # calculate a single 16 bit value from an octal number specification in the operand field (field3) # [+|-][']0..7.. | [+|-]predefined symbol sub evaloct { my $c; my $sign = '+'; my $systr = ""; my $r; $es = shift; # expression string $les = length($es); # length expression string $ei = 0; # char index in expression string $eval = 0; # result $etype = ""; # A = Absolute, "!" = Error $exerr = 0; # reset error flag if($DEBUG){print "expr-in1:$es.ei.$ei.les.$les.\n";} if($les <= 0) { $exerr = 1; error("Error: Expected octal number is missing in operand"); } if($DEBUG){print "expr-in2:$es.ei.$ei.les.$les.exerr.$exerr\n";} # expressing must start with either: [0-7] or ' or + or - or [A-Z] $systr = ""; $c = peekchar(); # if($c eq '+') { $sign = '+'; $c=nextchar(); $c=peekchar(); } if($c eq '-') { $sign = '-'; $c=nextchar(); $c=peekchar(); } if($c eq '\'') { # octal number $c=nextchar(); # skip if($sign eq '+') { $eval = cvoct(); } else { $eval = - cvoct(); } } else { if($c =~ /[0-7]/) { # octal number if($sign eq '+') { $eval = cvoct(); } else { $eval = - cvoct(); } } else { if( $c =~ /[A-Z]/) { # symbol $c=nextchar(); $systr=$c; while( peekchar() =~ /[A-Z0-9]/ ) { $c = nextchar(); $systr = $systr . $c; } if(length($systr) > $symlength) { $exerr = 1; error("Error: Symbol $systr in operand longer than $symlength characters"); } if($sym{$systr} ne undef) { if($sign eq '+') { $eval = $sym{$systr}; } else { $eval = - $sym{$systr}; } } else { $exerr = 1; error("Error: Symbol $systr in operand undefined"); } } else { $exerr = 1; error("Error: Malformed octal operand"); } } } if($exerr) { $etype = '!'; } else { $etype = 'A'; } if($DEBUG){print "evaloct-out:" . proct($eval & 0xFFFF) . " type:" . $etype . "\n";} return ($eval = ($eval & 0xFFFF)); } # calculate a single 16 bit value from a decimal number specification in the operand field (field3) # [+|-]0..9.. | [+|-]predefined symbol sub evaldec { my $c; my $sign = '+'; my $systr = ""; my $r; $es = shift; # expression string $les = length($es); # length expression string $ei = 0; # char index in expression string $eval = 0; # result $etype = ""; # A = Absolute, "!" = Error $exerr = 0; # reset error flag if($DEBUG){print "expr-in1:$es.ei.$ei.les.$les.\n";} if($les <= 0) { $exerr = 1; error("Error: Expected decimal number is missing in operand"); } if($DEBUG){print "expr-in2:$es.ei.$ei.les.$les.exerr.$exerr\n";} # expressing must start with either: [0-7] or ' or + or - or [A-Z] $systr = ""; $c = peekchar(); # if($c eq '+') { $sign = '+'; $c=nextchar(); $c=peekchar(); } if($c eq '-') { $sign = '-'; $c=nextchar(); $c=peekchar(); } if($c =~ /[0-9]/) { # octal number if($sign eq '+') { $eval = cvdec(); } else { $eval = - cvdec(); } } else { if( $c =~ /[A-Z]/) { # symbol $c=nextchar(); $systr=$c; while( peekchar() =~ /[A-Z0-9]/ ) { $c = nextchar(); $systr = $systr . $c; } if(length($systr) > $symlength) { $exerr = 1; error("Error: Symbol $systr in operand longer than $symlength characters"); } if($sym{$systr} ne undef) { if($sign eq '+') { $eval = $sym{$systr}; } else { $eval = - $sym{$systr}; } } else { $exerr = 1; error("Error: Symbol $systr in operand undefined"); } } else { $exerr = 1; error("Error: Malformed decimal operand"); } } if($exerr) { $etype = '!'; } else { $etype = 'A'; } if($DEBUG){print "evaldec-out:" . proct($eval & 0xFFFF) . " type:" . $etype . "\n";} return ($eval = ($eval & 0xFFFF)); } # evalexpr # ======== # Calculates a 16 bit absolute value for an expression (-32768 .. 32767) in the operand field (field3) # # =:: [+|-][+|-[+|-]] # =:: | # # =:: | | # =:: 1 or more digits 0..9 # =:: followed by 1 or more digits 0..7 # =:: followed by 1 or more digits 0..F # # # ::= * | ** | identifier of 1 upto 4 characters starting with an alpha and representing a location address value # two special symbols: # * ::= current value of location counter # ** ::= 0 # sub evalexpr { my $c; my $sign = '+'; my $systr = ""; my $r; my $x; my $nterm=3; # three terms max my $term; $es = shift; # expression string $les = length($es); # length expression string $ei = 0; # char index in expression string $eval = 0; # result $etype = ""; # A = Absolute, "!" = Error $exerr = 0; # reset error flag if($DEBUG){print "expr-in1:$es.ei.$ei.les.$les.\n";} if($les <= 0) { $exerr = 1; error("Error: Expected expression is missing in operand"); } if($DEBUG){print "expr-in2:$es.ei.$ei.les.$les.exerr.$exerr\n";} $term=$nterm; # expressing must start with either: *, or [A-Z] or [0-9] or ' or $ or + or - while(($ei < $les) && ($exerr == 0) && ($term > 0)) { $systr = ""; $c = peekchar(); # process term if($c eq '+') { $sign = '+'; $c=nextchar(); $c=peekchar(); } if($c eq '-') { $sign = '-'; $c=nextchar(); $c=peekchar(); } if($c eq '*' ) { nextchar(); $c = peekchar(); if($c eq '*') { nextchar(); $c = peekchar(); if($c eq ' ' || $c eq '+'|| $c eq '-') # symbol ** { $eval = $eval + 0; } else { $exerr = 1; error("Error: invalid symbol **..."); } } elsif($c eq ' ' || $c eq '+'|| $c eq '-') # symbol * { if($sign eq '+') { $eval = $eval + $p; } else { $eval = $eval - $p; } } else { $exerr = 1; error("Error: invalid symbol *..."); } } elsif($c eq '\'') { # octal number $c=nextchar(); # skip if($sign eq '+') { $eval = $eval + cvoct(); } else { $eval = $eval - cvoct(); } } elsif($c eq '$') { # hex number $c=nextchar(); # skip $ if($sign eq '+') { $eval = $eval + cvhex(); } else { $eval = $eval - cvhex(); } } elsif($c =~ /[0-9]/) { # decimal number if($sign eq '+') { $eval = $eval + cvdec(); } else { $eval = $eval - cvdec(); } } elsif( $c =~ /[A-Z]/) { # symbol $c=nextchar(); $systr=$c; while( peekchar() =~ /[A-Z0-9]/ ) { $c = nextchar(); $systr = $systr . $c; } if(length($systr) > $symlength) { $exerr = 1; error("Error: Symbol $systr in operand longer than $symlength characters"); } if($sym{$systr} ne undef) { if($sign eq '+') { $eval = $eval + $sym{$systr}; } else { $eval = $eval - $sym{$systr}; } } else { $exerr = 1; error("Error: Symbol $systr in operand undefined"); } } else { $exerr = $nterm - $term + 1; error("Error: Term $exerr in operand expression malformed"); } # term must be followed by either 'nothing', or a + or - to introduce the next term (or , for BCI) $term=$term+1; $c = peekchar(); if($c eq ' ') { last; } elsif($c eq '+') { next; } elsif($c eq '-') { next; } if($c eq ',') { last; } else { $exerr = 1; error("Error: Malformed operand expression"); } } if($exerr) { $etype = '!'; } else { $etype = 'A'; } if($DEBUG){print "expr-out:" . proct($eval & 0xFFFF) . " type:" . $etype . "\n";} return ($eval = ($eval & 0xFFFF)); } ########################### operand syntax scanner ######################### sub scan { my $o = shift; # operation code my $t = shift; # operand template my $s = shift; # operand to scan my $t1; # t1,t2 template parts my $t2; my $p1; # p1,p2 operand parts my $p2; my $l; ($t1,$t2) = split(/\,/, $t); # split template ($p1,$p2) = split(/\,/, $s); # split operand if($DEBUG){print "t1:$t1.t2:$t2.\n";} if($DEBUG){print "p1:$p1.p2:$p2.\n";} $oty = ''; # scan part 1 $l = length($t1); if($l>0) { if($t1 eq 'm') { $m = evalexpr($p1); $oty = $etype; } elsif($t1 eq 'sc') { $sc = evalexpr($p1); $oty = $etype; if($sc > 32) { error("Error: Shift Count bigger than 32"); } } elsif($t1 eq 'da') { $da = evalexpr($p1); $oty = $etype; if($da > 1023) { error("Error: Device Address/Function bigger than 1023 (10 bit)"); } } else { error("Error: Operand expected"); } } else { if($p1 eq '') { # instruction without 1st operand, ok ; } else { error("Error: No operand expected"); } } # scan part 2 $l = length($t2); if($l>0) { if($t2 eq '@1') { if($p2 eq "1") { $tag = 1; } elsif($p2 eq undef) { $tag = 0; } else { error("Error: Wrong index used"); } } } else { if($p2 ne '') { error("Error: No 2nd operand field expected") } return; } } #$I{"JMP"} = 'm,@1'; $G{"JMP"} = '0.t.0001.p.sa'; #$I{"LDX"} = 'm'; $G{"LDX"} = '010111.p.sa'; #$I{"LRL"} = 'sc'; $G{"LRL"} = '0100000000.csc'; #$I{"TCA"} = ''; $G{"TCA"} = '1100001100000111'; #$I{"OCP"} = 'da'; $G{"OCP"} = '001100.da'; ####################### code generator ########################## # generator of word 1 sub gen1 { my $s = shift; my $c1; my $c2; my $c3; my $c4; my $c5; my $w=0; my $l; my $i; my $b; ($c1,$c2,$c3,$c4,$c5) = split(/\./, $s); if($DEBUG){print "gw1--c1:$c1.c2:$c2.c3:$c3.c4:$c4.c5:$c5.\n";} # part 1 $l = length($c1); if($l>0) { for($i=0; $i<$l; $i++) { $c=substr($c1,$i,1); if($c eq '0') { $b = 0; $w = $w << 1; $w=$w+$b; } elsif($c eq '1') { $b = 1; $w = $w << 1; $w=$w+$b; } else { error("Error: Code gen error w part1 $c1"); } } } # part 2 $l = length($c2); if($l>0) { if($c2 eq 't') # tag bit? { $w = $w << 1; $w = $w + $tag; } elsif($c2 eq 'p') # page bit?(LDX/STX) { $w = $w << 1; if($m < 512) { # address in sector 0 => page bit is 0 $sa=$m & 0x01FF; # save sector address part of operand $pagebit=0; } else { # sector address PC and operand (m) equal? if( ($p & 0xFC00) == ($m & 0xFC00)) { # yes, => set page bit to 1 $pagebit=1; $w=$w+1; $sa=$m & 0x01FF; # save sector address part of operand } else { # error error("Error: Operand address not in current sector or sector 0"); } } } elsif($c2 eq 'csc') { $w = $w << 6; $sc= ((~ $sc) + 1) & 0x003F; # two complement of shift count $w = $w + $sc; } elsif($c2 eq 'da') { $w = $w << 10; $w = $w + $da; } elsif($c2 eq 'm') # EAC and EAC*: m is extended address (15 bit) { $w = $w << 15; # $w = ($w & 0xC000) + ($m & 0x3FFF); # sometimes a negative address is used, so $w = $w | $m; } else { error("Error: Code gen error w part2 $c2"); } } # part 3 $l = length($c3); if($l>0) { if($c3 eq 'sa') { $w = $w << 9; $w = $w + $sa; } elsif($c3 eq 'm') { $w = $w << 14; # $w = ($w & 0xC000) + ($m & 0x3FFF); # sometimes in DAC a negative address is used, so $w = $w | $m; } else { for($i=0; $i<$l; $i++) { $c=substr($c3,$i,1); if($c eq '0') { $b = 0; $w = $w << 1; $w=$w+$b; } elsif($c eq '1') { $b = 1; $w = $w << 1; $w=$w+$b; } else { error("Error: Code gen error w part3 $c3"); } } } } # part 4 $l = length($c4); if($l>0) { if($c4 eq 'p') { $w = $w << 1; if($m < 512) { # address in sector 0 => page bit is 0 $sa=$m & 0x01FF; # save sector address part of operand $pagebit=0; } else { # sector address PC and operand (m) equal? if( ($p & 0xFC00) == ($m & 0xFC00)) { # yes, => set page bit to 1 $pagebit=1; $w=$w+1; $sa=$m & 0x01FF; # save sector address part of operand } else { # error error("Error: Operand address not in current sector or sector 0"); } } } else { error("Error: Code gen error w part4 $c4"); } } # part 5 $l = length($c5); if($l>0) { if($c5 eq 'sa') { $w = $w << 9; $w = $w + $sa; } else { error("Error: Code gen error w part5 $c5"); } } return $w; }