mirror of
https://github.com/irmen/prog8.git
synced 2024-11-25 04:31:20 +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
|
parser.out
|
||||||
parsetab.py
|
parsetab.py
|
||||||
!/il65/lib/*
|
!/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 {
|
~ c64 {
|
||||||
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
|
||||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 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_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||||
|
|
||||||
|
|
||||||
memory .byte TIME_HI = $a0 ; software jiffy clock, hi byte
|
memory byte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||||
memory .byte TIME_MID = $a1 ; .. mid byte
|
memory byte TIME_MID = $a1 ; .. mid byte
|
||||||
memory .byte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
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 STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||||
memory .byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
memory byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||||
|
|
||||||
memory .byte COLOR = $0286 ; cursor color
|
memory byte COLOR = $0286 ; cursor color
|
||||||
memory .byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
memory byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||||
memory .word CINV = $0314 ; IRQ vector
|
memory word CINV = $0314 ; IRQ vector
|
||||||
memory .matrix(40, 25) Screen = $0400 ; default character screen matrix
|
memory word NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||||
memory .matrix(40, 25) Colors = $d800 ; character screen colors
|
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 ----
|
; ---- VIC-II registers ----
|
||||||
@ -88,20 +92,20 @@
|
|||||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||||
|
|
||||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||||
memory .float FL_PIVAL = $aea8 ; 3.1415926...
|
memory float FL_PIVAL = $aea8 ; 3.1415926...
|
||||||
memory .float FL_N32768 = $b1a5 ; -32768
|
memory float FL_N32768 = $b1a5 ; -32768
|
||||||
memory .float FL_FONE = $b9bc ; 1
|
memory float FL_FONE = $b9bc ; 1
|
||||||
memory .float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||||
memory .float FL_SQRTWO = $b9db ; SQR(2)
|
memory float FL_SQRTWO = $b9db ; SQR(2)
|
||||||
memory .float FL_NEGHLF = $b9e0 ; -.5
|
memory float FL_NEGHLF = $b9e0 ; -.5
|
||||||
memory .float FL_LOG2 = $b9e5 ; LOG(2)
|
memory float FL_LOG2 = $b9e5 ; LOG(2)
|
||||||
memory .float FL_TENC = $baf9 ; 10
|
memory float FL_TENC = $baf9 ; 10
|
||||||
memory .float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||||
memory .float FL_FHALF = $bf11 ; .5
|
memory float FL_FHALF = $bf11 ; .5
|
||||||
memory .float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||||
memory .float FL_PIHALF = $e2e0 ; PI / 2
|
memory float FL_PIHALF = $e2e0 ; PI / 2
|
||||||
memory .float FL_TWOPI = $e2e5 ; 2 * PI
|
memory float FL_TWOPI = $e2e5 ; 2 * PI
|
||||||
memory .float FL_FR4 = $e2ea ; .25
|
memory float FL_FR4 = $e2ea ; .25
|
||||||
|
|
||||||
|
|
||||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
; 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 MOVAF () -> (A?, X?) = $bc0c ; copy fac1 to fac2 (rounded)
|
||||||
sub MOVEF () -> (A?, X?) = $bc0f ; copy fac1 to fac2
|
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 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)
|
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||||
sub GETADR () -> (Y, A, X?) = $b7f7 ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY)
|
; (use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal order)
|
||||||
; (result also in $14/15) use c64flt.GETADRAY 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 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 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.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.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.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)
|
; 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 FREADUY (ubyte: Y) -> (?) = $b3a2 ; 8 bit unsigned Y -> float in fac1
|
||||||
sub FREADSA (sbyte: A) -> (?) = $bc3c ; 8 bit signed A -> 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
|
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 ----
|
; ---- 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 ----
|
; ----- 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) -> (?) {
|
sub word2hex (word: XY) -> (?) {
|
||||||
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
|
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
|
||||||
%asm {
|
%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?) {
|
sub word2bcd (word: XY) -> (A?, X?) {
|
||||||
; Convert an 16 bit binary value to BCD
|
; 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) -> (?) {
|
sub word2decimal (word: XY) -> (?) {
|
||||||
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
|
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
|
||||||
%asm {
|
%asm {
|
||||||
|
@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
~ il65_lib {
|
~ il65_lib {
|
||||||
; note: the following ZP scratch registers must be the same as in c64lib
|
; 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_ZP1 = $02 ; scratch register #1 in ZP
|
||||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 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_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||||
|
|
||||||
|
|
||||||
%asm {
|
%asm {
|
||||||
|
@ -12,10 +12,10 @@
|
|||||||
|
|
||||||
~ math {
|
~ math {
|
||||||
; note: the following ZP scratch registers must be the same as in c64lib
|
; 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_ZP1 = $02 ; scratch register #1 in ZP
|
||||||
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 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_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||||
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
attrs
|
attrs
|
||||||
ply
|
ply
|
||||||
cbmcodecs >= 0.2.0
|
cbmcodecs >= 0.2.0
|
||||||
|
|
||||||
|
@ -4,15 +4,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
var .word var1 = 99
|
var word var1 = 99
|
||||||
memory .word mem1 = $cff0
|
memory word mem1 = $cff0
|
||||||
|
|
||||||
var .byte varb1 = 99
|
var byte varb1 = 99
|
||||||
memory .byte memb1 = $cff0
|
memory byte memb1 = $cff0
|
||||||
const .word constw = $2355
|
const word constw = $2355
|
||||||
const .byte constb = $23
|
const byte constb = $23
|
||||||
const .float constf = 3.4556677
|
const float constf = 3.4556677
|
||||||
const .str constt = "derp"
|
const str constt = "derp"
|
||||||
|
|
||||||
sub sub1 () -> (X?) = $ffdd
|
sub sub1 () -> (X?) = $ffdd
|
||||||
sub sub2 (A) -> (Y?) = $eecc
|
sub sub2 (A) -> (Y?) = $eecc
|
||||||
|
@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
~ main {
|
~ main {
|
||||||
|
|
||||||
var .word value
|
var word value
|
||||||
memory .word memvalue = $8000
|
memory word memvalue = $8000
|
||||||
|
|
||||||
|
|
||||||
start:
|
start:
|
||||||
@ -78,9 +78,9 @@ label4:
|
|||||||
|
|
||||||
~ conditionals {
|
~ conditionals {
|
||||||
var bytevar = 22 + 23
|
var bytevar = 22 + 23
|
||||||
var .str name = "?"*80
|
var str name = "?"*80
|
||||||
var bytevar2 = 23
|
var bytevar2 = 23
|
||||||
var .word wordvar = 22345
|
var word wordvar = 22345
|
||||||
|
|
||||||
|
|
||||||
start:
|
start:
|
||||||
|
@ -18,11 +18,11 @@
|
|||||||
var zpvar2
|
var zpvar2
|
||||||
memory zpmem1 = $f0
|
memory zpmem1 = $f0
|
||||||
const zpconst = 1.234
|
const zpconst = 1.234
|
||||||
var .word zpvarw1
|
var word zpvarw1
|
||||||
var .word zpvarw2
|
var word zpvarw2
|
||||||
var .float zpvarflt1 = 11.11
|
var float zpvarflt1 = 11.11
|
||||||
var .float zpvarflt2 = 22.22
|
var float zpvarflt2 = 22.22
|
||||||
var .float zpvarflt3
|
var float zpvarflt3
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,96 +43,96 @@
|
|||||||
~ main {
|
~ main {
|
||||||
; variables
|
; variables
|
||||||
var uninitbyte1
|
var uninitbyte1
|
||||||
var .byte uninitbyte2
|
var byte uninitbyte2
|
||||||
var initbyte1 = $12
|
var initbyte1 = $12
|
||||||
var initbyte1b = true
|
var initbyte1b = true
|
||||||
var .byte initbyte2 = $12
|
var byte initbyte2 = $12
|
||||||
var .byte initbyte2b = false
|
var byte initbyte2b = false
|
||||||
var .byte initbyte3 = 99.876
|
var byte initbyte3 = 99.876
|
||||||
var initchar1 = '@'
|
var initchar1 = '@'
|
||||||
var .byte initchar2 = '@'
|
var byte initchar2 = '@'
|
||||||
var .word uninitword
|
var word uninitword
|
||||||
var .word initword1 = $1234
|
var word initword1 = $1234
|
||||||
var .word initword1b = true
|
var word initword1b = true
|
||||||
var .word initword2 = false
|
var word initword2 = false
|
||||||
var .word initword3 = 9876.554321
|
var word initword3 = 9876.554321
|
||||||
var .word initword5 = 20
|
var word initword5 = 20
|
||||||
var .float uninitfloat
|
var float uninitfloat
|
||||||
var .float initfloat1 = 0
|
var float initfloat1 = 0
|
||||||
var .float initfloat1b = true
|
var float initfloat1b = true
|
||||||
var .float initfloat2 = -1.234e-14
|
var float initfloat2 = -1.234e-14
|
||||||
var .float initfloat3 = 9.87e+14
|
var float initfloat3 = 9.87e+14
|
||||||
var .float initfloat4 = 1.70141183e+38
|
var float initfloat4 = 1.70141183e+38
|
||||||
var .float initfloat5 = -1.70141183e+38
|
var float initfloat5 = -1.70141183e+38
|
||||||
var .float initfloat6 = 1.234
|
var float initfloat6 = 1.234
|
||||||
|
|
||||||
var .wordarray( 256 ) uninit_wordarray
|
var wordarray( 256 ) uninit_wordarray
|
||||||
var .wordarray(10) init_wordarray = $1234
|
var wordarray(10) init_wordarray = $1234
|
||||||
var .wordarray(10) init_wordarrayb = true
|
var wordarray(10) init_wordarrayb = true
|
||||||
var .array( 256) uninit_bytearray
|
var array( 256) uninit_bytearray
|
||||||
var .array(10 ) init_bytearray =$12
|
var array(10 ) init_bytearray =$12
|
||||||
var .array(10 ) init_bytearrayb =true
|
var array(10 ) init_bytearrayb =true
|
||||||
var .array(10 ) init_bytearrayc ='@'
|
var array(10 ) init_bytearrayc ='@'
|
||||||
|
|
||||||
var .str text = "hello-null"
|
var str text = "hello-null"
|
||||||
var .strp ptext = 'hello-pascal'
|
var strp ptext = 'hello-pascal'
|
||||||
var .strs stext = 'screencodes-null'
|
var strs stext = 'screencodes-null'
|
||||||
var .strps pstext = "screencodes-pascal"
|
var strps pstext = "screencodes-pascal"
|
||||||
|
|
||||||
var .matrix( 2, 128 ) uninitmatrix
|
var matrix( 2, 128 ) uninitmatrix
|
||||||
var .matrix(10, 20) initmatrix1 = $12
|
var matrix(10, 20) initmatrix1 = $12
|
||||||
var .matrix(10, 20) initmatrix1b = true
|
var matrix(10, 20) initmatrix1b = true
|
||||||
var .matrix(10, 20) initmatrix1c = '@'
|
var matrix(10, 20) initmatrix1c = '@'
|
||||||
var .matrix(10, 20) initmatrix1d = 123.456
|
var matrix(10, 20) initmatrix1d = 123.456
|
||||||
|
|
||||||
; memory-mapped variables
|
; memory-mapped variables
|
||||||
memory membyte1 = $cf01
|
memory membyte1 = $cf01
|
||||||
memory .byte membyte2 = $c222
|
memory byte membyte2 = $c222
|
||||||
memory .word memword1 = $cf03
|
memory word memword1 = $cf03
|
||||||
memory .float memfloat = $cf04
|
memory float memfloat = $cf04
|
||||||
memory .array(10 ) membytes = $cf05
|
memory array(10 ) membytes = $cf05
|
||||||
memory .wordarray( 10) memwords = $cf06
|
memory wordarray( 10) memwords = $cf06
|
||||||
memory .matrix( 10, 20 ) memmatrix = $cf07
|
memory matrix( 10, 20 ) memmatrix = $cf07
|
||||||
|
|
||||||
; constants (= names for constant values, can never occur as lvalue)
|
; constants (= names for constant values, can never occur as lvalue)
|
||||||
const cbyte1 = 1
|
const cbyte1 = 1
|
||||||
const cbyte1b = false
|
const cbyte1b = false
|
||||||
const .byte cbyte2 = 1
|
const byte cbyte2 = 1
|
||||||
const .byte cbyte3 = '@'
|
const byte cbyte3 = '@'
|
||||||
const .byte cbyte4 = true
|
const byte cbyte4 = true
|
||||||
const .word cword1 = false
|
const word cword1 = false
|
||||||
const .word cword2 = $1234
|
const word cword2 = $1234
|
||||||
const .word cword5 = 9876.5432
|
const word cword5 = 9876.5432
|
||||||
const cfloat1 = 1.2345
|
const cfloat1 = 1.2345
|
||||||
const .float cfloat2 = 2.3456
|
const float cfloat2 = 2.3456
|
||||||
const .float cfloat2b = cfloat2*3.44
|
const float cfloat2b = cfloat2*3.44
|
||||||
const .float cfloat3 = true
|
const float cfloat3 = true
|
||||||
const .str ctext3 = "constant-text"
|
const str ctext3 = "constant-text"
|
||||||
const .strp ctext4 = "constant-ptext"
|
const strp ctext4 = "constant-ptext"
|
||||||
const .strs ctext5 = "constant-stext"
|
const strs ctext5 = "constant-stext"
|
||||||
const .strps ctext6 = "constant-pstext"
|
const strps ctext6 = "constant-pstext"
|
||||||
|
|
||||||
; taking the address of various things:
|
; taking the address of various things:
|
||||||
var .word vmemaddr1 = &membyte1
|
var word vmemaddr1 = &membyte1
|
||||||
var .word vmemaddr2 = &memword1
|
var word vmemaddr2 = &memword1
|
||||||
var .word vmemaddr3 = &memfloat
|
var word vmemaddr3 = &memfloat
|
||||||
var .word vmemaddr4 = &membytes
|
var word vmemaddr4 = &membytes
|
||||||
var .word vmemaddr5 = &memwords
|
var word vmemaddr5 = &memwords
|
||||||
var .word vmemaddr6 = &memmatrix
|
var word vmemaddr6 = &memmatrix
|
||||||
var .word vmemaddr8 = 100*sin(cbyte1)
|
var word vmemaddr8 = 100*sin(cbyte1)
|
||||||
var .word vmemaddr9 = cword2+$5432
|
var word vmemaddr9 = cword2+$5432
|
||||||
var .word vmemaddr10 = cfloat2b
|
var word vmemaddr10 = cfloat2b
|
||||||
|
|
||||||
; taking the address of things from the ZP will work even when it is a var
|
; 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.
|
; 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
|
var initbytea0 = &ZP.zpmem1
|
||||||
|
|
||||||
|
|
||||||
; (constant) expressions
|
; (constant) expressions
|
||||||
var .word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
|
var word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
|
||||||
var .byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
|
var byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
|
||||||
|
|
||||||
|
|
||||||
sin:
|
sin:
|
||||||
|
@ -6,15 +6,15 @@
|
|||||||
|
|
||||||
~ main_testing {
|
~ main_testing {
|
||||||
start:
|
start:
|
||||||
var .float myfloat1 = 1234.56789
|
var float myfloat1 = 1234.56789
|
||||||
var .float myfloat2 = 9876.54321
|
var float myfloat2 = 9876.54321
|
||||||
var .float myfloatneg1 = -555.666
|
var float myfloatneg1 = -555.666
|
||||||
var .float myfloat_large1 = 987654.1111
|
var float myfloat_large1 = 987654.1111
|
||||||
var .float myfloat_large2 = -123456.2222
|
var float myfloat_large2 = -123456.2222
|
||||||
var .str myfloatstr = "1234.998877"
|
var str myfloatstr = "1234.998877"
|
||||||
var .float myfloatzero = 0
|
var float myfloatzero = 0
|
||||||
var .float myfloatsmall1 = 1.234
|
var float myfloatsmall1 = 1.234
|
||||||
var .float myfloatsmall2 = 2.6677
|
var float myfloatsmall2 = 2.6677
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -22,21 +22,25 @@ start:
|
|||||||
|
|
||||||
~ main {
|
~ main {
|
||||||
|
|
||||||
var .float flt_pi = 3.141592653589793
|
byte bbvar
|
||||||
var .float flt_minus32768 = -32768
|
|
||||||
var .float flt_1 = 1
|
float@
|
||||||
var .float flt_half_sqr2 = 0.7071067811865476
|
|
||||||
var .float flt_sqr2 = 1.4142135623730951
|
var float flt_pi = 3.141592653589793
|
||||||
var .float flt_minus_half = -.5
|
var float flt_minus32768 = -32768
|
||||||
var .float flt_log_2 = 0.6931471805599453
|
var float flt_1 = 1
|
||||||
var .float flt_10 = 10
|
var float flt_half_sqr2 = 0.7071067811865476
|
||||||
var .float flt_1e9 = 1000000000
|
var float flt_sqr2 = 1.4142135623730951
|
||||||
var .float flt_half = .5
|
var float flt_minus_half = -.5
|
||||||
var .float flt_one_over_log_2 = 1.4426950408889634
|
var float flt_log_2 = 0.6931471805599453
|
||||||
var .float flt_half_pi = 1.5707963267948966
|
var float flt_10 = 10
|
||||||
var .float flt_double_pi = 6.283185307179586
|
var float flt_1e9 = 1000000000
|
||||||
var .float flt_point25 = .25
|
var float flt_half = .5
|
||||||
var .float my_float
|
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 .word some_address = $ccdd
|
||||||
memory .byte some_addressb = $ccee
|
memory .byte some_addressb = $ccee
|
||||||
var .byte bytevar = $cc
|
var .byte bytevar = $cc
|
||||||
|
Loading…
Reference in New Issue
Block a user