bugbear Posted May 12, 2017 Share Posted May 12, 2017 In a possibly ambitious bid to learn about A8 programming, I wanted to pick an assembler. In order to confirm that I'd picked an assembler that would suit me, I wanted to see what a large project would look like (1 page examples show you nothing). (thread : http://atariage.com/forums/topic/265271-recommend-me-an-assembler/) And so I decided to take a look at analmux's "Scrolling MCS, PMU & RMT" project from here: http://atariage.com/forums/topic/229764-source-code-released-scrolling-mcs-pmu-rmt/?do=findComment&comment=3203170 Using my prior experience with gnu Make, I knocked up a Makefile a pattern rule for assembly. .SUFFIXES: .SUFFIXES: .asx .inc .h .c .s .lst .map .o .com BB=/home/bugbear/code/cc65-master/bin CC = $(BB)/cc65 LD = $(BB)/ld65 AS = $(BB)/ca65 AFLAGS = -t atari %.o: %.s $(AS) $(AFLAGS) -l $*.lst -o $@ $< But the .asx file weren't going to play nice. I could have hand edited (using search/replace in my favourite editor) but wanted to be able to diff(1) my altered files against the originals to avoid typos. I don't know enough A8 to debug a project this sophisticated in the face of typos. So I created a perl script to do the bulk of the spade work of turning .asx into .s files. In some cases the generated ca65 code relies on macros, of which more anon. #!/usr/bin/perl use strict; use warnings; use Getopt::Std; use Data::Dumper; my %opt; sub usage { print STDERR "Usage: $0 -i <input> -o <output>", "\n"; exit 1; } # a symbol at the start of the line is either # being used as a label, or defined by an equ sub leading_sym { my ($sym, $tail) = @_; #print Dumper($s); if($tail =~ /\s*equ/) { my $ret = "$sym$tail"; $ret =~ s/equ/=/g; return $ret; } else { return "$sym:$tail"; } } sub quot_param { my ($x) = @_; return "{$x}" if($x =~ /,/); return $x; } sub macro2 { my ($com, $x, $y) = @_; return sprintf("%s\t%s, %s", $com, quot_param($x), quot_param($y)); } my $sym = "(?:[_a-zA-Z][_a-zA-Z0-9]*)"; sub convert { my ($s) = @_; $s =~ s/^\*/;/gm; # start of comment $s =~ s/\bdta\b/.byte/gm; $s =~ s/\borg\b/; org/gm; # ORGs are bad, mmkay? $s =~ s/icl\s+'([^']+).asx'/.INCLUDE "$1.s"/gm; $s =~ s/^($sym)(.*)$/leading_sym($1,$2)/egm; $s =~ s/(mva|mwa)\s+([^\s]+)\s+([^\s]+)/macro2($1,$2,$3)/egm; $s =~ s/\b([a-z]{3})[a-z]{3})\b/$1\r\n\t$2/gm; # multiple instructions with : $s =~ s/(ror|asl|lsr)\s*@/$1/gm; # strip implied addressing mode in @ $s =~ s/a\(($sym)\)/addr_l_h($1)/gm; # address as 2 bytes, macro return $s; } sub proc { my ($xasm, $cc65) = @_; my $ih; open($ih, "<", $xasm) or die "cannot open $xasm"; my $all; { local $/; # set input record separator to undef, but in local scope, $all = <$ih>; } close($ih); $all = convert($all); my $oh; open($oh, ">", $cc65) or die "cannot open $cc65"; print $oh $all; close($oh); } getopts("i:o:", \%opt) || usage(); usage() if(!exists($opt{o})); usage() if(!exists($opt{i})); proc($opt{i}, $opt{o}); In the main, this simply turns asx command into their equivalent ca65 commands. The macros (which I intend to continue using in standalone ca65 development) are: ; handle som XASM stuff in a cc65 style .macro mva val, addr lda val sta addr .endmacro ; taken from the documention example in ; "12.4 Detecting parameter types" .macro mwa src, dest .if (.match (.left (1, {src}), #)) ; immediate mode lda #<(.right (.tcount ({src})-1, {src})) sta dest lda #>(.right (.tcount ({src})-1, {src})) sta dest+1 .else ; assume absolute or zero page lda src sta dest lda 1+(src) sta dest+1 .endif .endmacro ; the well known missing instruction; add without carry .macro add val clc adc val .endmacro .define addr_l_h(addr) <(addr), >(addr) with this support in place, I extended the Makefile: %.s: %.asx xasm2ca65.pl -o $@ -i $< So that make will turn an .asx into a .s when needed. There are a few things this perl doesn't handle, which I hand expanded. mva dlinit_data,x (z_dest),y+ I made a macro for mva, but ca65 doesn't do y post increment. There were only 3 instances, which became (e.g.) mva dlinit_data,x (z_dest),y iny The project link line is then (with main.o automatically assembled from main.s, which is automatically made from main.asx) main.com: main.o $(LD) -o $@ -S 0x9000 -vm -m $*.map -C ./sil.cfg $^ we shall talk more of sil.cfg. :-) So far. so good. It assembles but either doesn't link, or links and crashes (TBH, I forget which). Enter ca65, ld65, MEMORY and SEGMENTS. This is where by far the largest effort went. The original .asx files don't really allocate memory. Symbols are directly assigned memory values, and code/data is ORG'd into place. It appears that XASM generates an Atari load chunk for each ORG. ca65 doesn't really put code or data anywhere. It's a "pure" assembler. (sidebar: ca65 has an "org" directive who's behaviour I consider obscure to the point of deception) ca65 puts stuff in SEGMENTS, which (at this stage) are just buckets 'o stuff floating in space. Within a segment, code and data is placed in the obvious way. Variable areas (for scratch tables, pointers etc) are reserved using .res <bytes>. So the original placement of data tables: ; now declared in cc65 TAB segment, using .res ; Dlist equ $8600 ; Regtab equ $8700 ; Lintabl equ $8900 ; Lintabh equ $8980 ; Bartabl equ $8a00 ; Bartabh equ $8b00 ; shapemaskx equ $8c00 becomes: .pushseg .segment "TAB" dlist .res $100 Regtab .res $200 Lintabl .res $80 Lintabh .res $80 Bartabl .res $100 Bartabh .res $100 shapemaskx .res $400 .export dlist, Regtab, Lintabl, Lintabh .export Bartabl, Bartabh, shapemaskx, helpscreen_dlist .popseg I only exported (made public) the symbols so I could see them in the link map, and confirm that they have gone where they should. NB throughout the code, dlist is referred to in lower case, but was defined in XASM as mixed case!! XASM is case insensitive, ca65 is case sensitive. So; how to get stuff to an actual place in a memory map using ca65/ld65. I won't repeat the general docs, just give what I did. Since ld65 is VERY general, it just outputs files. It doesn't output Atari files. However, it's general model is powerful enough that an Atari file can be nicely expressed. Actual area of memory are described by lines in the MEMORY block: here's the memory for TAB (above, and ignore TAB_H for the moment) TAB_H: file = %O, start = $0000, size = $0004; TAB: file = %O, start = $8600, size = $0A00; Lines in the SEGMENTS block of the cfg file put SEGMENTS (from the source files) into MEMORY areas (again, ignore the TAB_H) TAB_H: load = TAB_H, type = ro, optional = yes; TAB: load = TAB, type = rw, define = yes, optional = yes, align=$100; The names of MEMORY and SEGMENT don't need to match, but they can; it spares my creative faculties. The practical upshot of this is that the TAB segment from the .s file is placed at $8600, which can hold upto $A00 bytes, which is what we want. But the Atari load model doesn't just haul a file into RAM starting at zero. It consists of chunks, each of which define memory locations (start and end). About which ld65 knows absolutely nothing. Building headers manually: what TAB_H does.The "define=yes" means that symbols for the memory used by the TAB segments are available during linking. This allows headers to be built like this, in the TAB_H segment. .import __TAB_LOAD__, __TAB_SIZE__ .segment "TAB_H" .word __TAB_LOAD__ .word __TAB_LOAD__ + __TAB_SIZE__ - 1 So that's just a two word thing, calculated from the symbols exported during the link. If you glance up again, you'll see that the TAB_H segment goes in the TAB_H memory location. So this just puts those two numbers in the file, followed by the actual memory image. So we've just made a loadable chunk, with a header. Kewl! It involves a lot of typing though. The original project used 10 ORG directives, and I didn't fancy it. Enter another perl script: #!/usr/bin/perl use strict; use warnings; use Getopt::Std; sub mk_seg { my ($s) = @_; print qq@\t.import __${s}_LOAD__, __${s}_SIZE__ .segment "${s}_H" .word __${s}_LOAD__ .word __${s}_LOAD__ + __${s}_SIZE__ - 1 @; } print "\t.pushseg\n\n"; foreach my $s (@ARGV) { mk_seg($s); } print "\t.popseg\n"; This just spews out header-calculating stuff as above. The Makefile has seg.inc : mk_seg.pl FONT MAPY MAPX PMU TAB > $@ Originally I had more segments than this to correspond to the multiple ORG in the MAPY data. I also wrote a perl script to dump out the segments from the final executable, and used a binary compare to chase out some typos in my macros and perl converter. I kept at this until my main code chunk ($9000) was identical to the xasm compiled one. And it all works! Now I have a large, working, ca65 project. BugBear 3 Quote Link to comment Share on other sites More sharing options...
sanny Posted May 12, 2017 Share Posted May 12, 2017 Very nice explanation. Thank you! Quote Link to comment Share on other sites More sharing options...
bugbear Posted May 12, 2017 Author Share Posted May 12, 2017 I should have said; I am working on this project (at the moment) by editing the .asx files. Since my perl script only replaces xasm stuff, if I put ca65 text in there, it goes straight through to the .s file unaltered, where ca65 is happy to assemble it. BugBear Quote Link to comment Share on other sites More sharing options...
bugbear Posted May 15, 2017 Author Share Posted May 15, 2017 I have attached the project files, in their edited and usable by ca65 state. Just typing "make" will do everything, at least on Linux. BugBear pub_cc65_Silence.zip Quote Link to comment Share on other sites More sharing options...
bugbear Posted May 15, 2017 Author Share Posted May 15, 2017 I should say that this is NOT my notion of a well made ca65 project; it is merely (and only) the smallest edit I could make to the xasm project that actually assembles and runs. This will be the start point for my ca65 exploration. Sadly, it already looks as if the token based macro language of ca65 is not sufficient for me to play around much with "higher level" constructs, so I may have to use another stage of external macro-ing (m4, or something) via the Makefile. BugBear Quote Link to comment Share on other sites More sharing options...
ggn Posted May 17, 2017 Share Posted May 17, 2017 Sadly, it already looks as if the token based macro language of ca65 is not sufficient for me to play around much with "higher level" constructs, so I may have to use another stage of external macro-ing (m4, or something) via the Makefile. Out of curiosity can you mention a couple of examples of what you'd like to do? Quote Link to comment Share on other sites More sharing options...
bugbear Posted May 17, 2017 Author Share Posted May 17, 2017 Out of curiosity can you mention a couple of examples of what you'd like to do? https://en.wikipedia.org/wiki/Assembly_language#Support_for_structured_programming http://wilsonminesco.com/StructureMacros/ BugBear Quote Link to comment Share on other sites More sharing options...
ggn Posted May 17, 2017 Share Posted May 17, 2017 (edited) Without looking that hard it seems like all those "high level" commands can be emulated using macros. But anyway, to each their own. (Edit: at least they look doable in rmac) Edited May 17, 2017 by ggn Quote Link to comment Share on other sites More sharing options...
Wrathchild Posted May 17, 2017 Share Posted May 17, 2017 (edited) Might be worth a look at what was done with NESHLA by the detailed by the writer in this video (source snippets here too). But I'd agree with ggn that ca65 macros should be able to handle much that you would want them too. Edited May 17, 2017 by Wrathchild Quote Link to comment Share on other sites More sharing options...
bugbear Posted May 18, 2017 Author Share Posted May 18, 2017 (edited) The main difficulty that I can't see a way round in ca65 macros is the need for each "structure" to have its own label. In a full textual macro language, like m4, one could simply have a counter (to spawn new labels) and a stack, so that each structure uses its own label on entry/exit. Since m4 can overdefine (AKA update) macro values as it goes along, this is perfectly doable. But the ca65 (and many other assemblers) implement macros at the token level, so I'm not sure it's doable. Here's an example (I'm happy with how I could implement the comparison/conditions, BTW) IF(<=, 10) mva #10, z_cnt lda z_inc IF(>, 20) mva #50, z_hit ENDIF ELSE mva #81, z_cnt ENDIF Which expands to: IF(<=, 10) mva #10, z_cnt lda z_inc IF(>, 20) mva #50, z_hit ENDIF if_2_end: ELSE if_1_else: mva #81, z_cnt ENDIF if_1_end: The difficulty is spawning, and appropriately using, the if_1 and if_2 labels. Similar issue apply to the other structures. If I'm missing an obvious implementation, I'm all ears! BugBear Edited May 18, 2017 by bugbear Quote Link to comment Share on other sites More sharing options...
Irgendwer Posted May 18, 2017 Share Posted May 18, 2017 Since m4 can overdefine (AKA update) macro values as it goes along, this is perfectly doable. Without analysing your example in detail, it sounds that maybe the usage of scopes http://cc65.github.io/doc/ca65.html#toc7.3 solve your problem? Quote Link to comment Share on other sites More sharing options...
sanny Posted May 18, 2017 Share Posted May 18, 2017 Or "Local symbols inside macros" http://cc65.github.io/doc/ca65.html#ss12.6 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.