From 132e4ea7e829112c6b42b4005753eeb333171956 Mon Sep 17 00:00:00 2001 From: wfjm Date: Tue, 26 Jul 2022 08:34:43 +0200 Subject: [PATCH] asm-11: add limited macro support --- doc/CHANGELOG.md | 2 + tools/asm-11/tests/test_0300_macro.mac | 76 +++++ tools/bin/asm-11 | 367 +++++++++++++++++++++---- tools/man/man1/asm-11.1 | 29 +- 4 files changed, 420 insertions(+), 54 deletions(-) create mode 100644 tools/asm-11/tests/test_0300_macro.mac mode change 100755 => 100644 tools/bin/asm-11 diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index f19766c0..41b93382 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -30,6 +30,7 @@ The full set of tests is only run for tagged releases. - add fast mac-only verification codes under tool/tcode, integrated with tbrun - add ostest support for rsx11m-31_rk, rsx11m-40_rk and rsx11mp-30_rp oskits - all actively used commands have now a man page +- asm-11 has now limited macro support - Doxygen support now for V1.9.4; remove discontinued Tcl support - build flow Vivado 2022.1 ready; handle synth 8-3331 -> 8-7129 transition - remove Atlys support (only test designs, a w11 design was never done) @@ -42,6 +43,7 @@ The full set of tests is only run for tagged releases. - ci.yml: define TBW_GHDL_OPTS and suppress IEEE package warnings at t=0ms - **/tbrun.yml: since nexys4 not longer available switch to nexys4d - tools/bin + - asm-11: limited macro support (.macro,.endm) - create_disk: -help: print byte size of disk - njobihtm: add -n and -h options - tbrun_tbwrri: fully implement --r(l|b)mon diff --git a/tools/asm-11/tests/test_0300_macro.mac b/tools/asm-11/tests/test_0300_macro.mac new file mode 100644 index 00000000..061de40e --- /dev/null +++ b/tools/asm-11/tests/test_0300_macro.mac @@ -0,0 +1,76 @@ +; $Id: test_0300_macro.mac 1262 2022-07-25 09:44:55Z mueller $ +; SPDX-License-Identifier: GPL-3.0-or-later +; Copyright 2019- by Walter F.J. Mueller +; +; test .macro basics +; + .asect + .blkw 400 + +; list macro expansion + + .list me + +; define and use simple macros + + .macro scall,dst + jsr pc,dst + .endm + .macro sret + rts pc + .endm + .macro push,src + mov src,-(sp) + .endm + .macro pop,dst + mov (sp)+,dst + .endm + +t01: scall t01sub + halt +1$: ;;!! 001006: + +t01sub: push r0 + push r1 + pop r1 + pop r0 + sret +1$: ;;!! 001020: + +; macro with defaults and auto-label + + .macro scopy,src,dst=#t02tmp,?lbl + mov src,r0 + mov dst,r1 +lbl: movb (r0)+,(r1)+ ;;!! 112021 + bne lbl ;;!! 001376 + .endm scopy + + . = 02000 +t02: scopy #t02a1+<2*2>,#t02buf +1$: ;;!! 002014: + scopy #t02a2 +2$: ;;!! 002030: + mov #t02a1,r5 + scopy r5 +3$: ;;!! 002046: +; +t02a1: .asciz /1234567890/ +t02a2: .asciz /abcdefghij/ +t02buf: .blkb 32. +t02tmp: .blkb 32. + +; nested macro calls + + .macro bcopy,src,dst + push r0 + push r1 + scopy #src,#dst + pop r1 + pop r0 + .endm + + . = 3000 +t03: bcopy t02a1,t02tmp +1$: ;;!! 003024: + .end diff --git a/tools/bin/asm-11 b/tools/bin/asm-11 old mode 100755 new mode 100644 index 64307937..8678e9bf --- a/tools/bin/asm-11 +++ b/tools/bin/asm-11 @@ -1,19 +1,12 @@ #!/usr/bin/perl -w -# $Id: asm-11 1138 2019-04-26 08:14:56Z mueller $ -# +# $Id: asm-11 1236 2022-05-14 10:11:35Z mueller $ +# SPDX-License-Identifier: GPL-3.0-or-later # Copyright 2013-2019 by Walter F.J. Mueller # -# This program is free software; you may redistribute and/or modify it under -# the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY -# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# for complete details. -# # Revision History: # Date Rev Version Comment +# 2019-07-13 1189 1.1.1 drop superfluous exists for $opts +# 2019-05-25 1152 1.1 add .macro,.endm,.list,.nlist # 2019-04-25 1138 1.0.7 print lines with errors to stderr unless -lst seen # 2019-04-19 1133 1.0.6 .end directive autocreates '...end' label # 2018-11-03 1065 1.0.5 add and use bailout @@ -35,6 +28,7 @@ use Getopt::Long; use constant TMASK_STRING => 0x0001; use constant TMASK_STRINGEXP => 0x0002; +use constant TMASK_MACROARG => 0x0004; my %opts = (); @@ -66,6 +60,10 @@ my %pst = ( '.even' => {typ=>'dir'}, # '.odd' => {typ=>'dir'}, # '.asect' => {typ=>'dir'}, # + '.macro' => {typ=>'dir'}, # + '.endm' => {typ=>'dir'}, # + '.list' => {typ=>'dir'}, # + '.nlist' => {typ=>'dir'}, # '.end' => {typ=>'dir'}, # #register defs 'r0' => {typ=>'reg', val=>0}, @@ -253,6 +251,9 @@ my $llbl_ascope = 0; # annonymous local label scope count # macro table my %mst; +my $mdefnam; # macro currently being defined +my @mdefstk; # open .macro,.rept defs stack +my $mautolbl = 30000; # auto-generated label my @flist; # list of filenames my $fstem; # stem or last file name @@ -278,7 +279,7 @@ my @out_data; # output data autoflush STDOUT 1 if (-p STDOUT); # autoflush if output into pipe -if (exists $opts{help}) { +if ($opts{help}) { print_help(); exit 0; } @@ -384,24 +385,7 @@ sub read_file { chomp; my $line = $_; $lineno += 1; - my $rl = parse_line($fileno, $lineno, $line); - dump_rl($rl) if $opts{tpass1}; - push @src, $rl; - - # handle .include - if (defined $$rl{oper} && $$rl{oper} eq '.include' && defined $$rl{ifile}) { - my $fnam = $$rl{ifile}; - unless ($fnam =~ m|^/|) { - foreach (@{$opts{I}}) { - if (-r "$_/$fnam") { - $fnam = "$_/$fnam"; - last; - } - } - } - read_file($fnam); - } - + pass1_line($fileno, $lineno, $line); } return; @@ -409,6 +393,57 @@ sub read_file { #------------------------------------------------------------------------------- +sub exec_macro { + my ($rmexec) = @_; + my $mname = $rmexec->{name}; + foreach my $rmline (@{$mst{$mname}{body}}) { + my $lrest = $rmline->{line}; + my $fileno = $rmline->{fileno}; + my $lineno = $rmline->{lineno}; + my $line = ''; + while ($lrest =~ m/([a-zA-Z\$\.][a-zA-Z0-9\$\.]*)/) { + $line .= $`; + $line .= (defined $rmexec->{vals}{$1}) ? $rmexec->{vals}{$1} : $1; + $lrest = $'; + } + $line .= $lrest; + pass1_line($fileno, $lineno, $line); + } + return; +} + +#------------------------------------------------------------------------------- + +sub pass1_line { + my ($fileno,$lineno,$line) = @_; + + my $rl = parse_line($fileno, $lineno, $line); + dump_rl($rl) if $opts{tpass1}; + push @src, $rl; + + # handle .include + if (defined $$rl{oper} && $$rl{oper} eq '.include' && defined $$rl{ifile}) { + my $fnam = $$rl{ifile}; + unless ($fnam =~ m|^/|) { + foreach (@{$opts{I}}) { + if (-r "$_/$fnam") { + $fnam = "$_/$fnam"; + last; + } + } + } + read_file($fnam); + } + + # handle macro expansions + if (defined $$rl{oper} && defined $$rl{mexec}) { + exec_macro($$rl{mexec}); + } + return; +} + +#------------------------------------------------------------------------------- + sub parse_line { my ($fileno,$lineno,$line) = @_; @@ -466,9 +501,45 @@ sub parse_line { } } + if (defined $mdefnam) { # handle .macro,... definitions + my $ltrim = $line; + my $mdefend; + $ltrim =~ s/^[a-zA-Z\$\.][a-zA-Z0-9\$\.]*://; # trim label + $ltrim =~ s/^[0-9]+\$]://; # trim local label + $ltrim =~ s/^\s*//; # trim leading white + if ($ltrim =~ m/^(\.[a-zA-Z\$\.][a-zA-Z0-9\$\.]*)/) { # directive seen + my $dnam = $1; + if ($dnam =~ m/^\.(macro|rept)/) { # nested .macro etc seen + push @mdefstk, $dnam; + } elsif ($dnam =~ m/^\.(endm|endr)/) { # .endm or .endr seen + if (scalar(@mdefstk) > 1) { + pop @mdefstk; + } else { + $mdefend = 1; + } + } + } + unless ($mdefend) { + push @{$mst{$mdefnam}{body}}, {line => $l{line}, + fileno => $l{fileno}, + lineno => $l{lineno} + }; + return \%l; + } + } + while (1) { if ($opts{tparse}) { - printf "-- state = $state"; + printf "-- state = %-8s",$state; + my $rest = ''; + foreach my $t (@t_pushback) { $rest .= $t->{val} if defined $t->{val}; } + foreach my $c (@{$l{cl}}) { $rest .= $c; } + $rest =~ s/\s+/ /g; + if (length($rest)>17) { + $rest = substr($rest,0,17) . '...'; + } + while (length($rest) < 20) { $rest .= ' ' } + printf "@ \"%s\"", $rest; printf ", nest = %d", scalar(@e_pbeg) if $state =~ m/^e_/; print "\n"; } @@ -485,6 +556,9 @@ sub parse_line { # directive name seen ? if (exists $pst{$$rt{val}} && $pst{$$rt{val}}{typ} eq 'dir') { $state = 'oper'; + # macro name seen ? + } elsif (exists $mst{$$rt{val}}) { + $state = 'oper'; # otherwise check for label or assignment } else { @@ -520,7 +594,7 @@ sub parse_line { } } - # anything else seen, treat a implicit .word + # anything else seen, treat as implicit .word } else { pushback_token(\%l); $state = 'iword'; @@ -546,21 +620,21 @@ sub parse_line { my $rs = $pst{$op}; if ($$rs{typ} eq 'dir') { # directives ------------------ $d_dire = $op; - if ($op eq '.word' || # .word - $op eq '.byte') { # .byte + if ($op eq '.word' || # .word ---------------- + $op eq '.byte') { # .byte ---------------- $state = 'dl_beg'; - } elsif ($op eq '.blkw' || # .blkw - $op eq '.blkb') { # .blkb + } elsif ($op eq '.blkw' || # .blkw ---------------- + $op eq '.blkb') { # .blkb ---------------- $state = 'dl_beg'; - } elsif ($op eq '.ascii' || # .ascii - $op eq '.asciz') { # .asciz + } elsif ($op eq '.ascii' || # .ascii --------------- + $op eq '.asciz') { # .asciz --------------- $tmask = TMASK_STRING; $state = 'al_next'; - } elsif ($op eq '.even' || # .even - $op eq '.odd') { # .odd + } elsif ($op eq '.even' || # .even ---------------- + $op eq '.odd') { # .odd ----------------- my $dot = getdot(); my $inc = 0; $inc = 1 if $op eq '.even' && ($dot&01)==1; @@ -571,12 +645,12 @@ sub parse_line { $l{lstdot} = 1; $state = 'end'; - } elsif ($op eq '.asect') { # .asect + } elsif ($op eq '.asect') { # .asect --------------- # .asect is currently a noop because asect is start default $l{lstdot} = 1; $state = 'end'; - } elsif ($op eq '.include') { # .include + } elsif ($op eq '.include') { # .include ------------- $rt = get_token(\%l, TMASK_STRING); if ($$rt{tag} eq 'STR') { my $ifile = $$rt{val}; @@ -591,7 +665,82 @@ sub parse_line { $state = 'q'; } - } elsif ($op eq '.end') { # .end + } elsif ($op eq '.macro') { # .macro --------------- + $rt = get_token(\%l, $tmask); + if ($$rt{tag} eq 'SYM') { + $mdefnam = $$rt{val}; + $mst{$mdefnam} = {name => $mdefnam, + typ => '.macro', + args => [], + body => [] + }; + my $mod; + $state = 'end'; + while (1) { # loop over argument definitions + $rt = get_token(\%l, TMASK_MACROARG); + last if $$rt{tag} eq 'EOL'; + next if $$rt{tag} eq 'DEL'; # FIXME_code: that's likely not OK + if ($$rt{tag} eq 'MMOD') { # modifier seen ? + $mod = $$rt{val}; + $rt = get_token(\%l, TMASK_MACROARG); + if ($$rt{tag} eq 'SYM') { # ensure next token is symbol + pushback_token(\%l); + next; + } else { + $state = 'q'; + last; + } + } + if ($$rt{tag} eq 'SYM') { # key name seen + my $key = $$rt{val}; + my $val; + $rt = get_token(\%l, TMASK_MACROARG); + if ($$rt{tag} eq 'ASS') { # is it key=val pair ? + $rt = get_token(\%l, TMASK_MACROARG); + if ($$rt{tag} eq 'SYM' || $$rt{tag} eq 'MVAL') { + $val = $$rt{val}; + } elsif ($$rt{tag} eq 'DEL' || $$rt{tag} eq 'EOL') { + $val = ''; + } else { + $state = 'q'; + last; + } + } else { + pushback_token(\%l); + } + push @{$mst{$mdefnam}{args}}, {key => $key, + val => $val, + mod => $mod + }; + } else { + $state = 'q'; + last; + } + } + } else { + $state = 'q'; + } + + } elsif ($op eq '.endm') { # .endm ---------------- + $rt = get_token(\%l, $tmask); + if ($$rt{tag} eq 'EOL' || $$rt{tag} eq 'SYM') { + add_err(\%l, 'A') if $$rt{tag} eq 'SYM' && $$rt{val} ne $mdefnam; + $mdefnam = undef; + $state = 'end'; + } else { + $state = 'q'; + } + + } elsif ($op eq '.list' || # .list ---------------- + $op eq '.nlist') { # .nlist --------------- + while (1) { # loop over options + $rt = get_token(\%l, $tmask); + last if $$rt{tag} eq 'EOL'; + next if $$rt{tag} eq 'DEL'; # FIXME_code: check delimiter + # FIXME_code: handle .list/.nlist + } + $state = 'end'; + } elsif ($op eq '.end') { # .end ----------------- $state = 'dl_beg'; } else { @@ -621,8 +770,41 @@ sub parse_line { } } - } else { # oper noy in pst --> implicit .word - pushback_token(\%l); + + } elsif (exists $mst{$op}) { # op in mst -> expand macro --- + my %mexec = (name => $op, + keys => [], + vals => {} + ); + foreach my $marg (@{$mst{$op}{args}}) { + push @{$mexec{keys}}, $marg->{key}; + $mexec{vals}{$marg->{key}} = $marg->{val}; # set defaults + } + my $iarg = 0; + while (1) { + $rt = get_token(\%l, TMASK_MACROARG); + last if $$rt{tag} eq 'EOL'; + next if $$rt{tag} eq 'DEL'; # FIXME_code: that's for sure not OK + if ($$rt{tag} eq 'SYM' || $$rt{tag} eq 'MVAL' || + $$rt{tag} eq 'MSTR') { + my $key = $mexec{keys}[$iarg]; # FIXME_code: check valid index ! + my $val = $$rt{val}; # FIXME_code: strip <> or ^// ! + $mexec{vals}{$key} = $val; + $iarg += 1; + } + } + # finally setup auto-generated labels + foreach my $marg (@{$mst{$op}{args}}) { + next unless defined $marg->{mod} && $marg->{mod} eq '?'; + my $key = $marg->{key}; + next if defined $mexec{vals}{$key} && $mexec{vals}{$key} ne ''; + $mexec{vals}{$key} = sprintf '%5d$', $mautolbl++; + } + $l{mexec} = \%mexec; + $state = 'end'; + + } else { # oper not in pst and mst ----- + pushback_token(\%l); # --> implicit .word $state = 'iword'; } @@ -1416,11 +1598,60 @@ sub get_token1 { $str .= $c; return finish_token(\%t, tag=> 'STR', val=> $str) if $c eq $del; } - add_err($rl, 'A'); + add_err($rl, 'A'); # indeed 'A' error, not 'Q' !! return finish_token(\%t, tag=> 'STR', val=> $str); } } + if ($tmask & TMASK_MACROARG) { + if ($c eq '<' || $c eq '^') { # <...> and ^/..../ + my $del = shift @$rcl; + my $str = $del; + if ($del eq '^') { + $del = shift @$rcl; + if (not defined $del) { + add_err($rl, 'A'); + return finish_token(\%t, tag=> 'MSTR', val=> $str); + } + $str .= $del; + } + while (scalar(@$rcl)) { + my $c = shift @$rcl; + $str .= $c; + return finish_token(\%t, tag=> 'MSTR', val=> $str) if $c eq $del; + } + add_err($rl, 'A'); + return finish_token(\%t, tag=> 'MSTR', val=> $str); + } elsif ($c =~ m/[a-zA-Z\$\.]/) { # symbol names + while (scalar(@$rcl)) { + last if ($$rcl[0] !~ m/[a-zA-Z0-9\$\.]/); + $val .= shift @$rcl; + } + if ((not defined $$rcl[0]) || $$rcl[0]=~ m/(\s|=|,|;)/) { # delimiter seen + return finish_token(\%t, tag=> 'SYM', val=> $val); # it's a symbol + } else { # else swallow rest + while (scalar(@$rcl)) { + last if $$rcl[0] =~ m/(\s|,|;)/; # stop at white, comma, comment + $val .= shift @$rcl; + } + return finish_token(\%t, tag=> 'MVAL', val=> $val); + } + } elsif ($c eq '?' || $c eq '\\') { # ? or \ modifiers + return finish_token(\%t, tag=> 'MMOD', val=> shift @$rcl); + } elsif ($c eq '=') { # = key/val + return finish_token(\%t, tag=> 'ASS', val=> shift @$rcl); + } elsif ($c eq ',') { # ' seperator + return finish_token(\%t, tag=> 'DEL', val=> shift @$rcl); + } else { + $val = shift @$rcl; + while (scalar(@$rcl)) { + last if $$rcl[0] =~ m/(\s|,|;)/; # stop at white, comma, comment + $val .= shift @$rcl; + } + return finish_token(\%t, tag=> 'MVAL', val=> $val); + } + } + # looks like symbol ? if ($c =~ m/[a-zA-Z\$\.]/) { while (scalar(@$rcl)) { @@ -2317,7 +2548,7 @@ sub dump_rl { } } if (defined $$rl{opcode}) { - printf " code: %6.6o,fmt=%-2s", $$rl{opcode}, $$rl{opfmt}; + printf " code : %6.6o,fmt=%-2s", $$rl{opcode}, $$rl{opfmt}; if (defined $$rl{o1mod}) { printf ", o1=%s%s", $$rl{o1mod},$$rl{o1reg}; printf ",ei=%d:%d,val=%s", $$rl{o1ebeg}, $$rl{o1eend}, @@ -2336,17 +2567,31 @@ sub dump_rl { print "\n"; } if (scalar(@{$$rl{outw}})) { - print " outw:"; + print " outw :"; foreach (@{$$rl{outw}}) { printf " %6.6o", $_; } print "\n"; } if (scalar(@{$$rl{outb}})) { - print " outb:"; + print " outb :"; foreach (@{$$rl{outb}}) { printf " %3.3o", $_; } print "\n"; } + if (defined $$rl{cl}) { + my $str = join '',@{$$rl{cl}}; + print " cl : $str\n" if length($str); + } + if (defined $$rl{mexec}) { + printf " mexec : %-6s : ", $$rl{mexec}{name}; + my $del = ''; + foreach my $key (@{$$rl{mexec}{keys}}) { + printf '%s%s', $del,$key; + printf '=%s', $$rl{mexec}{vals}{$key} if defined $$rl{mexec}{vals}{$key}; + $del = ','; + } + printf "\n"; + } foreach my $key (sort keys %{$rl}) { - next if $key =~ m/^(line|err|typ|oper|lineno|psect|dot|opcode|opfmt|o[12](mod|reg|ebeg|eend)|ebeg|eend|tl|delist|outw|outb)$/; + next if $key =~ m/^(line|err|typ|oper|lineno|psect|dot|opcode|opfmt|o[12](mod|reg|ebeg|eend)|ebeg|eend|tl|delist|outw|outb|cl|mexec)$/; printf " %-6s: %s\n", $key, savestr($$rl{$key}); } return; @@ -2377,6 +2622,26 @@ sub dump_sym { $lst{$key}{psect}, save66o($lst{$key}{val}); } + my @mlist = sort keys %mst; + if (scalar @mlist) { + print "\n"; + print "macro typ body argument-list\n"; + print "------ ------ ---- -------------\n"; + foreach my $mnam (@mlist) { + printf '%-6s %-6s %4d ', $mnam, + $mst{$mnam}{typ}, scalar(@{$mst{$mnam}{body}}); + my $del = ''; + foreach my $arg (@{$mst{$mnam}{args}}) { + printf '%s', $del; + printf '%s',$arg->{mod} if defined $arg->{mod}; + printf '%s',$arg->{key}; + printf '=%s',$arg->{val} if defined $arg->{val}; + $del = ','; + } + print "\n"; + } + } + return; } @@ -2436,7 +2701,7 @@ sub print_help { print " --lst create listing (default file name)\n"; print " --olst=fnam create listing (concrete file name)\n"; print " --lda create absolute loader output (default file name)\n"; - print " --olda create absolute loader output (concrete file name)\n"; + print " --olda=fnam create absolute loader output (concrete file name)\n"; print " --cof create compound output (default file name)\n"; print " --ocof=fnam create compound output (concrete file name)\n"; print " --lsm create lsmem style memory dump (default file name)\n"; diff --git a/tools/man/man1/asm-11.1 b/tools/man/man1/asm-11.1 index 41eb0948..75e7b735 100644 --- a/tools/man/man1/asm-11.1 +++ b/tools/man/man1/asm-11.1 @@ -1,11 +1,11 @@ .\" -*- nroff -*- -.\" $Id: asm-11.1 1237 2022-05-15 07:51:47Z mueller $ +.\" $Id: asm-11.1 1262 2022-07-25 09:44:55Z mueller $ .\" SPDX-License-Identifier: GPL-3.0-or-later .\" Copyright 2013-2022 by Walter F.J. Mueller .\" .\" ------------------------------------------------------------------ . -.TH ASM-11 1 2019-04-19 "Retro Project" "Retro Project Manual" +.TH ASM-11 1 2019-05-25 "Retro Project" "Retro Project Manual" .\" ------------------------------------------------------------------ .SH NAME asm-11 \- simple assembler for MACRO-11 style PDP-11 code @@ -61,13 +61,36 @@ Activated with the \fB\-\-lst\fP or \fB\-\-olst\fP options. .blkw allocate words of storage .byte store bytes of data .end end of source + .endm end of macro .even ensure word aligment .include include another source file + .list parsed but otherwise ignored; me always enabled + .nlist parsed but otherwise ignored + .macro subset of macro functionality .odd align to odd byte address .word store words of data .EE -and thus neither macro, nor conditional assembly, nor psect support. +and thus restricted macro support and neither conditional assembly, +nor psect support. +. +.SS Level of macro support +Enough for simple macros and useful for writing test benches. +Auto-labels are supported (?name syntax). Main limitations: +.RS 2 +.PD 0 +.IP "-" 2 +no \\var support (pass by value) +.IP "-" +only positional parameters +.IP "-" +no concatination support +.IP "-" +no .mexit, .mdelete, .mcall support +.PD +.RE +.PP + . .\" ------------------------------------------------------------------ .SH OPTIONS