Preface

Yet 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 Examples

Content

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

Example 1

Running k2asm

Let'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
  rts
You 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
  }
  rts
The 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 2

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 = 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 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:

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 (#define FOO 5) or an assignment (BAR=5), use an assignment! We follow the strategy to use assignments for label/adressess and macros for most values. For example, the colornames are macros, the vic-registers are assignments.

Macros with more arguments can be pretty useful:

#define SCREENFONT(screen,font) ((screen)/64) | ((font)/1024)

lda #SCREENFONT($0400,$3800)
sta 53272
Also 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 6

Libraries

Macros 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 

#include "printlib.hdr"

.encoding "../include/enc/petscii.enc"

.org org.main
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
    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. Starting a scope like that instructs the assembler to export the label in an export file (use the -x option when invoking k2asm). Additionally a named scope opens a new namespace, all exported symbols from this namespace would be prefixed with the scope's name:

  .scope print {
    .export label:
    .global label2:
  }

The symbol label2 is visible in the whole file as print.label2, also label which is additionally exported as print.label = <adr>. The .local directive in the anonymous scope makes the definition. smod visible in the innermost named scope

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 #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 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 


;***** 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!