Mastering K2Development-Tools in 10 Examples

  1. Running k2asm
  2. Running k2asm and k2pp
  3. Makefiles and external tools
  4. Text-encoding
  5. Macros
  6. Libraries
  7. Generating tabels, using irqs, including music+graphic: a small project
  8. Profiling your Routines
  9. Crunching the Program
  10. Working with Diskimages

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!

 

Example 1: running k2asm

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

Example 2: running k2asm and k2pp

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

Example 3: Makefiles and external tools

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

Example 4: Text-encoding

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

Example 5: Macros

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

 


Example 6: Libraries

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;

These hdr-files are automatically produced with the Makefile, and can removed with make clean .

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!

Example 7: A small Project

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!

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!