PrefaceYet another 6502 Assembler?This often is the very first (but also the most respectful) question when telling other people about k2asm ('try making something useful!' is an other often heard comment). But there a lot of reasons, for doing this. Style, syntax and features didn't change for many years, so we tried to bring a little bit fresh air in assembly language (before it may will die in the near future). This Tutorial is for the usage of the cross development system "k2xtools". The Audience must be familiar with 6502-Assembly. A basic Knowledge of command line interfaces (shells, command.com) is assumed. To test the output of the Assembler, we advise you to use the VICE Emulator, or transfer the object to the real Thing (we are using c2n232 for that matter). This Tutorial is not about Installation of k2development, read the notes in the download section. happy coding - zed yago, howkey, b0rje |
Mastering k2xtools in 10 ExamplesContent |
Example 1Running k2asmLet's start with an easy to understand example program:.org $8000 lda #32 ldx #0 loop: sta $0400,x sta $0500,x sta $0600,x sta $0700,x dex bne loop rtsYou should be quite familiar with this syntax, pseudo instructions start with a '.' and labels are declared as an identifier with a ':' at the end. We decided to leave some common notation and syntactical conventions of the assembly language behind us to introduce a little more sophisticated notation in k2asm, so the intended way of writing assembly code with the k2xtools looks like this:
.org $8000 lda #32 ldx #0 { sta $0400,x sta $0500,x sta $0600,x sta $0700,x dex bne _cont } rtsThe curly brackets are an unnamed Scope. That means that labels declared inside that Scope are not visible outside the Scope. One major improvement are those scopes scopes in a notation you may know from high-level languages like C/C++ or JAVA etc. Declaring a scope has a couple of side effects on the set of declared labels. So at the start (directly after the { ) a label _cont is generated, at the end
(directly before the } ) the label _break is generated.
Generally the grammer is free of required linebreaks, so you may want to write your code in a more compact manner: .org $8000 lda #32 ldx #0 { sta $0400,x sta $0500,x sta $0600,x sta $0700,x dex bne _cont } rts Write the assembler-code into a file named example1.src. Then go to a commandline, enter that directory and type k2asm -o example1.obj example1.src |
Example 2Example 2: running k2asm and k2ppNow we will using the preprocessor. k2pp has many advanced Features, but now we will only look at the most basic Stuff. Change the example-sourcecode to start with:
instead of the .org $8000 line. Create a new file called local.inc in the same directory as the example: org.main = $8000 screen = $0400 You might also want to replace $0400 with screen, giving: lda #32 ldx #0 { sta screen,x sta screen+$100,x sta screen+$200,x sta screen+$300,x dex bne _cont } rts Now that looks better! But if we try to assemble it with k2asm, it just gives us error-messages. That's because k2asm is no macro-assembler (yet), but uses another Program as a preprocessor. You can use k2pp as a standalone Program, or in cunjunction with k2asm. The -k switch enables usage of k2pp without nasty pipes and stuff: k2asm -k -o example2.obj example2.src |
Example 3Makefiles and external toolsIf you are coding only small routines, the former Examples are all you must know. However, because Demos become pretty complicated during the man-years of Development, you should set up a Project. We do want a basic-header, and writing them in assembly is just plain ugly. This is our basic-header,called basicheader.bas:
You need VICE's petcat to produce an Object: petcat -w2 <basicheader.bas >basicheader.obj But we dont want to type this line into the CLI, everytime we build the Project, nor do we want similar commands (pucrunch, gfx-conversion) type in by hand. Thats what Makefiles are for! Makefiles describe the Dependencies between the various Files inside the Project, and how to produce them. After you created a proper Makefile, you only need to get a working Version is type make. make cares about what Files need to be updated then. If something went wrong, first make clean, then make all. Makefile: MAIN = example3.obj OBJECTS = basicheader.obj # name TRG_NAME = test.prg DNC_NAME = test.dnc ############################################################ # default einstellungen ############################################################ ASM = k2asm -k LD = k2link -d $(DNC_NAME) TOKENIZER = petcat -w2 ############################################################ # Main Targets ############################################################ all: $(OBJECTS) $(MAIN) $(LD) -o $(TRG_NAME) $^ ############################## # dependencies ############################## $(MAIN) $(OBJECTS) : Makefile local.inc ############################################################ # other ############################################################ clean: rm -f *.obj *.dnc *.hdr distclean: clean rm -f *~ %.obj : %.src $(ASM) -o $@ -x $(@:%.obj=%.hdr) \ -c $(@:%.obj=%.dnc) $< %.obj : %.bas $(TOKENIZER) <$< >$@ ############################################################ I can't go into further Details about Makefiles, please consult your local manpage. One Hint for Beginners: TABs are absolutely essential in Makefiles, make sure your Editor outputs them. Note: If you don't want to mess around with Makefiles, but need that basicheader, you can uses k2pp's system directive: .org $0801 #system echo "2004 sys 32768" \ | petcat -w2 | c64la -l 0 | bintoasm .align 32768 ;code starts here c64la -l 0 removes the loadaddress bintoasm converts raw data to .byte instructions |
Example 4Text-encodingI hope you are still with me, because now comes the most popular Example of all Programmers: Hello, World! k2asm uses a very flexible Method of using text. Thats because sometimes you want ascii, sometimes petscii, sometimes screen-codes, and sometimes even your own character-definitions because you crunched the font! There are 2 commands for handling text:
Character-definitions are in filename, and will be used until another .encoding
This produces the actual text, as defined by the last .encoding command. BTW, as you might have guessed, the colon after an identifyer declares a label. Naturally, you must also process the Text: ldx #0 { lda text,x beq _break jsr $FFD2 inx jmp _cont } rts |
Example 5MacrosAlthough we are using the macro-preprocessor, we have not yet dealt with macros, only with file inclusion. Some guidelines concerning macros: The existence of macros can be tested with #ifdef. The following technique is called visual profiling and can be used to see the time needed for periodic code (for example raster-interrupts): #define DEBUG ;... #ifdef DEBUG inc $d020 #endif ;some routine #ifdef DEBUG dec $d020 #endif
If you are in doubt, whether to use a macro ( Macros with more arguments can be pretty useful: #define SCREENFONT(screen,font) ((screen)/64) | ((font)/1024) lda #SCREENFONT($0400,$3800) sta 53272Also multiline macros are supported by k2pp, they are declared like that: #begindef WAITX(COUNT) ldx COUNT { dex bne _cont } #enddef ; call the macro: WAITX(#10) |
Example 6LibrariesMacros are nice, but they consume much space in the pogram. Never underestimate the size of your code, my advice is to do first size-optimization/modularization as soon as it exceeds two pages (512 byte). Instead of using a Macro, one can also use a subroutine. Libraries are just a way to put Macros and Subroutines into the same file. local.inc: org.main = $8000 org.printlib = $8100 screen = $0400 main.src: #include "local.inc" #define MACROS_ONLY #include printlib.src: #include "local.inc" .org org.printlib #include <lib/print.inc> As you can see, the file printlib.src is just a collection of Libraries. The actual print-Macro and the subroutines are located in ../lib/print.inc. The sharp brackets mean to search in the k2pp-path, not in the actual Directory. The k2pp-path is defined in the Makefile. ../lib/print.inc: #begindef print(TEXT) { lda #<text ldy #>text jsr print jmp _break text: .enc TEXT,0 } #enddef #ifndef MACROS_ONLY .scope print { sta smod sty smod+1 ldx #0 { .local smod=*+1 lda $????,x beq _break jsr $FFD2 inx jmp _cont } rts } #endif Notice the pseudo instruction .scope print { .export label: .global label2: } The symbol This Library also shows also one of k2asm's weirdest Features: DNC. These are "do-not-care" markings, which tell the assembler that it is free to insert any value it wants there, the outcome will be the same. These values are given to the linker, but as of now, no packer supports them, so the only Advantage is to write cleaner code. How can main.src know the address of the print-subroutine? This is done via header-files. Please note that libraries must be assembled before the routines which call them - but the Makefile automatically takes care of that! printlib.hdr:
print = $8100; print._end = $8115; These hdr-files are automatically produced with the Makefile, and can be removed with make clean . Please note that there is NO .import-directive in k2asm, the
header files (and all their labels) are |
Example 7A small ProjectI hope you can understand the working of this Project on your own, just play around with it to get comfortable with k2development-tools. This Project plays music, and moves an ugly sprite in circles. Makefile: INCDIR = ../include export K2PP_INCLUDEPATH=$(INCDIR) MAIN = main.obj OBJECTS = basicheader.obj TABELS = sinus.obj MUSIC = shortacid.dat DATA = sprite.obj LIB = # name TRG_NAME = test.prg DNC_NAME = test.dnc ############################################# # default einstellungen ############################################# ASM = k2asm -k LD = k2link -d $(DNC_NAME) TOKENIZER = petcat -w2 ############################################# # Main Targets ############################################# all: $(LIB) $(OBJECTS) $(MAIN) $(TABELS) $(DATA) $(MUSIC) $(LD) -o $(TRG_NAME) $^ ############################## # dependencies ############################## $(MAIN) $(OBJECTS) : Makefile local.inc ############################## # other ############################## clean: rm -f *.obj *.dnc *.hdr distclean: clean rm -f *~ %.obj : %.src $(ASM) -o $@ -x $(@:%.obj=%.hdr) \ -c $(@:%.obj=%.dnc) $< %.obj : %.bas $(TOKENIZER) <$< >$@ ############################## local.inc: #define DEBUG music.init=$1000 music.play=$1003 music.tune=0 org.main = $8000 org.printlib = $8100 sinus = $9000 sprite = $0f00 screen = $0400 sprptr = screen+1016 sprite.src: #include "local.inc" .org sprite .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff .byte $ff,$ff,$ff Not what you call a pretty sprite, but graphic conversion is beyond the scope of this Tutorial! sinus.src: #include "local.inc" .org sinus #pybegin from math import sin,cos,pi #pyend .export xsinus: #pybegin for i in range(256): print ".byte ",int(sin(pi*2*i/255)*100+130) #pyend .export ysinus: #pybegin for i in range(256): print ".byte ",int(cos(pi*2*i/255)*100+150) #pyend We are using the built-in python-support in k2pp to generate the sine-table. It is also possible to generate assembler-code this way, this technique is called speedcode or "unrolling" the code. Now comes the actual main.src, notice how short and readable it is: #include "local.inc" #include "sinus.hdr" #include <kernel/c64.inc> .org org.main lda #sprite/64 sta sprptr lda #1 sta vic.sen lda #music.tune jsr music.init sei lda #%01111111 sta cia1.icr lda #1 sta vic.irqmask lda #250 sta vic.raster lda vic.cr1 and #%01111111 sta vic.cr1 lda #<irq1 sta $0314 lda #>irq1 sta $0315 cli rts .scope irq1 { #ifdef DEBUG inc vic.border #endif jsr music.play ldx ptr lda xsinus,x sta vic.s0x lda ysinus,x sta vic.s0y inc ptr #ifdef DEBUG dec vic.border #endif dec vic.irq jmp $ea31 ptr: .byte 0 } Part of its readability is the inclusion of "c64.inc" which declares for example all registers of the vic-chip. c64.inc: ; ** Hardware Definitionen **; ; i do not use .export, if the code uses chips, ; it must #include <kernel/hardware.inc> #ifndef C64_INC #define C64_INC ;**** Memory Management **** pla.ddr=0 pla.dr=1 pla.LORAM =%00000001 pla.HIRAM =%00000010 pla.CHAREN =%00000100 pla.DEFAULT =%00110111 pla.ddr.DEFAULT=%00101111 pla.RAMIO =%00110101 pla.KERNEL =%00110110 pla.ALLRAM =%00110100 ;**** IRQ Vectoren ****; irq = $fffe; nmi = $fffa; ;***** CHIPS ****; #ifdef NO_DNC vic =$D000 sid =$D400 cia1=$DC00 cia2=$DD00 #else vic =%110100????000000 ;d000,d040,d080,d0c0,d100..d3c0 cia1=%11011100????0000 ;dc00,dc10,dc20,..dcf0 cia2=%11011101????0000 ;dd00,dd10..ddf0 ; D 4 0 0 ; / \/ \/ \/ \ ; 7654321076543210 ; 1101010000000000 ;d400 sid =%110101?????00000 ;d400,d420..d7e0 ; 1101011111100000 ;d7e0 ; D 7 E 0 ; / \/ \/ \/ \ ; 7654321076543210 #endif colorram=$d800 io1=$de00 io2=$df00 #include <kernel/vic.inc> #define SID() sid #include <kernel/sid.inc> mouse.x=sid.potx mouse.y=sid.poty #define CIA() cia1 #include <kernel/cia.inc> #undef CIA #define CIA() cia2 #include <kernel/cia.inc> #endif The dnc-markings are not really useful, but I had to use them, because I could! vic.inc: ; ** Hardware Definitionen **; ; i do not use .export, if the code uses chips, ; it must #include With these ugly include-files the main-tutorial ends, I hope you have learnt something, and can write nice C64-Demos and Games! The other Parts of the Tutorial are not yet finished! Get on my nerves at sourceforge or csdb to get em!
Have Fun, Zed Yago k2asm is a Project by k2 Please forgive my bad english, i am not a native speaker! |