Comments
K65 uses C-like comments.
// this is a line comment
/* this is a
block comment */
Variable declaration
Variables in K65 are names given to chosen memory addresses. Examples of variable declaration:
var foo=0x80 // declares 'foo' at address 0x80
var foo2 // declares 'foo2' at address 0x81 (next after previous var)
var foo3, foo4 // multiple declarations per line allowed
var bar[10], bar2[10] // [] specifies variable size in bytes (address increment for next var)
var bar3 ? // adding '?' at the end of var declaration makes compiler print var addresses
Constant declaration
The best way of defining constants is using the evaluator. Constants defined this way can be changed at any moment during compilation. Constants can be any value of floating point type. When used within 6502 instruction, they are converted to single byte by rounding to nearest integer and AND
-ing with 0xFF
(this way negative values are represented in U2 form).
Examples:
[ // square brace starts evaluator expression
MY_CONSTANT = 5, // define constants
SOME_NUMBER = 0x13
] // end of evaluator expression
Labels
A label can be placed at the beginning of a statement. During assembly, the label is assigned the current value of the active location counter and serves as an instruction operand. There are two types of lables: global and local. Examples below.
Global
var SCREEN=0x400
main {
x=0 {
a=hello,x z-?{ SCREEN,x=a x++ }
} z-?
return
}
data text_data {
charset ".ABCDEFGHIJKLMNOPQRSTUVWXYZ..... "
hello: "HELLO WORLD" 0
}
Local
func draw_level {
.LV_TO_DRAW+1=a=p_current_lv .LV_TO_DRAW+2=a=p_current_lv+1
.LT+1=.LD+1=a=&<screen_1+224 .RT+1=.RD+1=a=&<screen_1+225
.LT+2=.RT+2=a=&>screen_1+0x100 .LD+2=.RD+2=a=&>screen_2+0x100
y=[LV_SIZE] {
.LV_TO_DRAW: a=levels,y
x=a .LT: screen_1=x x++ .RT: screen_1+1=x
a|0x20
x=a .LD: screen_2=x x++ .RD: screen_2+1=x
c+ a=.LT+1 a-2 .LT+1=.LD+1=a c-?{ .LT+2-- .RT+2-- .LD+2-- .RD+2-- } x=a x++ .RT+1=.RD+1=x
y--
} !=
}
Bank selection
While default bank can be chosen in file list for each file, a file can span across multiple banks. Active bank can be changed at any point in the file.
Example:
bank my_bank // from now on all code and data will go to 'my_bank'
Raw data
You can use raw data to put (for example) unsupported opcodes or allocate certain memory for variables etc.
Example:
var bcol=0xd020
main {
data { 0xEA 0xEA 0xEA }
{ bcol++ } always
}
// produce this code:
//
//.C:0810 4C 13 08 JMP $0813
//.C:0813 EA NOP
//.C:0814 EA NOP
//.C:0815 EA NOP
//.C:0816 EE 20 D0 INC $D020
//.C:0819 4C 16 08 JMP $0816
Data block definition
Data blocks are defined using data keyword. Defining data block at the same time defines a label to its first element, so the block is accessibl using simple indexing, like MyData,x
. Datablocks can have optional alignment or no-page-crossing restrictions enabled.
Examples:
data MyData1 {
align 16 // align to 16 byte boundary
1 2 3 4 5 6 7 8 // data bytes folow
}
data MyData2 {
align 256 + 8 // align to 8 bytes after page boundary (lower address byte will be 0x08)
1 2 3 4 5 6 7 8 // data bytes folow
}
data MyData3 {
nocross // the data will fit completely inside single page, but can be at any offset within it
1 2 3 4 5 6 7 8 // data bytes folow
}
data MyData4 {
address 0x5000 // fixed memory address
1 2 3 4 5 6 7 8 // data bytes folow
}
data MyData5 {
0 0 code { a=x }
}
data sprite {
align 256
// image <file> <x0> <y0> <byte> <repeat> - gather bits from image
// <file> - file name without ".bmp" extension
// <x0> <y0> - first pixel to scan
// <byte> - scanning mode for each single byte starting with MSB (count+direction)
// <repeat> - scanning mode for consecutive bytes (count+direction)
image sprites 0 0 8> 16v // start at pixel (0,0), each byte is 8 bits to the right, repeat 16 times going up
image sprites 10 0 8> 16v // do the same from (10,0)
image sprites 20 0 8> 16v // and again starting at (20,0)
}
data fonts {
address 0x5000
image "data/font" 0 0 8> 8v tiles 8 0 31
image "data/font" 0 8 8> 8v tiles 8 0 31
image "data/font" 0 16 8> 8v tiles 8 0 31
image "data/font" 0 24 8> 8v tiles 8 0 31
}
data table {
address 0x2000
binary "table.bin"
}
data SineX {
align 256
0
for x=0..213 eval [ (sin(x/212*pi*2)*.499+.499)*130 ]
}
data SineY {
align 256
0
for x=0..255 eval [ (sin(x/256*pi*2)*.499+.499)*180+1 ]
}
data InfoScript {
charset " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!'-?:/()acelnosxz&"
"POlaCZONY Z WYWIADEM" 0xFE
"Z TYM WIELKIM" 0xFE
"ARTYSTa." 0xFE 0xF0
0xFE
}
Compile-time Evaluator
The K65 compiler has embedded evaluator that always executes at compile-time. The evaluator can perform arbitrary math operations which results are inlined in the final code as immediates. The evaluator can also set and use global compiler constants.
The evaluator is explained in detail on K65 Evaluator page.
Code sections
Executable code in K65 compiler is specified in sections. There currently supported types of code sections are:
main
This function being program entry point.
main {
a=0 // set accumulator to 0
{} always // loop forever
}
func
User defined function, that can be called from the code.
func inc_x {
x++ // increments X register and returns
} // RTS is added automatically
naked
Just like func
, but no RTS
is added automatically at the end.
naked inc_x_twice {
x++ // increments X register
goto inc_x // jump to previously defined inc_x (saves stack)
} // no RTS here; make sure function never reaches here
inline
User defined macro
that is inlined in the code when used.
inline inc_y {
y++
}
Functions and inlines are used simply by specifying their names, which places JSR
opcode or inlines the code. Function and inline calls do not pass any parameters. Any potential parameter and return value handling must be handled explicitly by the programmer using either registers, stack or predetermined memory locations.
func test {
inc_x // this will use JSR instruction to call 'inc_x' defined earlier
inc_y // this will inline the 'inc_y' inline - note that it will not add any overhead compared to simple 'y++'
}
else
In fact this creates (in branch code) jump to .label outside else
bracket.
Example:
inline check_if_key_or_doors {
a?60 == {gamestate_keys++}
else {
a?62 == {
ptr_doors=a=x
temp1=a=1
}
}
}
Far calls
Functions can also be called with far prefix to mark that the target function is in another bank, than the current one. The bankswitching mechanism used is defined by the linker. Inlines do not use far prefix, because the code is simply inlined.
NOTE: if far call is used within inline make sure to use such inline with care - inlining such inline in another bank will copy the inline code directly, which will usually result in improper bankswitching and program crash.
func test2_in_different_bank {
far inc_x
}
This page is still under construction - much of the infrmation is still to be filled