Preface: This Tutorial is for the usage of the Crossdevelopment-System "k2development". The Audience must be familiar with 6502-Assembly.A basic Knowledge of CommandLineInterfaces (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!
Here come the Sourcecode, it just erases the screen and returns.
.org $8000
lda #32
ldx #0
{
sta $0400,x
sta $0500,x
sta $0600,x
sta $0700,x
dex
bne _cont
}
rts
The curly brackets are an unnamed Scope. That means that labels declared inside that Scope are not visible outside the Scope, except they are .local , .global or .export.
Every Scope does also define 2 labels, _cont at the beginning of the Scope and _break at the end of the Scope.
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
Now 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:
#include "local.inc"
.org org.main
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
If 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:
2004 sys32768
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 = $(NAME).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
.align 32768
;code starts here
I 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:
.encoding "filename"
Character-definitions are in filename, and will be used until another .encoding
text: .enc "Hello, World!",0
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
Although we are using the macro-preprocessor, we have not yet dealt with macros,only with file-inclusion.
Some Guidelines concerning Macros:
Macros with only one Argument 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, to use a Macro (#define FOO 5
) or an Assignment
(BAR=5
), use an Assignment!
Macros with more arguments can be pretty useful:
#define SCREENFONT(screen,font) ((screen)/64) | ((font)/1024)
lda #SCREENFONT($0400,$3800)
sta 53272
Macros are nice, but consume much space in the pogram. Never underestimate the size of your Code, my Advise is to do first size-optimisation/modularisation 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
#include "printlib.hdr"
.org org.main
.encoding "../include/enc/petscii.enc"
print("hello, world!")
rts
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
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
This Library shows also one of k2asm's weirdest Features: DNC.
That are "do-not-care" markings, which tell the assembler that he is free to insert any value he 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 that does the Makefile automatically for us!
printlib.hdr:
print = $8100;
print._end = $8115;
The print label is written into the header-file, because it is a named scope, another Method is to .export labels.
The .local Directive make a label visible in the parent-scope.
We did not use the .global Directive yet, but it makes a label visible to every scope in the actual file.
Please note that there is NO .import-directive in k2asm, the header files (and all their labels) are #included via k2pp!
I hope you can understand the working of this Project on your own, just play around with it to get comfartable with k2development-tools.
This Project plays music (thanks to finn, nice tune!), 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 petty 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
t=0.0
for i in range(0,256):
print ".byte ",int(sin(t)*100+130)
t=t+(2*pi)/255
#pyend
.export ysinus:
#pybegin
t=0.0
for i in range(0,256):
print ".byte ",int(cos(t)*100+150)
t=t+(2*pi)/255
#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
;***** CHIP DETAILS *****
;we use "|" instead of "+" because we are sure how to or dnc-values,
;but not how to add them (we are open for suggestions)
;Video Interface Chip
vic.s0x=vic
vic.s0y=vic|1
vic.s1x=vic|2
vic.s1y=vic|3
vic.s2x=vic|4
vic.s2y=vic|5
vic.s3x=vic|6
vic.s3y=vic|7
vic.s4x=vic|8
vic.s4y=vic|9
vic.s5x=vic|10
vic.s5y=vic|11
vic.s6x=vic|12
vic.s6y=vic|13
vic.s7x=vic|14
vic.s7y=vic|15
vic.sxmsb=vic|$10
vic.cr1=vic|$11
vic.raster=vic|$12
vic.lpx=vic|$13
vic.lpy=vic|$14
vic.sen=vic|$15
vic.cr2=vic|$16
vic.sexy=vic|$17
vic.mem=vic|$18
vic.irq=vic|$19
vic.irqmask=vic|$1a
vic.sprio=vic|$1b
vic.smcm=vic|$1c
vic.sexx=vic|$1d
vic.sscoll=vic|$1e
vic.sbcoll=vic|$1f
vic.border=vic|$20
vic.bg0=vic|$21
vic.bg1=vic|$22
vic.bg2=vic|$23
vic.bg3=vic|$24
vic.smc0=vic|$25
vic.smc1=vic|$26
vic.s0c=vic|$27
vic.s1c=vic|$28
vic.s2c=vic|$29
vic.s3c=vic|$2a
vic.s4c=vic|$2b
vic.s5c=vic|$2c
vic.s6c=vic|$2d
vic.s7c=vic|$2e
;aliases
vic.bg=vic.bg0
vic.badline=vic.cr1
vic.finescroll=vic.cr2
vic.smsb=vic.sxmsb
victoria.silvstedt=vic.sexx
terry.hatcher=vic.sexy
;some values
vic.ECM =%01000000
vic.BMM =%00100000
vic.DEN =%00010000
vic.RSEL=%00001000
vic.MCM =%00010000
vic.CSEL=%00001000
vic.LP =%00001000
vic.SSC =%00000100
vic.SBC =%00000010
vic.RST =%00000001
With these ugly include-files the main-tutorial ends, I hope
you have learnt something, and can write nice C64-Demos and Games!
Have Fun,
Zed Yago
k2asm is a Project by k2
Please forgive my bad english, i am not a native speaker!