mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +00:00
390 lines
14 KiB
Plaintext
390 lines
14 KiB
Plaintext
------------------------------------------------------------
|
|
il65 - "Intermediate Language for 6502/6510 microprocessors"
|
|
------------------------------------------------------------
|
|
Written by Irmen de Jong (irmen@razorvine.net)
|
|
License: GNU GPL 3.0, see LICENSE
|
|
------------------------------------------------------------
|
|
|
|
|
|
The python program parses it and generates 6502 assembler code.
|
|
It uses the 64tass macro cross assembler to assemble it into binary files.
|
|
|
|
|
|
|
|
Memory Model
|
|
------------
|
|
|
|
Zero page: $00 - $ff
|
|
Hardware stack: $100 - $1ff
|
|
Free RAM/ROM: $0200 - $ffff
|
|
|
|
Reserved:
|
|
|
|
data direction $00
|
|
bank select $01
|
|
NMI VECTOR $fffa
|
|
RESET VECTOR $fffc
|
|
IRQ VECTOR $fffe
|
|
|
|
A particular 6502/6510 machine such as the Commodore-64 will have many other
|
|
special addresses due to:
|
|
- ROMs installed in the machine (basic, kernel and character generator roms)
|
|
- memory-mapped I/O registers (for the video and sound chip for example)
|
|
- RAM areas used for screen graphics and sprite data.
|
|
|
|
|
|
Usable Hardware registers:
|
|
A, X, Y,
|
|
AX, AY, XY (16-bit combined register pairs)
|
|
SC (status register Carry flag)
|
|
These cannot occur as variable names - they will always refer to the hardware registers.
|
|
|
|
|
|
The zero page locations $02-$ff can be regarded as 254 other registers.
|
|
Free zero page addresses on the C-64:
|
|
$02,$03 # reserved as scratch addresses
|
|
$04,$05
|
|
$06
|
|
$0a
|
|
$2a
|
|
$52
|
|
$93
|
|
$f7,$f8
|
|
$f9,$fa
|
|
$fb,$fc
|
|
$fd,$fe
|
|
|
|
|
|
|
|
IL program parsing structure:
|
|
-----------------------------
|
|
|
|
|
|
OUTPUT MODES:
|
|
-------------
|
|
output raw ; no load address bytes
|
|
output prg ; include the first two load address bytes, (default is $0801), no basic program
|
|
output prg,sys ; include the first two load address bytes, basic start program with sys call to code, default code start
|
|
; immediately after the basic program at $081d, or beyond.
|
|
|
|
address $0801 ; override program start address (default is set to $c000 for raw mode and $0801 for c-64 prg mode)
|
|
; cannot be used if output mode is prg,sys because basic programs always have to start at $0801
|
|
|
|
|
|
data types:
|
|
byte 8 bits $8f (unsigned, @todo signed bytes)
|
|
int 16 bits $8fee (unsigned, @todo signed ints)
|
|
bool true/false (aliases for the integer values 1 and 0, not a true datatype by itself)
|
|
char '@' (converted to a byte)
|
|
float 40 bits 1.2345 (stored in 5-byte cbm MFLPT format)
|
|
@todo 24 and 32 bits integers, unsigned and signed?
|
|
string 0-terminated sequence of bytes "hello." (implicit 0-termination byte)
|
|
pstring sequence of bytes where first byte is the length. (no 0-termination byte)
|
|
For strings, both petscii and screencode variants can be written in source, they will be translated at compile/assembler time.
|
|
|
|
|
|
Note: for many floating point operations, the compiler uses routines in the C64 BASIC and KERNAL ROMs.
|
|
So they will only work if the BASIC ROM (and KERNAL ROM) are banked in.
|
|
largest 5-byte MFLPT float: 1.7014118345e+38 (negative: -1.7014118345e+38)
|
|
|
|
|
|
Note: with the # prefix you can take the address of something. This is sometimes useful,
|
|
for instance when you want to manipulate the ADDRESS of a memory mapped variable rather than
|
|
the value it represents. You can take the address of a string as well, but the compiler already
|
|
treats those as a value that you manipulate via its address, so the # is ignored here.
|
|
|
|
|
|
|
|
BLOCKS
|
|
------
|
|
|
|
~ blockname [address] {
|
|
statements
|
|
}
|
|
|
|
The blockname "ZP" is reserved and always means the ZeroPage. Its start address is always set to $04,
|
|
because $00/$01 are used by the hardware and $02/$03 are reserved as general purpose scratch registers.
|
|
|
|
Block names cannot occur more than once, EXCEPT 'ZP' where the contents of every occurrence of it are merged.
|
|
Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack)
|
|
|
|
You can omit the blockname but then you can only refer to the contents of the block via its absolute address,
|
|
which is required in this case. If you omit both, the block is ignored altogether (and a warning is displayed).
|
|
|
|
|
|
IMPORTING, INCLUDING and BINARY-INCLUDING files
|
|
-----------------------------------------------
|
|
|
|
import "filename[.ill]"
|
|
Can only be used outside of a block (usually at the top of your file).
|
|
Reads everything from the named IL65 file at this point and compile it as a normal part of the program.
|
|
|
|
asminclude "filename.txt", scopelabel
|
|
Can only be used in a block.
|
|
The assembler will include the file as asm source text at this point, il65 will not process this at all.
|
|
The scopelabel will be used as a prefix to access the labels from the included source code,
|
|
otherwise you would risk symbol redefinitions or duplications.
|
|
|
|
asmbinary "filename.bin" [, <offset>[, <length>]]
|
|
Can only be used in a block.
|
|
The assembler will include the file as binary bytes at this point, il65 will not process this at all.
|
|
The optional offset and length can be used to select a particular piece of the file.
|
|
|
|
|
|
|
|
MACROS
|
|
------
|
|
|
|
@todo macros are meta-code (written in Python syntax) that actually runs in a preprecessing step
|
|
during the compilation, and produces output value that is then replaced on that point in the input source.
|
|
Allows us to create pre calculated sine tables and such. Something like:
|
|
|
|
var .array sinetable ``[sin(x) * 10 for x in range(100)]``
|
|
|
|
|
|
|
|
EXPRESSIONS
|
|
-----------
|
|
|
|
In most places where a number or other value is expected, you can use just the number, or a full constant expression.
|
|
The expression is parsed and evaluated by Python itself at compile time, and the (constant) resulting value is used in its place.
|
|
Ofcourse the special il65 syntax for hexadecimal numbers ($xxxx), binary numbers (%bbbbbb),
|
|
and the address-of (#xxxx) is supported. Other than that it must be valid Python syntax.
|
|
Expressions can contain function calls to the math library (sin, cos, etc) and you can also use
|
|
all builtin functions (max, avg, min, sum etc). They can also reference idendifiers defined elsewhere in your code,
|
|
if this makes sense.
|
|
|
|
The syntax "[address]" means: the contents of the memory at address, or "indirect addressing".
|
|
By default, if not otherwise known, a single byte is assumed. You can add the ".byte" or ".word" or ".float" suffix
|
|
to make it clear what data type the address points to.
|
|
This addressing mode is only supported for constant (integer) addresses and not for variable types,
|
|
unless it is part of a subroutine call statement. For an indirect goto call, the 6502 CPU has a special opcode
|
|
(JMP indirect) and an indirect subroutine call (JSR indirect) is synthesized using a couple of instructions.
|
|
|
|
Everything after a semicolon ';' is a comment and is ignored, however the comment (if it is the only thing
|
|
on the line) is copied into the resulting assembly source code.
|
|
|
|
|
|
FLOW CONTROL
|
|
------------
|
|
|
|
Required building blocks: additional forms of 'go' statement: including an if clause, comparison statement.
|
|
|
|
- a primitive conditional branch instruction (special case of 'go'): directly translates to a branch instruction:
|
|
if[_XX] go <label>
|
|
XX is one of: (cc, cs, vc, vs, eq, ne, pos, min,
|
|
lt==cc, lts==min, gt==eq+cs, gts==eq+pos, le==cc+eq, les==neg+eq, ge==cs, ges==pos)
|
|
and when left out, defaults to ne (not-zero, i.e. true)
|
|
NOTE: some combination branches such as cc+eq an be peephole optimized see http://www.6502.org/tutorials/compare_beyond.html#2.2
|
|
|
|
- conditional go with expression: where the if[_XX] is followed by a <expression>
|
|
in that case, evaluate the <expression> first (whatever it is) and then emit the primitive if[_XX] go
|
|
if[_XX] <expression> go <label>
|
|
eventually translates to:
|
|
<expression-code>
|
|
bXX <label>
|
|
|
|
- comparison statement: compares left with right: compare <first_value>, <second_value>
|
|
(and keeps the comparison result in the status register.)
|
|
this translates into a lda first_value, cmp second_value sequence after which a conditional branch is possible.
|
|
|
|
|
|
|
|
IF_XX:
|
|
------
|
|
if[_XX] [<expression>] {
|
|
...
|
|
}
|
|
[ else {
|
|
... ; evaluated when the condition is not met
|
|
} ]
|
|
|
|
|
|
==> DESUGARING ==>
|
|
|
|
(no else:)
|
|
|
|
if[_!XX] [<expression>] go il65_if_999_end ; !XX being the conditional inverse of XX
|
|
.... (true part)
|
|
il65_if_999_end ; code continues after this
|
|
|
|
|
|
(with else):
|
|
if[_XX] [<expression>] go il65_if_999
|
|
... (else part)
|
|
go il65_if_999_end
|
|
il65_if_999 ... (true part)
|
|
il65_if_999_end ; code continues after this
|
|
|
|
|
|
IF X <COMPARISON> Y:
|
|
-----------------------
|
|
|
|
==> DESUGARING ==>
|
|
compare X, Y
|
|
if_XX go ....
|
|
XX based on <COMPARISON>.
|
|
|
|
|
|
|
|
|
|
|
|
WHILE:
|
|
------
|
|
while[_XX] <expression> {
|
|
...
|
|
continue
|
|
break
|
|
}
|
|
|
|
==> DESUGARING ==>
|
|
|
|
go il65_while_999_check ; jump to the check
|
|
il65_while_999
|
|
... (code)
|
|
go il65_while_999 ;continue
|
|
go il65_while_999_end ;break
|
|
il65_while_999_check
|
|
if[_XX] <expression> go il65_while_999 ; loop condition
|
|
il65_while_999_end ; code continues after this
|
|
|
|
|
|
|
|
REPEAT:
|
|
------
|
|
|
|
repeat {
|
|
...
|
|
continue
|
|
break
|
|
} until[_XX] <expressoin>
|
|
|
|
==> DESUGARING ==>
|
|
|
|
il65_repeat_999
|
|
... (code)
|
|
go il65_repeat_999 ;continue
|
|
go il65_repeat_999_end ;break
|
|
if[_!XX] <expression> go il65_repeat_999 ; loop condition via conditional inverse of XX
|
|
il65_repeat_999_end ; code continues after this
|
|
|
|
|
|
|
|
FOR:
|
|
----
|
|
|
|
for <loopvar> = <from_expression> to <to_expression> [step <step_expression>] {
|
|
...
|
|
break
|
|
continue
|
|
}
|
|
|
|
|
|
@todo how to do signed integer loopvars?
|
|
|
|
|
|
==> DESUGARING ==>
|
|
|
|
loopvar = <from_expression>
|
|
compare loopvar, <to_expression>
|
|
if_ge go il65_for_999_end ; loop condition
|
|
step = <step_expression> ; (store only if step < -1 or step > 1)
|
|
il65_for_999
|
|
go il65_for_999_end ;break
|
|
go il65_for_999_loop ;continue
|
|
.... (code)
|
|
il65_for_999_loop
|
|
loopvar += step ; (if step > 1 or step < -1)
|
|
loopvar++ ; (if step == 1)
|
|
loopvar-- ; (if step == -1)
|
|
go il65_for_999 ; continue the loop
|
|
il65_for_999_end ; code continues after this
|
|
|
|
|
|
|
|
MEMORY BLOCK OPERATIONS:
|
|
|
|
@todo matrix,list,string memory block operations:
|
|
- matrix type operations (whole matrix, per row, per column, individual row/column)
|
|
operations: set, get, copy (from another matrix with the same dimensions, or list with same length),
|
|
shift-N (up, down, left, right, and diagonals, meant for scrolling)
|
|
rotate-N (up, down, left, right, and diagonals, meant for scrolling)
|
|
clear (set whole matrix to the given value, default 0)
|
|
|
|
- list operations (whole list, individual element)
|
|
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right)
|
|
clear (set whole list to the given value, default 0)
|
|
|
|
- list and matrix operations ofcourse work identical on vars and on memory mapped vars of these types.
|
|
|
|
- strings: identical operations as on lists.
|
|
|
|
these should call (or emit inline) optimized pieces of assembly code, so they run as fast as possible
|
|
|
|
|
|
|
|
SUBROUTINES DEFINITIONS
|
|
-----------------------
|
|
|
|
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
|
|
Their definition, using the sub statement, includes the specification of the required input- and output parameters.
|
|
For now, only register based parameters are supported (A, X, Y and paired registers, and the carry status bit SC as a special).
|
|
The syntax is:
|
|
|
|
sub <identifier> ([proc_parameters]) -> ([proc_results]) {
|
|
... statements ...
|
|
}
|
|
|
|
proc_parameters = comma separated list of "<parametername>:<register>" pairs specifying the input parameters
|
|
proc_results = comma separated list of <register> names specifying in which register(s) the output is returned.
|
|
If the register name ends with a '?', that means the register doesn't contain a real return value but
|
|
is clobbered in the process so the original value it had before calling the sub is no longer valid.
|
|
This is not immediately useful for your own code, but the compiler needs this information to
|
|
emit the correct assembly code to preserve the cpu registers if needed when the call is made.
|
|
|
|
|
|
Subroutines that are pre-defined on a specific memory location (usually routines from ROM),
|
|
can also be defined using the 'sub' statement. But in this case you don't supply a block with statements,
|
|
but instead assign a memory address to it:
|
|
|
|
sub <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
|
|
|
|
example: "sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3"
|
|
|
|
|
|
SUBROUTINE CALLS
|
|
----------------
|
|
|
|
You call a subroutine like this:
|
|
subroutinename_or_address [!] ( [arguments...] )
|
|
|
|
Normally, the registers are preserved when calling the subroutine and restored on return.
|
|
If you add a '!' after the name, no register preserving is done and the call essentially
|
|
is just a single JSR instruction.
|
|
Arguments should match the subroutine definition. You are allowed to omit the parameter names.
|
|
If no definition is available (because you're directly calling memory or a label or something else),
|
|
you can freely add arguments (but in this case they all have to be named).
|
|
|
|
To jump to a subroutine (without returning), prefix the subroutine call with the word 'goto'.
|
|
Unlike gotos in other languages, here it take arguments as well, because it
|
|
essentially is the same as calling a subroutine and only doing something different when it's finished.
|
|
|
|
|
|
@todo support call non-register args (variable parameter passing)
|
|
@todo support assigning call return values (so that you can assign these to other variables, and allows the subroutine call be an actual expression)
|
|
|
|
|
|
REGISTER PRESERVATION BLOCK: @todo (no)preserve
|
|
----------------------------
|
|
|
|
preserve [regs] { .... } adds register preservation around the containing code default = all 3 regs, or specify which.
|
|
nopreserve [regs] { .... } removes register preservation on all statements in the block that would otherwise have it.
|
|
|
|
|
|
|
|
@todo BITMAP DEFINITIONS:
|
|
to define CHARACTERS (8x8 monochrome or 4x8 multicolor = 8 bytes)
|
|
--> PLACE in memory on correct address (???k aligned)
|
|
and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
|
|
--> PLACE in memory on correct address (base+sprite pointer, 64-byte aligned)
|