mirror of
https://github.com/irmen/prog8.git
synced 2024-12-23 09:32:43 +00:00
new docs
This commit is contained in:
parent
bc8d56cbd7
commit
0e785fcfb3
3
.gitignore
vendored
3
.gitignore
vendored
@ -17,3 +17,6 @@ __pycache__/
|
||||
parser.out
|
||||
parsetab.py
|
||||
!/il65/lib/*
|
||||
.pytest_cache/
|
||||
docs/build
|
||||
|
||||
|
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = IL65
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
36
docs/make.bat
Normal file
36
docs/make.bat
Normal file
@ -0,0 +1,36 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
set SPHINXPROJ=IL65
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
@ -1,552 +0,0 @@
|
||||
What is a Program?
|
||||
------------------
|
||||
|
||||
A "complete program" is a compiled, assembled, and linked together single unit.
|
||||
It contains all of the program's code and data and has a certain file format that
|
||||
allows it to be loaded directly on the target system.
|
||||
|
||||
Most programs will need a tiny BASIC launcher that does a SYS into the generated machine code,
|
||||
but it is also possible to output just binary programs that can be loaded into memory elsewhere.
|
||||
|
||||
Compiling a program
|
||||
-------------------
|
||||
|
||||
Compilation of a program is done by compiling just the main source code module file.
|
||||
Other modules that the code needs can be imported from within the file.
|
||||
The compiler will eventually link them together into one output program.
|
||||
|
||||
Program code structure
|
||||
----------------------
|
||||
|
||||
A program is created by compiling and linking *one or more module source code files*.
|
||||
|
||||
### Module file
|
||||
|
||||
This is a file with the ``.ill`` suffix, without spaces in its name, containing:
|
||||
- source code comments
|
||||
- global program options
|
||||
- imports of other modules
|
||||
- one or more *code blocks*
|
||||
|
||||
The filename doesn't really matter as long as it doesn't contain spaces.
|
||||
The full name of the symbols defined in the file is not impacted by the filename.
|
||||
|
||||
#### Source code comments
|
||||
|
||||
A=5 ; set the initial value to 5
|
||||
; next is the code that...
|
||||
|
||||
In any file, everything after a semicolon '``;``' is considered a comment and is ignored by the compiler.
|
||||
If all of the line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code.
|
||||
|
||||
|
||||
#### Things global to the program
|
||||
|
||||
The global program options that can be put at the top of a module file,
|
||||
determine the settings for the entire output program.
|
||||
They're all optional (defaults will be chosen as mentioned below).
|
||||
If specified though, they can only occur once in the entire program:
|
||||
|
||||
%output prg
|
||||
%address $0801
|
||||
%launcher none
|
||||
%zp compatible
|
||||
|
||||
|
||||
##### ``%output`` : select output format of the program
|
||||
- ``raw`` : no header at all, just the raw machine code data
|
||||
- ``prg`` : C64 program (with load address header)
|
||||
|
||||
The default is ``prg``.
|
||||
|
||||
|
||||
##### ``%address`` : specify start address of the code
|
||||
|
||||
- default for ``raw`` output is $c000
|
||||
- default for ``prg`` output is $0801
|
||||
- cannot be changed if you select ``prg`` with a ``basic`` launcher;
|
||||
then it is always $081d (immediately after the BASIC program), and the BASIC program itself is always at $0801.
|
||||
This is because the C64 expects BASIC programs to start at this address.
|
||||
|
||||
|
||||
##### ``%launcher`` : specify launcher type
|
||||
|
||||
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
|
||||
- ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
|
||||
- ``none`` : no launcher logic is added at all
|
||||
|
||||
|
||||
##### ``%zp`` : select ZeroPage behavior
|
||||
|
||||
- ``compatible`` : only use a few free locations in the ZP
|
||||
- ``full`` : use the whole ZP for variables, makes the program faster but can't use BASIC or KERNAL routines anymore, and cannot exit cleanly
|
||||
- ``full-restore`` : like ``full``, but makes a backup copy of the original values at program start.
|
||||
These are restored when exiting the program back to the BASIC prompt
|
||||
|
||||
Defaults to ``compatible``.
|
||||
The exact meaning of these options can be found in the paragraph
|
||||
about the ZeroPage in the system documentation.
|
||||
|
||||
|
||||
##### Program Start and Entry Point
|
||||
|
||||
Your program must have a single entry point where code execution begins.
|
||||
The compiler expects a ``start`` subroutine in the ``main`` block for this,
|
||||
taking no parameters and having no return value.
|
||||
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call).
|
||||
|
||||
~ main {
|
||||
sub start () -> () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
The ``main`` module is always relocated to the start of your programs
|
||||
address space, and the ``start`` subroutine (the entrypoint) will be on the
|
||||
first address. This will also be the address that the BASIC loader program (if generated)
|
||||
calls with the SYS statement.
|
||||
|
||||
Blocks and subroutines are explained below.
|
||||
|
||||
|
||||
#### Using other modules via import
|
||||
|
||||
Immediately following the global program options at the top of the module file,
|
||||
the imports of other modules are placed:
|
||||
|
||||
``%import filename``
|
||||
|
||||
This reads and compiles the named module source file as part of your current program.
|
||||
Symbols from the imported module become available in your code,
|
||||
without a module or filename prefix.
|
||||
You can import modules one at a time, and importing a module more than once has no effect.
|
||||
|
||||
|
||||
#### Blocks, Scopes, and accessing Symbols
|
||||
|
||||
Blocks are the separate pieces of code and data of your program. They are combined
|
||||
into a single output program. No code or data can occur outside a block.
|
||||
|
||||
~ blockname [address] {
|
||||
[directives...]
|
||||
[variables...]
|
||||
[subroutines...]
|
||||
}
|
||||
|
||||
Block names must be unique in your entire program.
|
||||
It's possible to 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* name and address, the block is *ignored* by the compiler (and a warning is displayed).
|
||||
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
|
||||
want to work on later, because the contents of the ignored block are not fully parsed either.
|
||||
|
||||
The address can be used to place a block at a specific location in memory.
|
||||
Otherwise the compiler will automatically choose the location (usually immediately after
|
||||
the previous block in memory).
|
||||
The address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack).
|
||||
|
||||
A block is also a *scope* in your program so the symbols in the block don't clash with
|
||||
symbols of the same name defined elsewhere in the same file or in another file.
|
||||
You can refer to the symbols in a particular block by using a *dotted name*: ``blockname.symbolname``.
|
||||
Labels inside a subroutine are appended again to that; ``blockname.subroutinename.label``.
|
||||
|
||||
Every symbol is 'public' and can be accessed from elsewhere given its dotted name.
|
||||
|
||||
|
||||
**The special "ZP" ZeroPage block**
|
||||
|
||||
Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage.
|
||||
The contents of every block with that name (this one may occur multiple times) are merged into one.
|
||||
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.
|
||||
|
||||
|
||||
Code elements
|
||||
-------------
|
||||
|
||||
### Data types for Variables and Values
|
||||
|
||||
IL65 supports the following data types:
|
||||
|
||||
| type | storage size | type identifier | example |
|
||||
|-------------------------|-------------------|-----------------|---------------------------------------------------|
|
||||
| unsigned byte | 1 byte = 8 bits | ``.byte`` | ``$8f`` |
|
||||
| unsigned word | 2 bytes = 16 bits | ``.word`` | ``$8fee`` |
|
||||
| floating-point | 5 bytes = 40 bits | ``.float`` | ``1.2345`` (stored in 5-byte cbm MFLPT format) |
|
||||
| byte array | varies | ``.array`` | @todo |
|
||||
| word array | varies | ``.wordarray`` | @todo |
|
||||
| matrix (of bytes) | varies | ``.matrix`` | @todo |
|
||||
| string (petscii) | varies | ``.str`` | ``"hello."`` (implicitly terminated by a 0-byte) |
|
||||
| pascal-string (petscii) | varies | ``.strp`` | ``"hello."`` (implicit first byte = length, no 0-byte |
|
||||
| string (screencodes) | varies | ``.strs`` | ``"hello."`` (implicitly terminated by a 0-byte) |
|
||||
| pascal-string (scr) | varies | ``.strps`` | ``"hello."`` (implicit first byte = length, no 0-byte |
|
||||
|
||||
|
||||
You can use the literals ``true`` and ``false`` as 'boolean' values, they are aliases for the
|
||||
byte value 1 and 0 respectively.
|
||||
|
||||
|
||||
Strings in your code will be encoded in either CBM PETSCII or C-64 screencode variants,
|
||||
this encoding is done by the compiler. PETSCII is the default, if you need screencodes you
|
||||
have to use the ``s`` variants of the string type identifier.
|
||||
A string with just one character in it is considered to be a BYTE instead with
|
||||
that character's PETSCII value. So if you really need a string of length 1 you must declare
|
||||
the variable explicitly of type ``.str``.
|
||||
|
||||
Floating point numbers are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
|
||||
but most float operations are specific to the Commodore-64 even because
|
||||
routines in the C-64 BASIC and KERNAL ROMs are used.
|
||||
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM) are banked in, and your code imports the ``c654lib.ill``.
|
||||
The largest 5-byte MFLPT float that can be stored is: 1.7014118345e+38 (negative: -1.7014118345e+38)
|
||||
|
||||
The initial values of your variables will be restored automatically when the program is (re)started,
|
||||
*except for string variables*. It is assumed these are left unchanged by the program.
|
||||
If you do modify them in-place, you should take care yourself that they work as
|
||||
expected when the program is restarted.
|
||||
|
||||
|
||||
@todo pointers/addresses? (as opposed to normal WORDs)
|
||||
@todo signed integers (byte and word)?
|
||||
|
||||
|
||||
### Indirect addressing and address-of
|
||||
|
||||
**Address-of:**
|
||||
The ``#`` prefix is used to 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 could take the address of a string as well, but that is redundant:
|
||||
the compiler already treats those as a value that you manipulate via its address.
|
||||
For most other types this prefix is not supported and will result in a compile error.
|
||||
The resulting value is simply a 16 bit word.
|
||||
|
||||
**Indirect addressing:** The ``[address]`` syntax 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``
|
||||
type identifier, inside the bracket, to make it clear what data type the address points to.
|
||||
For instance: ``[address .word]`` (notice the space, to distinguish this from a dotted symbol name).
|
||||
For an indirect goto call, the 6502 CPU has a special instruction
|
||||
(``jmp`` indirect) and an indirect subroutine call (``jsr`` indirect) is emitted
|
||||
using a couple of instructions.
|
||||
|
||||
|
||||
### Conditional Execution
|
||||
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
rather than having fixed gotos or subroutine calls. IL65 has a *conditional goto* statement for this,
|
||||
that is translated into a comparison (if needed) and then a conditional branch instruction:
|
||||
|
||||
if[_XX] [<expression>] goto <label>
|
||||
|
||||
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, pos, neg, lt, gt, le, ge]
|
||||
It defaults to 'true' (=='ne', not-zero) if omitted. ('pos' will translate into 'pl', 'neg' into 'mi')
|
||||
@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
|
||||
|
||||
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
|
||||
if-statuses can be used when such a *comparison expression* is used. An example is:
|
||||
|
||||
if_not A > 55 goto more_iterations
|
||||
|
||||
|
||||
Conditional jumps are compiled into 6502's branching instructions (such as ``bne`` and ``bcc``) so
|
||||
the rather strict limit on how *far* it can jump applies. The compiler itself can't figure this
|
||||
out unfortunately, so it is entirely possible to create code that cannot be assembled successfully.
|
||||
You'll have to restructure your gotos in the code (place target labels closer to the branch)
|
||||
if you run into this type of assembler error.
|
||||
|
||||
|
||||
### Including other files or raw assembly code literally
|
||||
|
||||
- ``%asminclude "filename.txt", scopelabel``
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly 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>]]``
|
||||
This directive can only be used inside 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.
|
||||
- ``%asm {`` [raw assembly code lines] ``}``
|
||||
This directive includes raw unparsed assembly code at that exact spot in the program.
|
||||
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
|
||||
|
||||
|
||||
|
||||
### Assignments
|
||||
|
||||
Assignment statements assign a single value to a target variable or memory location.
|
||||
|
||||
target = value-expression
|
||||
|
||||
|
||||
### Augmented Assignments
|
||||
|
||||
A special assignment is the *augmented assignment* where the value is modified in-place.
|
||||
Several assignment operators are available: ``+=``, ``-=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=``
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
### Subroutine Definition
|
||||
|
||||
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,
|
||||
the carry status bit SC and the interrupt disable bit SI as specials).
|
||||
For subroutine return values, the special SZ register is also available, it means the zero status bit.
|
||||
|
||||
The syntax is:
|
||||
|
||||
sub <identifier> ([proc_parameters]) -> ([proc_results]) {
|
||||
... statements ...
|
||||
}
|
||||
|
||||
**proc_parameters =**
|
||||
comma separated list of "<parametername>:<register>" pairs specifying the input parameters.
|
||||
You can omit the parameter names as long as the arguments "line up".
|
||||
(actually, the Python parameter passing rules apply, so you can also mix positional
|
||||
and keyword arguments, as long as the keyword arguments come last)
|
||||
|
||||
**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.
|
||||
For convenience: a single '?' als the result spec is shorthand for ``A?, X?, Y?`` ("I don't know
|
||||
what the changed registers are, assume the worst")
|
||||
|
||||
|
||||
Pre-defined subroutines that are available on specific memory addresses
|
||||
(in system ROM for instance) can also be defined using the 'sub' statement.
|
||||
To do this you assign the routine's memory address to the sub:
|
||||
|
||||
sub <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
|
||||
|
||||
example:
|
||||
|
||||
sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3"
|
||||
|
||||
|
||||
### Subroutine Calling
|
||||
|
||||
You call a subroutine like this:
|
||||
|
||||
subroutinename_or_address ( [arguments...] )
|
||||
|
||||
or:
|
||||
|
||||
subroutinename_or_address ![register(s)] ( [arguments...] )
|
||||
|
||||
If the subroutine returns one or more values as results, you must use an assignment statement
|
||||
to store those values somewhere:
|
||||
|
||||
outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 )
|
||||
|
||||
The output variables must occur in the correct sequence of return registers as specified
|
||||
in the subroutine's definiton. It is possible to not specify any of them but the compiler
|
||||
will issue a warning then if the result values of a subroutine call are discarded.
|
||||
If you don't have a variable to store the output register in, it's then required
|
||||
to list the register itself instead as output variable.
|
||||
|
||||
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.
|
||||
|
||||
**Register preserving calls:** use the ``!`` followed by a combination of A, X and Y (or followed
|
||||
by nothing, which is the same as AXY) to tell the compiler you want to preserve the origial
|
||||
value of the given registers after the subroutine call. Otherwise, the subroutine may just
|
||||
as well clobber all three registers. Preserving the original values does result in some
|
||||
stack manipulation code to be inserted for every call like this, which can be quite slow.
|
||||
|
||||
|
||||
|
||||
Debugging (with Vice)
|
||||
---------------------
|
||||
|
||||
The ``%breakpoint`` directive instructs the compiler to put
|
||||
a *breakpoint* at that position in the code. It's a logical breakpoint instead of a physical
|
||||
BRK instruction because that will usually halt the machine altogether instead of breaking execution.
|
||||
Instead of this, a NOP instruction is generated and in a special output file the list of breakpoints is written.
|
||||
|
||||
This file is called "programname.vice-mon-list" and is meant to be used by the Vice C-64 emulator.
|
||||
It contains a series of commands for Vice's monitor, this includes source labels and the breakpoint settings.
|
||||
If you use the vice autostart feature of the compiler, it will be automatically processed by Vice.
|
||||
If you launch Vice manually, you can use a command line option to load this file: ``x64 -moncommands programname.vice-mon-list``
|
||||
|
||||
Vice will use the label names in memory disassembly, and will activate the breakpoints as well
|
||||
so if your program runs and it hits a breakpoint, Vice will halt execution and drop into the monitor.
|
||||
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Getting assembler error about undefined symbols such as ``not defined 'c64flt'``?
|
||||
This happens when your program uses floating point values, and you forgot to import the ``c64lib``.
|
||||
If you use floating points, the program will need routines from that library.
|
||||
Fix it by adding an ``%import c64lib``.
|
||||
|
||||
|
||||
|
||||
@Todo
|
||||
-----
|
||||
|
||||
### IF_XX:
|
||||
|
||||
if[_XX] [<expression>] {
|
||||
...
|
||||
}
|
||||
[ else {
|
||||
... ; evaluated when the condition is not met
|
||||
} ]
|
||||
|
||||
|
||||
==> DESUGARING ==>
|
||||
|
||||
(no else:)
|
||||
|
||||
if[_!XX] [<expression>] goto 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>] goto il65_if_999
|
||||
... (else part)
|
||||
goto 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 goto ....
|
||||
XX based on <COMPARISON>.
|
||||
|
||||
|
||||
|
||||
|
||||
### While
|
||||
|
||||
|
||||
while[_XX] <expression> {
|
||||
...
|
||||
continue
|
||||
break
|
||||
}
|
||||
|
||||
==> DESUGARING ==>
|
||||
|
||||
goto il65_while_999_check ; jump to the check
|
||||
il65_while_999
|
||||
... (code)
|
||||
goto il65_while_999 ;continue
|
||||
goto il65_while_999_end ;break
|
||||
il65_while_999_check
|
||||
if[_XX] <expression> goto 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)
|
||||
goto il65_repeat_999 ;continue
|
||||
goto il65_repeat_999_end ;break
|
||||
if[_!XX] <expression> goto 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 goto il65_for_999_end ; loop condition
|
||||
step = <step_expression> ; (store only if step < -1 or step > 1)
|
||||
il65_for_999
|
||||
goto il65_for_999_end ;break
|
||||
goto 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)
|
||||
goto il65_for_999 ; continue the loop
|
||||
il65_for_999_end ; code continues after this
|
||||
|
||||
|
||||
|
||||
### 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)]``
|
||||
|
||||
|
||||
### 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.
|
||||
|
||||
- matrix with row-interleave can only be a memory mapped variable and can be used to directly
|
||||
access a rectangular area within another piece of memory - such as a rectangle on the (character) screen
|
||||
|
||||
these should call (or emit inline) optimized pieces of assembly code, so they run as fast as possible
|
||||
|
||||
|
||||
|
||||
### Bitmap Definition (for Sprites and Characters)
|
||||
|
||||
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)
|
||||
|
14
docs/source/_static/css/customize.css
Normal file
14
docs/source/_static/css/customize.css
Normal file
@ -0,0 +1,14 @@
|
||||
.wy-nav-content {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
/* override table width restrictions */
|
||||
.wy-table-responsive table td, .wy-table-responsive table th {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.wy-table-responsive {
|
||||
margin-bottom: 24px;
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
}
|
BIN
docs/source/_static/logo.jpg
Normal file
BIN
docs/source/_static/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
docs/source/_static/logo_small.jpg
Normal file
BIN
docs/source/_static/logo_small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
175
docs/source/conf.py
Normal file
175
docs/source/conf.py
Normal file
@ -0,0 +1,175 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/stable/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'IL65'
|
||||
copyright = '2018, Irmen de Jong'
|
||||
author = 'Irmen de Jong'
|
||||
|
||||
# The short X.Y version
|
||||
version = 'x.y'
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = 'x.y alpha'
|
||||
|
||||
# -- extensions
|
||||
|
||||
def setup(app):
|
||||
# add custom css
|
||||
app.add_stylesheet("css/customize.css")
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
needs_sphinx = '1.5.3'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.githubpages',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path .
|
||||
exclude_patterns = []
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
highlight_language = 'none'
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# logo in top of sidebar
|
||||
html_logo = '_static/logo_small.jpg'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'IL65doc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'IL65.tex', 'IL65 Documentation',
|
||||
'Irmen de Jong', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'il65', 'IL65 Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'IL65', 'IL65 Documentation',
|
||||
author, 'IL65', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
|
||||
# -- Extension configuration -------------------------------------------------
|
||||
|
||||
# -- Options for todo extension ----------------------------------------------
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
2
docs/source/docutils.conf
Normal file
2
docs/source/docutils.conf
Normal file
@ -0,0 +1,2 @@
|
||||
[restructuredtext parser]
|
||||
smart_quotes=true
|
68
docs/source/index.rst
Normal file
68
docs/source/index.rst
Normal file
@ -0,0 +1,68 @@
|
||||
IL65 documentation - |version|
|
||||
==============================
|
||||
|
||||
.. image:: _static/logo.jpg
|
||||
:align: center
|
||||
:alt: IL65 logo
|
||||
|
||||
.. index:: what is IL65
|
||||
|
||||
What is IL65?
|
||||
-------------
|
||||
|
||||
IL65 is an experimental compiled programming language targeting the 8-bit
|
||||
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor.
|
||||
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
|
||||
while still being low level enough to create high performance programs.
|
||||
|
||||
|
||||
IL65 is copyright © Irmen de Jong (irmen@razorvine.net | http://www.razorvine.net).
|
||||
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
|
||||
Design principles
|
||||
-----------------
|
||||
|
||||
- It is a cross-compilation toolkit running on a modern machine.
|
||||
The resulting output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Based on simple and familiar imperative structured programming paradigm.
|
||||
- Allowing modular programming: modules, code blocks, subroutines.
|
||||
- Provide high level programming constructs but stay close to the metal;
|
||||
still able to directly use memory addresses, CPU registers and ROM subroutines
|
||||
- No dynamic memory allocation. All variables stay fixed size as determined at compile time.
|
||||
- Provide various quality of life language features specifically for the target platform.
|
||||
- Provide a convenient edit/compile/run cycle by being able to directly launch
|
||||
the compiled program in an emulator and provide debugging information to the emulator.
|
||||
- The compiler outputs a regular 6502 assembly code file, it doesn't assemble this itself.
|
||||
A third party cross-assembler tool is used to do this final step.
|
||||
- Goto is considered harmful, but not here; arbitrary control flow jumps are allowed.
|
||||
|
||||
|
||||
Required tools
|
||||
--------------
|
||||
|
||||
@TODO
|
||||
- 64tass cross-assembler?
|
||||
- java?
|
||||
- kotlin?
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents of this manual:
|
||||
|
||||
targetsystem.rst
|
||||
programming.rst
|
||||
progstructure.rst
|
||||
syntaxreference.rst
|
||||
todo.rst
|
||||
|
||||
|
||||
Index
|
||||
=====
|
||||
|
||||
* :ref:`genindex`
|
73
docs/source/programming.rst
Normal file
73
docs/source/programming.rst
Normal file
@ -0,0 +1,73 @@
|
||||
==============================
|
||||
Writing and building a program
|
||||
==============================
|
||||
|
||||
What is a "Program" anyway?
|
||||
---------------------------
|
||||
|
||||
A "complete runnable program" is a compiled, assembled, and linked together single unit.
|
||||
It contains all of the program's code and data and has a certain file format that
|
||||
allows it to be loaded directly on the target system. IL65 currently has no built-in
|
||||
support for programs that exceed 64 Kb of memory, nor for multi-part loaders.
|
||||
|
||||
For the Commodore-64, most programs will have a tiny BASIC launcher that does a SYS into the generated machine code.
|
||||
This way the user can load it as any other program and simply RUN it to start. (This is a regular ".prg" program).
|
||||
Il65 can create those, but it is also possible to output plain binary programs
|
||||
that can be loaded into memory anywhere.
|
||||
|
||||
|
||||
Compiling program code
|
||||
----------------------
|
||||
|
||||
Compilation of program code is done by telling the IL65 compiler to compile a main source code module file.
|
||||
Other modules that this code needs will be loaded and processed via imports from within that file.
|
||||
The compiler will link everything together into one output program at the end.
|
||||
|
||||
The compiler is invoked with the command:
|
||||
|
||||
``$ @todo``
|
||||
|
||||
|
||||
Module source code files
|
||||
------------------------
|
||||
|
||||
A module source file is a text file with the ``.ill`` suffix, containing the program's source code.
|
||||
It consists of compilation options and other directives, imports of other modules,
|
||||
and source code for one or more code blocks.
|
||||
|
||||
IL65 has a couple of *LIBRARY* modules that are defined in special internal files provided by the compiler:
|
||||
``c64lib``, ``il65lib``, ``mathlib``.
|
||||
You should not overwrite these or reuse their names.
|
||||
|
||||
|
||||
.. _debugging:
|
||||
|
||||
Debugging (with Vice)
|
||||
---------------------
|
||||
|
||||
There's support for using the monitor and debugging capabilities of the rather excellent
|
||||
`Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
|
||||
The ``%breakpoint`` directive (see :ref:`directives`) in the source code instructs the compiler to put
|
||||
a *breakpoint* at that position. Some systems use a BRK instruction for this, but
|
||||
this will usually halt the machine altogether instead of just suspending execution.
|
||||
IL65 issues a NOP instruction instead and creates a 'virtual' breakpoint at this position.
|
||||
All breakpoints are then written to a file called "programname.vice-mon-list",
|
||||
which is meant to be used by the Vice emulator.
|
||||
It contains a series of commands for Vice's monitor, including source labels and the breakpoint settings.
|
||||
If you use the vice autostart feature of the compiler, it will be processed by Vice automatically and immediately.
|
||||
If you launch Vice manually, you'll have to use a command line option to load this file:
|
||||
|
||||
``$ x64 -moncommands programname.vice-mon-list``
|
||||
|
||||
Vice will then use the label names in memory disassembly, and will activate the breakpoints as well.
|
||||
If your running program hits one of the breakpoints, Vice will halt execution and drop you into the monitor.
|
||||
|
||||
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Getting an assembler error about undefined symbols such as ``not defined 'c64flt'``?
|
||||
This happens when your program uses floating point values, and you forgot to import the ``c64lib``.
|
||||
If you use floating points, the program will need routines from that library.
|
||||
Fix it by adding an ``%import c64lib``.
|
164
docs/source/progstructure.rst
Normal file
164
docs/source/progstructure.rst
Normal file
@ -0,0 +1,164 @@
|
||||
.. _programstructure:
|
||||
|
||||
=================
|
||||
Program Structure
|
||||
=================
|
||||
|
||||
This chapter describes a high level overview of the elements that make up a program.
|
||||
Details about each of them, and the syntax, are discussed in the :ref:`syntaxreference` chapter.
|
||||
|
||||
|
||||
Elements of a program
|
||||
---------------------
|
||||
|
||||
.. data:: Program
|
||||
|
||||
Consists of one or more *modules*.
|
||||
|
||||
.. data:: Module
|
||||
|
||||
A file on disk with the ``.ill`` suffix. It contains *directives* and *code blocks*.
|
||||
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
|
||||
You can also add *comments* to the source code.
|
||||
One moudule file can *import* others, and also import *library modules*.
|
||||
|
||||
.. data:: Comments
|
||||
|
||||
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
|
||||
.. data:: Directive
|
||||
|
||||
These are special instructions for the compiler, to change how it processes the code
|
||||
and what kind of program it creates. A directive is on its own line in the file, and
|
||||
starts with ``%``, optionally followed by some arguments.
|
||||
|
||||
.. data:: Code block
|
||||
|
||||
A block of actual program code. It defines a *scope* (also known as 'namespace') and
|
||||
can contain IL65 *code*, *variable declarations* and *subroutines*.
|
||||
More details about this below: :ref:`blocks`.
|
||||
|
||||
.. data:: Variable declarations
|
||||
|
||||
The data that the code works on is stored in variables ('named values that can change').
|
||||
The compiler allocates the required memory for them.
|
||||
There is *no dynamic memory allocation*. The storage size of all variables
|
||||
is fixed and is determined at compile time.
|
||||
Variable declarations tend to appear at the top of the code block that uses them.
|
||||
They define the name and type of the variable, and its initial value.
|
||||
IL65 supports a small list of data types, including special 'memory mapped' types
|
||||
that don't allocate storage but instead point to a fixed location in the address space.
|
||||
|
||||
.. data:: Code
|
||||
|
||||
These are the instructions that make up the program's logic. There are different kinds of instructions
|
||||
('statements' is a better name):
|
||||
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
|
||||
.. data:: Subroutine
|
||||
|
||||
Defines a piece of code that can be called by its name from different locations in your code.
|
||||
It accepts parameters and can return result values.
|
||||
It can define its own variables but it's not possible to define subroutines nested in other subroutines.
|
||||
To keep things simple, you can only define subroutines inside code blocks from a module.
|
||||
|
||||
.. data:: Label
|
||||
|
||||
To label a position in your code where you can jump to from another place, you use a label like this::
|
||||
|
||||
nice_place:
|
||||
; code ...
|
||||
|
||||
It's an identifier followed by a colon ``:``. It's allowed to put the next statement on
|
||||
the same line, after the label.
|
||||
You can jump to it with a jump statement elsewhere. It is also possible to use a
|
||||
subroutine call to a label (but without parameters and return value).
|
||||
|
||||
|
||||
.. data:: Scope
|
||||
|
||||
Also known as 'namespace', this is a named box around the symbols defined in it.
|
||||
This prevents name collisions (or 'namespace pollution'), because the name of the scope
|
||||
is needed as prefix to be able to access the symbols in it.
|
||||
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
|
||||
There are three scopes in IL65:
|
||||
|
||||
- global (no prefix)
|
||||
- code block
|
||||
- subroutine
|
||||
|
||||
Modules are *not* a scope! Everything defined in a module is merged into the global scope.
|
||||
|
||||
|
||||
.. _blocks:
|
||||
|
||||
Blocks, Scopes, and accessing Symbols
|
||||
-------------------------------------
|
||||
|
||||
Blocks are the separate pieces of code and data of your program. They are combined
|
||||
into a single output program. No code or data can occur outside a block. Here's an example::
|
||||
|
||||
~ main $c000 {
|
||||
; this is code inside the block...
|
||||
}
|
||||
|
||||
|
||||
The name of a block must be unique in your entire program.
|
||||
Also be careful when importing other modules; blocks in your own code cannot have
|
||||
the same name as a block defined in an imported module or library.
|
||||
|
||||
It's possible to omit this name, 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* name and address, the block is *ignored* by the compiler (and a warning is displayed).
|
||||
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
|
||||
want to work on later, because the contents of the ignored block are not fully parsed either.
|
||||
|
||||
The address can be used to place a block at a specific location in memory.
|
||||
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
|
||||
the previous block in memory).
|
||||
The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack).
|
||||
|
||||
A block is also a *scope* in your program so the symbols in the block don't clash with
|
||||
symbols of the same name defined elsewhere in the same file or in another file.
|
||||
You can refer to the symbols in a particular block by using a *dotted name*: ``blockname.symbolname``.
|
||||
Labels inside a subroutine are appended again to that; ``blockname.subroutinename.label``.
|
||||
|
||||
Every symbol is 'public' and can be accessed from elsewhere given its dotted name.
|
||||
|
||||
|
||||
**The special "ZP" ZeroPage block**
|
||||
|
||||
Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage.
|
||||
The contents of every block with that name (this one may occur multiple times) are merged into one.
|
||||
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.
|
||||
|
||||
|
||||
Program Start and Entry Point
|
||||
-----------------------------
|
||||
|
||||
Your program must have a single entry point where code execution begins.
|
||||
The compiler expects a ``start`` subroutine in the ``main`` block for this,
|
||||
taking no parameters and having no return value.
|
||||
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
||||
|
||||
~ main {
|
||||
sub start () -> () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
The ``main`` module is always relocated to the start of your programs
|
||||
address space, and the ``start`` subroutine (the entrypoint) will be on the
|
||||
first address. This will also be the address that the BASIC loader program (if generated)
|
||||
calls with the SYS statement.
|
397
docs/source/syntaxreference.rst
Normal file
397
docs/source/syntaxreference.rst
Normal file
@ -0,0 +1,397 @@
|
||||
.. _syntaxreference:
|
||||
|
||||
================
|
||||
Syntax Reference
|
||||
================
|
||||
|
||||
Module file
|
||||
-----------
|
||||
|
||||
This is a file with the ``.ill`` suffix, containing *directives* and *code blocks*, described below.
|
||||
The file is a text file wich can also contain:
|
||||
|
||||
.. data:: Lines, whitespace, indentation
|
||||
|
||||
Line endings are significant because *only one* declaration, statement or other instruction can occur on every line.
|
||||
Other whitespace and line indentation is arbitrary and ignored by the compiler.
|
||||
You can use tabs or spaces as you wish.
|
||||
|
||||
.. data:: Source code comments
|
||||
|
||||
Everything after a semicolon ``;`` is a comment and is ignored.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
|
||||
|
||||
.. _directives:
|
||||
|
||||
Directives
|
||||
-----------
|
||||
|
||||
.. data:: %output <type>
|
||||
|
||||
Level: module.
|
||||
Global setting, selects program output type. Default is ``prg``.
|
||||
|
||||
- type ``raw`` : no header at all, just the raw machine code data
|
||||
- type ``prg`` : C64 program (with load address header)
|
||||
|
||||
|
||||
.. data:: %launcher <type>
|
||||
|
||||
Level: module.
|
||||
Global setting, selects the program launcher stub to use.
|
||||
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
|
||||
|
||||
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
|
||||
- type ``none`` : no launcher logic is added at all
|
||||
|
||||
|
||||
.. data:: %zp <style>
|
||||
|
||||
Level: module.
|
||||
Global setting, select ZeroPage handling style. Defaults to ``compatible``.
|
||||
|
||||
- style ``compatible`` -- only use the few 'free' addresses in the ZP, and don't change anything else.
|
||||
This allows full use of BASIC and KERNAL ROM routines including default IRQs during normal system operation.
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
It is also not possible to cleanly exit the program, other than resetting the machine.
|
||||
This option makes programs smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
- style ``full-restore`` -- like ``full``, but makes a backup copy of the original values at program start.
|
||||
These are restored (except for the software jiffy clock in ``$a0``--``$a2``)
|
||||
when the program exits, and allows it to exit back to the BASIC prompt.
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
|
||||
|
||||
.. data:: %address <address>
|
||||
|
||||
Level: module.
|
||||
Global setting, set the program's start memory address
|
||||
|
||||
- default for ``raw`` output is ``$c000``
|
||||
- default for ``prg`` output is ``$0801``
|
||||
- cannot be changed if you select ``prg`` with a ``basic`` launcher;
|
||||
then it is always ``$081d`` (immediately after the BASIC program), and the BASIC program itself is always at ``$0801``.
|
||||
This is because the C64 expects BASIC programs to start at this address.
|
||||
|
||||
|
||||
.. data:: %import <name>
|
||||
|
||||
Level: module, block.
|
||||
This reads and compiles the named module source file as part of your current program.
|
||||
Symbols from the imported module become available in your code,
|
||||
without a module or filename prefix.
|
||||
You can import modules one at a time, and importing a module more than once has no effect.
|
||||
|
||||
|
||||
.. data:: %saveregisters
|
||||
|
||||
Level: block.
|
||||
@todo
|
||||
|
||||
.. data:: %noreturn
|
||||
|
||||
Level: block, subroutine.
|
||||
@todo
|
||||
|
||||
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside 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.
|
||||
|
||||
.. data:: %asminclude "<filename>", scopelabel
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly 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.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
||||
Level: block, subroutine.
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
|
||||
.. data:: %asm { ... }
|
||||
|
||||
Level: block, subroutine.
|
||||
Declares that there is *inline assembly code* in the lines enclosed by the curly braces.
|
||||
This code will be written as-is into the generated output file.
|
||||
The assembler syntax used should be for the 3rd party cross assembler tool that IL65 uses.
|
||||
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
|
||||
|
||||
|
||||
Identifiers
|
||||
-----------
|
||||
|
||||
Naming things in IL65 is done via valid *identifiers*. They start with a letter, and after that,
|
||||
must consist of letters, numbers, or underscores. Examples of valid identifiers::
|
||||
|
||||
a
|
||||
A
|
||||
monkey
|
||||
COUNTER
|
||||
Better_Name_2
|
||||
|
||||
|
||||
Code blocks
|
||||
-----------
|
||||
|
||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||
can contain IL65 *code*, *directives*, *variable declarations* and *subroutines*::
|
||||
|
||||
~ <blockname> [<address>] {
|
||||
<directives>
|
||||
<variables>
|
||||
<statements>
|
||||
<subroutines>
|
||||
}
|
||||
|
||||
The <blockname> must be a valid identifier or can be completely omitted.
|
||||
In that case the <address> is required to tell the compiler to put the block at
|
||||
a certain position in memory. Otherwise it would be impossible to access its contents.
|
||||
The <address> is optional. It must be a valid memory address such as ``$c000``.
|
||||
Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``$c000``::
|
||||
|
||||
~ main $c000 {
|
||||
; this is code inside the block...
|
||||
}
|
||||
|
||||
|
||||
Variables and value literals
|
||||
----------------------------
|
||||
|
||||
The data that the code works on is stored in variables. Variable names have to be valid identifiers.
|
||||
Values in the source code are written using *value literals*. In the table of the supported
|
||||
data types below you can see how they should be written.
|
||||
|
||||
Variable declarations
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@todo
|
||||
|
||||
|
||||
Data types
|
||||
^^^^^^^^^^
|
||||
|
||||
IL65 supports the following data types:
|
||||
|
||||
=============== ======================= ================= =========================================
|
||||
type identifier type storage size example var declaration and literal value
|
||||
=============== ======================= ================= =========================================
|
||||
``byte`` unsigned byte 1 byte = 8 bits ``byte myvar = $8f``
|
||||
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
|
||||
The true and false are actually just aliases
|
||||
for the byte values 1 and 0.
|
||||
``word`` unsigned word 2 bytes = 16 bits ``word myvar = $8fee``
|
||||
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
|
||||
stored in 5-byte cbm MFLPT format
|
||||
``byte[x]`` byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]``
|
||||
``word[x]`` word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]``
|
||||
``byte[x,y]`` byte matrix x*y bytes ``byte[40,25] myvar = @todo``
|
||||
Note: word-matrix not supported
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
``str_p`` pascal-string (petscii) varies ``str_p myvar = "hello."``
|
||||
implicit first byte = length, no 0-byte
|
||||
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
``str_ps`` pascal-string varies ``str_ps myvar = "hello."``
|
||||
(screencodes) implicit first byte = length, no 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
|
||||
|
||||
**String encoding:**
|
||||
Strings in your code will be encoded (translated from ASCII/UTF-8) into either CBM PETSCII or C-64 screencodes.
|
||||
PETSCII is the default, so if you need screencodes (also called 'poke' codes)
|
||||
you have to use the ``_s`` variants of the string type identifier.
|
||||
A string literal of length 1 is considered to be a *byte* instead with
|
||||
that single character's PETSCII value. If you really need a *string* of length 1 you
|
||||
can only do so by assigning it to a variable with one of the string types.
|
||||
|
||||
**Floating point numbers:**
|
||||
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
|
||||
and also most float operations are specific to the Commodore-64.
|
||||
This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
|
||||
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
|
||||
are banked in (and your code imports the ``c64lib.ill``)
|
||||
|
||||
The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (negative: **-1.7014118345e+38**)
|
||||
|
||||
**Initial values over multiple runs:**
|
||||
The initial values of your variables will be restored automatically when the program is (re)started,
|
||||
*except for string variables*. It is assumed these are left unchanged by the program.
|
||||
If you do modify them in-place, you should take care yourself that they work as
|
||||
expected when the program is restarted.
|
||||
|
||||
|
||||
**@todo pointers/addresses? (as opposed to normal WORDs)**
|
||||
**@todo signed integers (byte and word)?**
|
||||
|
||||
|
||||
|
||||
Indirect addressing and address-of
|
||||
----------------------------------
|
||||
|
||||
**Address-of:**
|
||||
The ``#`` prefix is used to take the address of something.
|
||||
It can be used for example to work with the *address* of a memory mapped variable rather than
|
||||
the value it holds. You could take the address of a string as well, but that is redundant:
|
||||
the compiler already treats those as a value that you manipulate via its address.
|
||||
For most other types this prefix is not supported and will result in a compilation error.
|
||||
The resulting value is simply a 16 bit word.
|
||||
|
||||
**Indirect addressing:**
|
||||
@todo
|
||||
|
||||
**Indirect addressing in jumps:**
|
||||
@todo
|
||||
For an indirect ``goto`` statement, the compiler will issue the 6502 CPU's special instruction
|
||||
(``jmp`` indirect). A subroutine call (``jsr`` indirect) is emitted
|
||||
using a couple of instructions.
|
||||
|
||||
|
||||
Conditional Execution
|
||||
---------------------
|
||||
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
rather than having fixed gotos or subroutine calls. IL65 has a *conditional goto* statement for this,
|
||||
that is translated into a comparison (if needed) and then a conditional branch instruction::
|
||||
|
||||
if[_XX] [<expression>] goto <label>
|
||||
|
||||
|
||||
The if-status XX is one of: [cc, cs, vc, vs, eq, ne, true, not, zero, pos, neg, lt, gt, le, ge]
|
||||
It defaults to 'true' (=='ne', not-zero) if omitted. ('pos' will translate into 'pl', 'neg' into 'mi')
|
||||
@todo signed: lts==neg?, gts==eq+pos?, les==neg+eq?, ges==pos?
|
||||
|
||||
The <expression> is optional. If it is provided, it will be evaluated first. Only the [true] and [not] and [zero]
|
||||
if-statuses can be used when such a *comparison expression* is used. An example is::
|
||||
|
||||
if_not A > 55 goto more_iterations
|
||||
|
||||
|
||||
Conditional jumps are compiled into 6502's branching instructions (such as ``bne`` and ``bcc``) so
|
||||
the rather strict limit on how *far* it can jump applies. The compiler itself can't figure this
|
||||
out unfortunately, so it is entirely possible to create code that cannot be assembled successfully.
|
||||
You'll have to restructure your gotos in the code (place target labels closer to the branch)
|
||||
if you run into this type of assembler error.
|
||||
|
||||
|
||||
Assignments
|
||||
-----------
|
||||
|
||||
Assignment statements assign a single value to a target variable or memory location.::
|
||||
|
||||
target = value-expression
|
||||
|
||||
|
||||
Augmented Assignments
|
||||
---------------------
|
||||
|
||||
A special assignment is the *augmented assignment* where the value is modified in-place.
|
||||
Several assignment operators are available: ``+=``, ``-=``, ``&=``, ``|=``, ``^=``, ``<<=``, ``>>=``
|
||||
|
||||
|
||||
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 (``%bbbbbbbb``),
|
||||
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.
|
||||
|
||||
|
||||
Subroutines
|
||||
-----------
|
||||
|
||||
Defining a subroutine
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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,
|
||||
the carry status bit SC and the interrupt disable bit SI as specials).
|
||||
For subroutine return values, the special SZ register is also available, it means the zero status bit.
|
||||
|
||||
The syntax is::
|
||||
|
||||
sub <identifier> ([proc_parameters]) -> ([proc_results]) {
|
||||
... statements ...
|
||||
}
|
||||
|
||||
**proc_parameters =**
|
||||
comma separated list of "<parametername>:<register>" pairs specifying the input parameters.
|
||||
You can omit the parameter names as long as the arguments "line up".
|
||||
(actually, the Python parameter passing rules apply, so you can also mix positional
|
||||
and keyword arguments, as long as the keyword arguments come last)
|
||||
|
||||
**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.
|
||||
For convenience: a single '?' als the result spec is shorthand for ``A?, X?, Y?`` ("I don't know
|
||||
what the changed registers are, assume the worst")
|
||||
|
||||
|
||||
Pre-defined subroutines that are available on specific memory addresses
|
||||
(in system ROM for instance) can also be defined using the 'sub' statement.
|
||||
To do this you assign the routine's memory address to the sub::
|
||||
|
||||
sub <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
|
||||
|
||||
example::
|
||||
|
||||
sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3"
|
||||
|
||||
|
||||
Calling a subroutine
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You call a subroutine like this::
|
||||
|
||||
subroutinename_or_address ( [arguments...] )
|
||||
|
||||
or::
|
||||
|
||||
subroutinename_or_address ![register(s)] ( [arguments...] )
|
||||
|
||||
If the subroutine returns one or more values as results, you must use an assignment statement
|
||||
to store those values somewhere::
|
||||
|
||||
outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 )
|
||||
|
||||
The output variables must occur in the correct sequence of return registers as specified
|
||||
in the subroutine's definiton. It is possible to not specify any of them but the compiler
|
||||
will issue a warning then if the result values of a subroutine call are discarded.
|
||||
If you don't have a variable to store the output register in, it's then required
|
||||
to list the register itself instead as output variable.
|
||||
|
||||
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.
|
||||
|
||||
**Register preserving calls:** use the ``!`` followed by a combination of A, X and Y (or followed
|
||||
by nothing, which is the same as AXY) to tell the compiler you want to preserve the origial
|
||||
value of the given registers after the subroutine call. Otherwise, the subroutine may just
|
||||
as well clobber all three registers. Preserving the original values does result in some
|
||||
stack manipulation code to be inserted for every call like this, which can be quite slow.
|
167
docs/source/targetsystem.rst
Normal file
167
docs/source/targetsystem.rst
Normal file
@ -0,0 +1,167 @@
|
||||
***************************
|
||||
Target system specification
|
||||
***************************
|
||||
|
||||
IL65 targets the following hardware:
|
||||
|
||||
- 8 bit MOS 6502/6510 CPU
|
||||
- 64 Kb addressable memory (RAM or ROM)
|
||||
- memory mapped I/O registers
|
||||
|
||||
The main target machine is the Commodore-64, which is an example of this.
|
||||
This chapter explains the relevant system details of such a machine.
|
||||
|
||||
|
||||
Memory Model
|
||||
============
|
||||
|
||||
Physical address space layout
|
||||
-----------------------------
|
||||
|
||||
The 6502 CPU can address 64 kilobyte of memory.
|
||||
Most of the 64 kilobyte address space can be used by IL65 programs.
|
||||
This is a hard limit: there is no built-in support for RAM expansions or bank switching.
|
||||
|
||||
|
||||
====================== ================== ========
|
||||
memory area type note
|
||||
====================== ================== ========
|
||||
``$00``--``$ff`` ZeroPage contains many sensitive system variables
|
||||
``$100``--``$1ff`` Hardware stack used by the CPU, normally not accessed directly
|
||||
``$0200``--``$ffff`` Free RAM or ROM free to use memory area, often a mix of RAM and ROM
|
||||
====================== ================== ========
|
||||
|
||||
|
||||
A few of these memory addresses are reserved and cannot be used for arbitrary data.
|
||||
They have a special hardware function, or are reserved for internal use in the
|
||||
code generated by the compiler:
|
||||
|
||||
================== =======================
|
||||
reserved address in use for
|
||||
================== =======================
|
||||
``$00`` data direction (CPU hw)
|
||||
``$01`` bank select (CPU hw)
|
||||
``$02`` IL65 scratch variable
|
||||
``$03`` IL65 scratch variable
|
||||
``$fb - $fc`` IL65 scratch variable
|
||||
``$fd - $fe`` IL65 scratch variable
|
||||
``$fffa - $fffb`` NMI vector (CPU hw)
|
||||
``$fffc - $fffd`` RESET vector (CPU hw)
|
||||
``$fffe - $ffff`` IRQ vector (CPU hw)
|
||||
================== =======================
|
||||
|
||||
The actual machine will often have many other special addresses as well,
|
||||
For example, the Commodore-64 has:
|
||||
|
||||
- ROMs installed in the machine: BASIC, kernal and character roms. Occupying ``$a000``--``$bfff`` and ``$e000``--``$ffff``.
|
||||
- memory-mapped I/O registers, for the video and sound chips, and the CIA's. Occupying ``$d000``--``$dfff``.
|
||||
- RAM areas that are used for screen graphics and sprite data: usually at ``$0400``--``$07ff``.
|
||||
|
||||
IL65 programs can access all of those special memory locations but it will have a special meaning.
|
||||
|
||||
|
||||
.. _zeropage:
|
||||
|
||||
ZeroPage ("ZP")
|
||||
---------------
|
||||
|
||||
The ZeroPage memory block ``$02``--``$ff`` can be regarded as 254 CPU 'registers', because
|
||||
they take less clock cycles to access and need fewer instruction bytes than accessing other memory locations outside of the ZP.
|
||||
Theoretically they can all be used in a program, with the follwoing limitations:
|
||||
|
||||
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use
|
||||
- most other addresses will already be in use by the machine's operating system or kernal,
|
||||
and overwriting them will probably crash the machine. It is possible to use all of these
|
||||
yourself, but only if the program takes over the entire system (and seizes control from the regular kernal).
|
||||
This means it can no longer use (most) BASIC and kernal routines from ROM.
|
||||
- it's more convenient and safe to let IL65 allocate these addresses for you and just
|
||||
use symbolic names in the program code.
|
||||
|
||||
Here is the list of the remaining free-to-use ZP addresses with BASIC and KERNAL active in the Commodore-64:
|
||||
|
||||
``$02``, ``$03``, ``$04``, ``$05``, ``$06``, ``$2a``, ``$52``,
|
||||
``$f7 - $f8``, ``$f9 - $fa``, ``$fb - $fc``, ``$fd - $fe``
|
||||
|
||||
*The six reserved addresses mentioned earliser are subtracted from this set,* leaving you with
|
||||
just *five* 1-byte and *two* 2-byte usable ZP 'registers' for use by the program.
|
||||
|
||||
**IL65 knows about all of this.** It will use the free ZP addresses to place its ZP variables in,
|
||||
until they're all used up. If instructed to output a program that takes over the entire
|
||||
machine, (almost) all of the ZP addresses are suddenly available and will be used.
|
||||
IL65 can also generate a special routine that saves and restores the ZP to let the program run
|
||||
and return safely back to the system afterwards - you don't have to take care of that yourself.
|
||||
|
||||
|
||||
IRQs and the ZeroPage
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
|
||||
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
|
||||
|
||||
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
|
||||
|
||||
These addresses will never be used by the compiler for ZP variables, so variables will
|
||||
not interfere with the IRQ routine and vice versa. This is true for the normal ZP mode but also
|
||||
for the mode where the whole system and ZP have been taken over.
|
||||
So the normal IRQ vector can still run and will be when the program is started!
|
||||
|
||||
|
||||
ZeroPage handling is configurable
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
There's a global program directive to specify the way the compiler
|
||||
treats the ZP for the program. The default is to be restrictive to just
|
||||
the few free locations mentioned above, where most of the ZP is considered a no-go zone by the compiler.
|
||||
It's possible to claim the whole ZP as well (by disabling the operating system or kernal),
|
||||
and even ask for a save/restore of the original values to be able to cleanly exit back to a BASIC prompt.
|
||||
|
||||
|
||||
|
||||
CPU
|
||||
===
|
||||
|
||||
Directly Usable Registers
|
||||
-------------------------
|
||||
|
||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
||||
|
||||
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
|
||||
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
|
||||
- ``SC`` status register's Carry flag
|
||||
- ``SI`` status register's Interrupt Disable flag
|
||||
|
||||
The other status bits of the status register are not directly accessible,
|
||||
but can be acted upon via conditional statements.
|
||||
The stack pointer and program counter registers are not accessible.
|
||||
|
||||
|
||||
Subroutine Calling Conventions
|
||||
------------------------------
|
||||
|
||||
Subroutine arguments and results are passed via registers.
|
||||
Sometimes the status register's Carry flag is used as well (as a boolean flag).
|
||||
Additional arguments can be passed via memory locations as well ofcourse.
|
||||
But you'll have to be careful when dealing with chained or even recursive calls then,
|
||||
because there's a big risk of overwriting those memory locations.
|
||||
|
||||
In IL65 the "caller saves" principle applies to calling subroutines.
|
||||
This means the code that calls a subroutine that clobbers certain
|
||||
registers (``A``, ``X`` or ``Y``), is responsible for storing and restoring the original values if
|
||||
those values are needed by the rest of the code.
|
||||
|
||||
Normally, registers are *not* preserved when calling a subroutine or when a certian
|
||||
operations are performed. Most calls will be simply a few instructions to load the
|
||||
values in the registers and then a ``JSR`` or ``JMP``.
|
||||
|
||||
By using the ``%saveregisters`` directive in a block, you can tell the
|
||||
compiler to preserve all registers. This does generate a lot of extra code that puts
|
||||
original values on the stack and gets them off the stack again once the subroutine is done.
|
||||
In this case however you don't have to worry about ``A``, ``X`` and ``Y`` losing their original values
|
||||
and you can essentially treat them as three local variables instead of scratch data.
|
||||
|
||||
You can also use a ``!`` on a single subroutine call to preserve register values, instead of
|
||||
setting this behavior for the entire block.
|
||||
|
||||
.. important::
|
||||
Basically, you should assume that the 3 hardware registers ``A``, ``X`` and ``Y``
|
||||
are volatile. Their values cannot be depended upon, unless you explicitly make sure otherwise.
|
155
docs/source/todo.rst
Normal file
155
docs/source/todo.rst
Normal file
@ -0,0 +1,155 @@
|
||||
====
|
||||
TODO
|
||||
====
|
||||
|
||||
|
||||
IF_XX::
|
||||
|
||||
if[_XX] [<expression>] {
|
||||
...
|
||||
}
|
||||
[ else {
|
||||
... ; evaluated when the condition is not met
|
||||
} ]
|
||||
|
||||
|
||||
==> DESUGARING ==>
|
||||
|
||||
(no else:)::
|
||||
|
||||
if[_!XX] [<expression>] goto 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>] goto il65_if_999
|
||||
... (else part)
|
||||
goto 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 goto ....
|
||||
XX based on <COMPARISON>.
|
||||
|
||||
|
||||
While::
|
||||
|
||||
while[_XX] <expression> {
|
||||
...
|
||||
continue
|
||||
break
|
||||
}
|
||||
|
||||
==> DESUGARING ==>::
|
||||
|
||||
goto il65_while_999_check ; jump to the check
|
||||
il65_while_999
|
||||
... (code)
|
||||
goto il65_while_999 ;continue
|
||||
goto il65_while_999_end ;break
|
||||
il65_while_999_check
|
||||
if[_XX] <expression> goto 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)
|
||||
goto il65_repeat_999 ;continue
|
||||
goto il65_repeat_999_end ;break
|
||||
if[_!XX] <expression> goto 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 goto il65_for_999_end ; loop condition
|
||||
step = <step_expression> ; (store only if step < -1 or step > 1)
|
||||
il65_for_999
|
||||
goto il65_for_999_end ;break
|
||||
goto 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)
|
||||
goto il65_for_999 ; continue the loop
|
||||
il65_for_999_end ; code continues after this
|
||||
|
||||
|
||||
|
||||
### 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)]``
|
||||
|
||||
|
||||
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.
|
||||
|
||||
- matrix with row-interleave can only be a memory mapped variable and can be used to directly
|
||||
access a rectangular area within another piece of memory - such as a rectangle on the (character) screen
|
||||
|
||||
these should call (or emit inline) optimized pieces of assembly code, so they run as fast as possible
|
||||
|
||||
|
||||
|
||||
Bitmap Definition (for Sprites and Characters)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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)
|
||||
|
137
docs/system.md
137
docs/system.md
@ -1,137 +0,0 @@
|
||||
System documentation: memory model and CPU
|
||||
------------------------------------------
|
||||
|
||||
### Memory address space layout
|
||||
|
||||
The 6502 CPU can directly address 64 kilobyte of memory.
|
||||
Most of the 64 kilobyte address space can be used by IL65 programs.
|
||||
|
||||
|
||||
| type | memory area | note |
|
||||
|-----------------|-------------------------|-----------------------------------------------------------------|
|
||||
| ZeroPage | ``$00`` - ``$ff`` | contains many sensitive system variables |
|
||||
| Hardware stack | ``$100`` - ``$1ff`` | is used by the CPU and should normally not be accessed directly |
|
||||
| Free RAM or ROM | ``$0200`` - ``$ffff`` | free to use memory area, often a mix of RAM and ROM |
|
||||
|
||||
|
||||
A few memory addresses are reserved and cannot be used,
|
||||
because they have a special hardware function, or are reserved for internal use in the
|
||||
code generated by the compiler:
|
||||
|
||||
| reserved address | in use for |
|
||||
|------------------|----------------------|
|
||||
| ``$00`` | data direction (hw) |
|
||||
| ``$01`` | bank select (hw) |
|
||||
| ``$02`` | IL65 scratch var |
|
||||
| ``$03`` | IL65 scratch var |
|
||||
| ``$fb-$fc`` | IL65 scratch var |
|
||||
| ``$fd-$fe`` | IL65 scratch var |
|
||||
| ``$fffa-$fffb`` | NMI vector (hw) |
|
||||
| ``$fffc-$fffd`` | RESET vector (hw) |
|
||||
| ``$fffe-$ffff`` | IRQ vector (hw) |
|
||||
|
||||
A particular 6502/6510 based machine such as the Commodore-64 will have many other special addresses as well:
|
||||
- ROMs installed in the machine (BASIC, kernal and character roms)
|
||||
- memory-mapped I/O registers (for the video and sound chip for example)
|
||||
- RAM areas used for screen graphics and sprite data
|
||||
|
||||
|
||||
### Directly Usable CPU Registers
|
||||
|
||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
||||
|
||||
- ``A``, ``X``, ``Y`` the A, X and Y cpu registers (8 bits)
|
||||
- ``AX``, ``AY``, ``XY`` surrogate 16-bit registers: LSB-order (lo/hi) combined register pairs
|
||||
- ``SC`` status register's Carry flag
|
||||
- ``SI`` status register's Interrupt Disable flag
|
||||
|
||||
The other status bits of the status register are not directly accessible,
|
||||
but can be acted upon via conditional statements.
|
||||
The stack pointer and program counter registers are not accessible.
|
||||
|
||||
|
||||
### ZeroPage ("ZP")
|
||||
|
||||
The ZP addresses ``$02`` - ``$ff`` can be regarded as 254 other 'registers', because
|
||||
they take less clock cycles to access and need fewer instruction bytes than access to other memory locations.
|
||||
Theoretically you can use all of them in a program but there are a few limitations:
|
||||
- several addresses (``$02``, ``$03``, ``$fb - $fc``, ``$fd - $fe``) are reserved for internal use as scratch registers by IL65
|
||||
- most other addresses often are in use by the machine's operating system or kernal,
|
||||
and overwriting them can crash the machine. The program must take over the entire
|
||||
system to be able to safely use all ZP addresses. This means it can no longer use
|
||||
most BASIC and kernal routines.
|
||||
- it's often more convenient to let IL65 allocate the particular addresses for you and just
|
||||
use symbolic names in the code.
|
||||
|
||||
For the Commodore-64 here is a list of free-to-use ZP addresses even when its BASIC and KERNAL are active:
|
||||
|
||||
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
|
||||
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
|
||||
|
||||
The six reserved addresses mentioned above are subtracted from this set, leaving you with
|
||||
just five 1-byte and two 2-byte usable ZP 'registers'.
|
||||
IL65 knows about all this: it will use the above ZP addresses to place its ZP variables in,
|
||||
until they're all used up. You can instruct it to output a program that takes over the entire
|
||||
machine, in which case (almost) all of the ZP addresses are suddenly available for variables.
|
||||
IL65 can also generate a special routine that saves and restores the ZP to let the program run
|
||||
and return safely back to the system afterwards - you don't have to take care of that yourself.
|
||||
|
||||
**IRQs and the ZeroPage:**
|
||||
|
||||
The normal IRQ routine in the C-64's kernal will read and write several addresses in the ZP
|
||||
(such as the system's software jiffy clock which sits in ``$a0 - $a2``):
|
||||
|
||||
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
|
||||
|
||||
These addresses will never be used by the compiler for ZP variables, so variables will
|
||||
not interfere with the IRQ routine and vice versa. This is tru for the normal zp mode but also
|
||||
for the mode where the whole ZP has been taken over. So the normal IRQ vector is still
|
||||
running when the program is entered!
|
||||
|
||||
|
||||
### ZeroPage handling in programs
|
||||
|
||||
The global ``%zp`` directive can be used to specify the way the compiler
|
||||
will treat the ZP for the program. The default is ``compatible``, where most
|
||||
of the ZP is considered a no-go zone by the compiler.
|
||||
|
||||
- ``compatible`` : only use the few 'free' addresses in the ZP, and don't change anything else.
|
||||
This allows full use of BASIC and KERNAL ROM routines including default IRQs during normal system operation.
|
||||
- ``full`` : claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
It is also not possible to cleanly exit the program, other than resetting the machine.
|
||||
This option makes programs smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
- ``full-restore`` : like ``full``, but makes a backup copy of the original values at program start.
|
||||
These are restored (except for the software jiffy clock in ``$a0 - $a2``)
|
||||
when the program exits, and allows it to exit back to the BASIC prompt.
|
||||
|
||||
|
||||
### Subroutine Calling Conventions
|
||||
|
||||
Subroutine arguments and results are passed via registers (and sometimes implicitly
|
||||
via certain memory locations).
|
||||
@todo support call non-register args (variable parameter passing)
|
||||
|
||||
In IL65 the "caller saves" principle applies to registers used in a subroutine.
|
||||
This means the code that calls a subroutine or performs some function that clobber certain
|
||||
registers (A, X or Y), is responsible for storing and restoring the original values if
|
||||
that is required.
|
||||
|
||||
*You should assume that the 3 hardware registers A, X and Y are volatile and their contents
|
||||
cannot be depended upon, unless you make sure otherwise*.
|
||||
|
||||
Normally, the registers are NOT preserved when calling a subroutine or when a certian
|
||||
operations are performed. Most calls will be simply a few instructions to load the
|
||||
values in the registers and then a JSR or JMP.
|
||||
|
||||
By using the ``%saveregisters`` directive in a block, you can tell the
|
||||
compiler to preserve all registers. This does generate a lot of extra code that puts
|
||||
original values on the stack and gets them off the stack again once the subroutine is done.
|
||||
In this case however you don't have to worry about A, X and Y losing their original values
|
||||
and you can essentially treat them as three local variables instead of scratch data.
|
||||
|
||||
You can also use a ``!`` on a single subroutine call to preserve register values, instead of
|
||||
setting this behavior for the entire block.
|
@ -9,23 +9,27 @@
|
||||
|
||||
|
||||
~ c64 {
|
||||
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
memory .byte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
memory .byte TIME_MID = $a1 ; .. mid byte
|
||||
memory .byte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
memory .byte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
memory .byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
memory byte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
memory byte TIME_MID = $a1 ; .. mid byte
|
||||
memory byte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
memory byte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
memory byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
memory .byte COLOR = $0286 ; cursor color
|
||||
memory .byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
memory .word CINV = $0314 ; IRQ vector
|
||||
memory .matrix(40, 25) Screen = $0400 ; default character screen matrix
|
||||
memory .matrix(40, 25) Colors = $d800 ; character screen colors
|
||||
memory byte COLOR = $0286 ; cursor color
|
||||
memory byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
memory word CINV = $0314 ; IRQ vector
|
||||
memory word NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
memory word RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
memory word IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
memory matrix(40, 25) Screen = $0400 ; default character screen matrix
|
||||
memory matrix(40, 25) Colors = $d800 ; character screen colors
|
||||
|
||||
|
||||
; ---- VIC-II registers ----
|
||||
@ -88,20 +92,20 @@
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||
memory .float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
memory .float FL_N32768 = $b1a5 ; -32768
|
||||
memory .float FL_FONE = $b9bc ; 1
|
||||
memory .float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
memory .float FL_SQRTWO = $b9db ; SQR(2)
|
||||
memory .float FL_NEGHLF = $b9e0 ; -.5
|
||||
memory .float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
memory .float FL_TENC = $baf9 ; 10
|
||||
memory .float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
memory .float FL_FHALF = $bf11 ; .5
|
||||
memory .float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
memory .float FL_PIHALF = $e2e0 ; PI / 2
|
||||
memory .float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
memory .float FL_FR4 = $e2ea ; .25
|
||||
memory float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
memory float FL_N32768 = $b1a5 ; -32768
|
||||
memory float FL_FONE = $b9bc ; 1
|
||||
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
memory float FL_SQRTWO = $b9db ; SQR(2)
|
||||
memory float FL_NEGHLF = $b9e0 ; -.5
|
||||
memory float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
memory float FL_TENC = $baf9 ; 10
|
||||
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
memory float FL_FHALF = $bf11 ; .5
|
||||
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
memory float FL_PIHALF = $e2e0 ; PI / 2
|
||||
memory float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
memory float FL_FR4 = $e2ea ; .25
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
@ -116,18 +120,26 @@ sub MOVFA () -> (A?, X?) = $bbfc ; copy fac2 to fac1
|
||||
sub MOVAF () -> (A?, X?) = $bc0c ; copy fac1 to fac2 (rounded)
|
||||
sub MOVEF () -> (A?, X?) = $bc0f ; copy fac1 to fac2
|
||||
sub FTOMEMXY (mflpt: XY) -> (A?, Y?) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
sub FTOSWORDYA () -> (Y, A, X?) = $b1aa ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; use c64flt.FTOSWRDAY to get A/Y output (lo/hi switched to normal order)
|
||||
sub GETADR () -> (Y, A, X?) = $b7f7 ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; (result also in $14/15) use c64flt.GETADRAY to get A/Y output (lo/hi switched to normal order)
|
||||
|
||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; (use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal order)
|
||||
sub FTOSWORDYA () -> (Y, A, X?) = $b1aa
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal order)
|
||||
sub GETADR () -> (Y, A, X?) = $b7f7
|
||||
|
||||
sub QINT () -> (?) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||
sub AYINT () -> (?) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
sub GIVAYF (lo: Y, hi: A) -> (?) = $b391 ; signed word in Y/A -> float in fac1
|
||||
; use c64flt.GIVAYFAY to use A/Y input (lo/hi switched to normal order)
|
||||
|
||||
; signed word in Y/A -> float in fac1
|
||||
; (use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also c64flt.GIVUAYF - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
sub GIVAYF (lo: Y, hi: A) -> (?) = $b391
|
||||
|
||||
sub FREADUY (ubyte: Y) -> (?) = $b3a2 ; 8 bit unsigned Y -> float in fac1
|
||||
sub FREADSA (sbyte: A) -> (?) = $bc3c ; 8 bit signed A -> float in fac1
|
||||
sub FREADSTR (len: A) -> (?) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
|
||||
@ -223,10 +235,6 @@ sub IOBASE () -> (X, Y) = $FFF3 ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
|
||||
memory .word NMI_VEC = $FFFA ; nmi vector, set by the kernal if banked in
|
||||
memory .word RESET_VEC = $FFFC ; reset vector, set by the kernal if banked in
|
||||
memory .word IRQ_VEC = $FFFE ; interrupt vector, set by the kernal if banked in
|
||||
|
||||
|
||||
|
||||
; ----- utility functions ----
|
||||
@ -735,7 +743,7 @@ hex_digits .str "0123456789abcdef" ; can probably be reused for other stuff as w
|
||||
}
|
||||
|
||||
|
||||
var .str word2hex_output = "1234" ; 0-terminated, to make printing easier
|
||||
var str word2hex_output = "1234" ; 0-terminated, to make printing easier
|
||||
sub word2hex (word: XY) -> (?) {
|
||||
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
|
||||
%asm {
|
||||
@ -753,7 +761,7 @@ sub word2hex (word: XY) -> (?) {
|
||||
}
|
||||
|
||||
|
||||
var .array(3) word2bcd_bcdbuff
|
||||
var array(3) word2bcd_bcdbuff
|
||||
sub word2bcd (word: XY) -> (A?, X?) {
|
||||
; Convert an 16 bit binary value to BCD
|
||||
;
|
||||
@ -791,7 +799,7 @@ sub word2bcd (word: XY) -> (A?, X?) {
|
||||
}
|
||||
|
||||
|
||||
var .array(5) word2decimal_output
|
||||
var array(5) word2decimal_output
|
||||
sub word2decimal (word: XY) -> (?) {
|
||||
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
|
||||
%asm {
|
||||
|
@ -7,10 +7,10 @@
|
||||
|
||||
~ il65_lib {
|
||||
; note: the following ZP scratch registers must be the same as in c64lib
|
||||
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
%asm {
|
||||
|
@ -12,10 +12,10 @@
|
||||
|
||||
~ math {
|
||||
; note: the following ZP scratch registers must be the same as in c64lib
|
||||
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
|
||||
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
attrs
|
||||
ply
|
||||
cbmcodecs >= 0.2.0
|
||||
|
||||
|
@ -4,15 +4,15 @@
|
||||
|
||||
|
||||
|
||||
var .word var1 = 99
|
||||
memory .word mem1 = $cff0
|
||||
var word var1 = 99
|
||||
memory word mem1 = $cff0
|
||||
|
||||
var .byte varb1 = 99
|
||||
memory .byte memb1 = $cff0
|
||||
const .word constw = $2355
|
||||
const .byte constb = $23
|
||||
const .float constf = 3.4556677
|
||||
const .str constt = "derp"
|
||||
var byte varb1 = 99
|
||||
memory byte memb1 = $cff0
|
||||
const word constw = $2355
|
||||
const byte constb = $23
|
||||
const float constf = 3.4556677
|
||||
const str constt = "derp"
|
||||
|
||||
sub sub1 () -> (X?) = $ffdd
|
||||
sub sub2 (A) -> (Y?) = $eecc
|
||||
|
@ -5,8 +5,8 @@
|
||||
|
||||
~ main {
|
||||
|
||||
var .word value
|
||||
memory .word memvalue = $8000
|
||||
var word value
|
||||
memory word memvalue = $8000
|
||||
|
||||
|
||||
start:
|
||||
@ -78,9 +78,9 @@ label4:
|
||||
|
||||
~ conditionals {
|
||||
var bytevar = 22 + 23
|
||||
var .str name = "?"*80
|
||||
var str name = "?"*80
|
||||
var bytevar2 = 23
|
||||
var .word wordvar = 22345
|
||||
var word wordvar = 22345
|
||||
|
||||
|
||||
start:
|
||||
|
@ -18,11 +18,11 @@
|
||||
var zpvar2
|
||||
memory zpmem1 = $f0
|
||||
const zpconst = 1.234
|
||||
var .word zpvarw1
|
||||
var .word zpvarw2
|
||||
var .float zpvarflt1 = 11.11
|
||||
var .float zpvarflt2 = 22.22
|
||||
var .float zpvarflt3
|
||||
var word zpvarw1
|
||||
var word zpvarw2
|
||||
var float zpvarflt1 = 11.11
|
||||
var float zpvarflt2 = 22.22
|
||||
var float zpvarflt3
|
||||
|
||||
}
|
||||
|
||||
@ -43,96 +43,96 @@
|
||||
~ main {
|
||||
; variables
|
||||
var uninitbyte1
|
||||
var .byte uninitbyte2
|
||||
var byte uninitbyte2
|
||||
var initbyte1 = $12
|
||||
var initbyte1b = true
|
||||
var .byte initbyte2 = $12
|
||||
var .byte initbyte2b = false
|
||||
var .byte initbyte3 = 99.876
|
||||
var initchar1 = '@'
|
||||
var .byte initchar2 = '@'
|
||||
var .word uninitword
|
||||
var .word initword1 = $1234
|
||||
var .word initword1b = true
|
||||
var .word initword2 = false
|
||||
var .word initword3 = 9876.554321
|
||||
var .word initword5 = 20
|
||||
var .float uninitfloat
|
||||
var .float initfloat1 = 0
|
||||
var .float initfloat1b = true
|
||||
var .float initfloat2 = -1.234e-14
|
||||
var .float initfloat3 = 9.87e+14
|
||||
var .float initfloat4 = 1.70141183e+38
|
||||
var .float initfloat5 = -1.70141183e+38
|
||||
var .float initfloat6 = 1.234
|
||||
var byte initbyte2 = $12
|
||||
var byte initbyte2b = false
|
||||
var byte initbyte3 = 99.876
|
||||
var initchar1 = '@'
|
||||
var byte initchar2 = '@'
|
||||
var word uninitword
|
||||
var word initword1 = $1234
|
||||
var word initword1b = true
|
||||
var word initword2 = false
|
||||
var word initword3 = 9876.554321
|
||||
var word initword5 = 20
|
||||
var float uninitfloat
|
||||
var float initfloat1 = 0
|
||||
var float initfloat1b = true
|
||||
var float initfloat2 = -1.234e-14
|
||||
var float initfloat3 = 9.87e+14
|
||||
var float initfloat4 = 1.70141183e+38
|
||||
var float initfloat5 = -1.70141183e+38
|
||||
var float initfloat6 = 1.234
|
||||
|
||||
var .wordarray( 256 ) uninit_wordarray
|
||||
var .wordarray(10) init_wordarray = $1234
|
||||
var .wordarray(10) init_wordarrayb = true
|
||||
var .array( 256) uninit_bytearray
|
||||
var .array(10 ) init_bytearray =$12
|
||||
var .array(10 ) init_bytearrayb =true
|
||||
var .array(10 ) init_bytearrayc ='@'
|
||||
var wordarray( 256 ) uninit_wordarray
|
||||
var wordarray(10) init_wordarray = $1234
|
||||
var wordarray(10) init_wordarrayb = true
|
||||
var array( 256) uninit_bytearray
|
||||
var array(10 ) init_bytearray =$12
|
||||
var array(10 ) init_bytearrayb =true
|
||||
var array(10 ) init_bytearrayc ='@'
|
||||
|
||||
var .str text = "hello-null"
|
||||
var .strp ptext = 'hello-pascal'
|
||||
var .strs stext = 'screencodes-null'
|
||||
var .strps pstext = "screencodes-pascal"
|
||||
var str text = "hello-null"
|
||||
var strp ptext = 'hello-pascal'
|
||||
var strs stext = 'screencodes-null'
|
||||
var strps pstext = "screencodes-pascal"
|
||||
|
||||
var .matrix( 2, 128 ) uninitmatrix
|
||||
var .matrix(10, 20) initmatrix1 = $12
|
||||
var .matrix(10, 20) initmatrix1b = true
|
||||
var .matrix(10, 20) initmatrix1c = '@'
|
||||
var .matrix(10, 20) initmatrix1d = 123.456
|
||||
var matrix( 2, 128 ) uninitmatrix
|
||||
var matrix(10, 20) initmatrix1 = $12
|
||||
var matrix(10, 20) initmatrix1b = true
|
||||
var matrix(10, 20) initmatrix1c = '@'
|
||||
var matrix(10, 20) initmatrix1d = 123.456
|
||||
|
||||
; memory-mapped variables
|
||||
memory membyte1 = $cf01
|
||||
memory .byte membyte2 = $c222
|
||||
memory .word memword1 = $cf03
|
||||
memory .float memfloat = $cf04
|
||||
memory .array(10 ) membytes = $cf05
|
||||
memory .wordarray( 10) memwords = $cf06
|
||||
memory .matrix( 10, 20 ) memmatrix = $cf07
|
||||
memory byte membyte2 = $c222
|
||||
memory word memword1 = $cf03
|
||||
memory float memfloat = $cf04
|
||||
memory array(10 ) membytes = $cf05
|
||||
memory wordarray( 10) memwords = $cf06
|
||||
memory matrix( 10, 20 ) memmatrix = $cf07
|
||||
|
||||
; constants (= names for constant values, can never occur as lvalue)
|
||||
const cbyte1 = 1
|
||||
const cbyte1b = false
|
||||
const .byte cbyte2 = 1
|
||||
const .byte cbyte3 = '@'
|
||||
const .byte cbyte4 = true
|
||||
const .word cword1 = false
|
||||
const .word cword2 = $1234
|
||||
const .word cword5 = 9876.5432
|
||||
const byte cbyte2 = 1
|
||||
const byte cbyte3 = '@'
|
||||
const byte cbyte4 = true
|
||||
const word cword1 = false
|
||||
const word cword2 = $1234
|
||||
const word cword5 = 9876.5432
|
||||
const cfloat1 = 1.2345
|
||||
const .float cfloat2 = 2.3456
|
||||
const .float cfloat2b = cfloat2*3.44
|
||||
const .float cfloat3 = true
|
||||
const .str ctext3 = "constant-text"
|
||||
const .strp ctext4 = "constant-ptext"
|
||||
const .strs ctext5 = "constant-stext"
|
||||
const .strps ctext6 = "constant-pstext"
|
||||
const float cfloat2 = 2.3456
|
||||
const float cfloat2b = cfloat2*3.44
|
||||
const float cfloat3 = true
|
||||
const str ctext3 = "constant-text"
|
||||
const strp ctext4 = "constant-ptext"
|
||||
const strs ctext5 = "constant-stext"
|
||||
const strps ctext6 = "constant-pstext"
|
||||
|
||||
; taking the address of various things:
|
||||
var .word vmemaddr1 = &membyte1
|
||||
var .word vmemaddr2 = &memword1
|
||||
var .word vmemaddr3 = &memfloat
|
||||
var .word vmemaddr4 = &membytes
|
||||
var .word vmemaddr5 = &memwords
|
||||
var .word vmemaddr6 = &memmatrix
|
||||
var .word vmemaddr8 = 100*sin(cbyte1)
|
||||
var .word vmemaddr9 = cword2+$5432
|
||||
var .word vmemaddr10 = cfloat2b
|
||||
var word vmemaddr1 = &membyte1
|
||||
var word vmemaddr2 = &memword1
|
||||
var word vmemaddr3 = &memfloat
|
||||
var word vmemaddr4 = &membytes
|
||||
var word vmemaddr5 = &memwords
|
||||
var word vmemaddr6 = &memmatrix
|
||||
var word vmemaddr8 = 100*sin(cbyte1)
|
||||
var word vmemaddr9 = cword2+$5432
|
||||
var word vmemaddr10 = cfloat2b
|
||||
|
||||
; taking the address of things from the ZP will work even when it is a var
|
||||
; because zp-vars get assigned a specific address (from a pool). Also, it's a byte.
|
||||
|
||||
var .word initword0a = &ZP.zpmem1
|
||||
var word initword0a = &ZP.zpmem1
|
||||
var initbytea0 = &ZP.zpmem1
|
||||
|
||||
|
||||
; (constant) expressions
|
||||
var .word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
|
||||
var .byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
|
||||
var word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
|
||||
var byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
|
||||
|
||||
|
||||
sin:
|
||||
|
@ -6,15 +6,15 @@
|
||||
|
||||
~ main_testing {
|
||||
start:
|
||||
var .float myfloat1 = 1234.56789
|
||||
var .float myfloat2 = 9876.54321
|
||||
var .float myfloatneg1 = -555.666
|
||||
var .float myfloat_large1 = 987654.1111
|
||||
var .float myfloat_large2 = -123456.2222
|
||||
var .str myfloatstr = "1234.998877"
|
||||
var .float myfloatzero = 0
|
||||
var .float myfloatsmall1 = 1.234
|
||||
var .float myfloatsmall2 = 2.6677
|
||||
var float myfloat1 = 1234.56789
|
||||
var float myfloat2 = 9876.54321
|
||||
var float myfloatneg1 = -555.666
|
||||
var float myfloat_large1 = 987654.1111
|
||||
var float myfloat_large2 = -123456.2222
|
||||
var str myfloatstr = "1234.998877"
|
||||
var float myfloatzero = 0
|
||||
var float myfloatsmall1 = 1.234
|
||||
var float myfloatsmall2 = 2.6677
|
||||
|
||||
return
|
||||
}
|
||||
@ -22,21 +22,25 @@ start:
|
||||
|
||||
~ main {
|
||||
|
||||
var .float flt_pi = 3.141592653589793
|
||||
var .float flt_minus32768 = -32768
|
||||
var .float flt_1 = 1
|
||||
var .float flt_half_sqr2 = 0.7071067811865476
|
||||
var .float flt_sqr2 = 1.4142135623730951
|
||||
var .float flt_minus_half = -.5
|
||||
var .float flt_log_2 = 0.6931471805599453
|
||||
var .float flt_10 = 10
|
||||
var .float flt_1e9 = 1000000000
|
||||
var .float flt_half = .5
|
||||
var .float flt_one_over_log_2 = 1.4426950408889634
|
||||
var .float flt_half_pi = 1.5707963267948966
|
||||
var .float flt_double_pi = 6.283185307179586
|
||||
var .float flt_point25 = .25
|
||||
var .float my_float
|
||||
byte bbvar
|
||||
|
||||
float@
|
||||
|
||||
var float flt_pi = 3.141592653589793
|
||||
var float flt_minus32768 = -32768
|
||||
var float flt_1 = 1
|
||||
var float flt_half_sqr2 = 0.7071067811865476
|
||||
var float flt_sqr2 = 1.4142135623730951
|
||||
var float flt_minus_half = -.5
|
||||
var float flt_log_2 = 0.6931471805599453
|
||||
var float flt_10 = 10
|
||||
var float flt_1e9 = 1000000000
|
||||
var float flt_half = .5
|
||||
var float flt_one_over_log_2 = 1.4426950408889634
|
||||
var float flt_half_pi = 1.5707963267948966
|
||||
var float flt_double_pi = 6.283185307179586
|
||||
var float flt_point25 = .25
|
||||
var float my_float
|
||||
memory .word some_address = $ccdd
|
||||
memory .byte some_addressb = $ccee
|
||||
var .byte bytevar = $cc
|
||||
|
Loading…
Reference in New Issue
Block a user