mirror of
https://github.com/irmen/prog8.git
synced 2025-04-07 16:41:46 +00:00
rethinking some aspects and rewriting text a bit
This commit is contained in:
parent
1ea7c015c8
commit
bc8d56cbd7
3
.gitignore
vendored
3
.gitignore
vendored
@ -7,13 +7,12 @@
|
||||
/build/
|
||||
/dist/
|
||||
/output/
|
||||
.cache/
|
||||
.*cache/
|
||||
.eggs/
|
||||
*.directory
|
||||
*.prg
|
||||
*.asm
|
||||
*.labels.txt
|
||||
.mypy_cache/
|
||||
__pycache__/
|
||||
parser.out
|
||||
parsetab.py
|
||||
|
2
LICENSE
2
LICENSE
@ -127,7 +127,7 @@ Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(kernal, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
|
@ -21,13 +21,12 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
- automatic type conversions
|
||||
- floating point operations
|
||||
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
|
||||
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
|
||||
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
- conditional gotos
|
||||
- various code optimizations (code structure, logical and numerical expressions, ...)
|
||||
- @todo: loops
|
||||
- @todo: memory block operations
|
||||
|
||||
|
||||
It still allows for low level programming however and inline assembly blocks
|
||||
to write performance critical pieces of code, but otherwise compiles fairly straightforwardly
|
||||
|
@ -1,133 +1,171 @@
|
||||
IL65 / 'Sick' - Experimental Programming Language for 8-bit 6502/6510 microprocessors
|
||||
=====================================================================================
|
||||
What is a Program?
|
||||
------------------
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
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.
|
||||
|
||||
*Software license: GNU GPL 3.0, see file LICENSE*
|
||||
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.
|
||||
|
||||
|
||||
This is an experimental programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's
|
||||
as used in many home computers from that era. IL65 is a medium to low level programming language,
|
||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
|
||||
#### Things global to the program
|
||||
|
||||
- reduction of source code length
|
||||
- easier program understanding (because it's higher level, and more terse)
|
||||
- option to automatically run the compiled program in the Vice emulator
|
||||
- modularity, symbol scoping, subroutines
|
||||
- subroutines have enforced input- and output parameter definitions
|
||||
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
|
||||
- automatic variable allocations, automatic string variables and string sharing
|
||||
- constant folding in expressions (compile-time evaluation)
|
||||
- automatic type conversions
|
||||
- floating point operations
|
||||
- optional automatic preserving and restoring CPU registers state, when calling routines that otherwise would clobber these
|
||||
- abstracting away low level aspects such as zero page handling, program startup, explicit memory addresses
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
- conditional gotos
|
||||
- various code optimizations (code structure, logical and numerical expressions, ...)
|
||||
- @todo: loops
|
||||
- @todo: memory block operations
|
||||
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
|
||||
|
||||
|
||||
It still allows for low level programming however and inline assembly blocks
|
||||
to write performance critical pieces of code, but otherwise compiles fairly straightforwardly
|
||||
into 6502 assembly code. This resulting code is assembled into a binary program by using
|
||||
an external macro assembler, [64tass](https://sourceforge.net/projects/tass64/).
|
||||
It can be compiled pretty easily for various platforms (Linux, Mac OS, Windows) or just ask me
|
||||
to provide a small precompiled executable if you need that.
|
||||
You need [Python 3.5](https://www.python.org/downloads/) or newer to run IL65 itself.
|
||||
##### ``%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)
|
||||
|
||||
IL65 is mainly targeted at the Commodore-64 machine, but should be mostly system independent.
|
||||
The default is ``prg``.
|
||||
|
||||
|
||||
Memory Model
|
||||
------------
|
||||
##### ``%address`` : specify start address of the code
|
||||
|
||||
Most of the 64 kilobyte address space can be accessed by your program.
|
||||
|
||||
| type | memory area | note |
|
||||
|-----------------|-------------------------|-----------------------------------------------------------------|
|
||||
| Zero page | ``$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 |
|
||||
- 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.
|
||||
|
||||
|
||||
A few memory addresses are reserved and cannot be used freely by your own code,
|
||||
because they have a special hardware function, or are reserved for internal use by the compiler:
|
||||
##### ``%launcher`` : specify launcher type
|
||||
|
||||
| reserved | address |
|
||||
|----------------|------------|
|
||||
| data direction | ``$00`` |
|
||||
| bank select | ``$01`` |
|
||||
| scratch var #1 | ``$02`` |
|
||||
| scratch var #2 | ``$03`` |
|
||||
| NMI vector | ``$fffa`` |
|
||||
| RESET vector | ``$fffc`` |
|
||||
| IRQ vector | ``$fffe`` |
|
||||
|
||||
A particular 6502/6510 machine such as the Commodore-64 will have many other special addresses due to:
|
||||
|
||||
- ROMs installed in the machine (BASIC, kernel and character generator roms)
|
||||
- memory-mapped I/O registers (for the video and sound chip for example)
|
||||
- RAM areas used for screen graphics and sprite data.
|
||||
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
|
||||
|
||||
|
||||
### Usable Hardware Registers
|
||||
##### ``%zp`` : select ZeroPage behavior
|
||||
|
||||
The following 6502 hardware registers are directly accessible in your code (and are reserved symbols):
|
||||
|
||||
- ``A``, ``X``, ``Y``
|
||||
- ``AX``, ``AY``, ``XY`` (surrogate registers: 16-bit combined register pairs in LSB byte order lo/hi)
|
||||
- ``SC`` (status register's Carry flag)
|
||||
- ``SI`` (status register's Interrupt Disable flag)
|
||||
- ``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.
|
||||
|
||||
|
||||
### Zero Page ("ZP")
|
||||
##### Program Start and Entry Point
|
||||
|
||||
The zero page locations ``$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 your program but there are a few limitations:
|
||||
- several locations (``$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. Your program must take over the entire
|
||||
system to be able to safely use all zero page locations.
|
||||
- it's often more convenient to let IL65 allocate the particular locations for you and just
|
||||
use symbolic names in your code.
|
||||
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).
|
||||
|
||||
For the Commodore-64 here is a list of free-to-use zero page locations even when its BASIC and KERNAL are active:
|
||||
~ main {
|
||||
sub start () -> () {
|
||||
; program entrypoint code here
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
|
||||
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
|
||||
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.
|
||||
|
||||
The four reserved locations mentioned above are subtracted from this set, leaving you with
|
||||
five 1-byte and two 2-byte usable zero page registers.
|
||||
IL65 knows about all this: it will use the above zero page locations to place its ZP variables in,
|
||||
until they're all used up. You can instruct it to treat your program as taking over the entire
|
||||
machine, in which case (almost) all of the zero page locations are suddenly available for variables.
|
||||
IL65 can generate a special routine that saves and restores the zero page to let your program run
|
||||
and return safely back to the system afterwards - you don't have to take care of that yourself.
|
||||
|
||||
**IRQ and the Zero page:**
|
||||
|
||||
The normal IRQ routine in the C-64's kernal will read and write several locations in the zero page:
|
||||
|
||||
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
|
||||
|
||||
These locations will not be used by the compiler for zero page variables, so your 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 zp has been taken over. So the normal IRQ vector is still
|
||||
running when your program is entered, even when you use ``%zp clobber``.
|
||||
Blocks and subroutines are explained below.
|
||||
|
||||
|
||||
@todo: some global way (in ZP block) to promote certian other blocks/variables from that block or even
|
||||
subroutine to the zeropage. Don't do this in the block itself because it's a global optimization
|
||||
and if blocks require it themselves you can't combine various modules anymore once ZP runs out.
|
||||
|
||||
#### 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.
|
||||
|
||||
|
||||
Data Types
|
||||
----------
|
||||
#### 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:
|
||||
|
||||
@ -162,9 +200,17 @@ 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.
|
||||
|
||||
Indirect addressing and address-of
|
||||
----------------------------------
|
||||
|
||||
@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,
|
||||
@ -183,68 +229,33 @@ For an indirect goto call, the 6502 CPU has a special instruction
|
||||
using a couple of instructions.
|
||||
|
||||
|
||||
Program Structure
|
||||
-----------------
|
||||
### Conditional Execution
|
||||
|
||||
In IL65 every line in the source file can only contain *one* statement or definitons.
|
||||
Compilation is done on *one* main source code file, but other files can be imported.
|
||||
A source file can start with global *directives* (starting with ``%``) and continues
|
||||
with imports and block definitions.
|
||||
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
|
||||
|
||||
|
||||
### Comments
|
||||
|
||||
Everything after a semicolon '``;``' is a comment and is ignored.
|
||||
If the comment is the only thing on the line, it is copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code.
|
||||
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.
|
||||
|
||||
|
||||
### Output Mode
|
||||
### Including other files or raw assembly code literally
|
||||
|
||||
The default format of the generated program is a "raw" binary where code starts at ``$c000``.
|
||||
You can generate other types of programs as well, by telling IL65 via an output mode directive
|
||||
at the beginning of your program:
|
||||
|
||||
| mode directive | meaning |
|
||||
|-----------------------|------------------------------------------------------------------------------------|
|
||||
| ``%output raw`` | no load address bytes |
|
||||
| ``%output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
|
||||
| ``%output prg,basic`` | as 'prg', but include a BASIC start program with SYS call, default code start is immediately after the BASIC program at ``$081d``, or after that. |
|
||||
| | |
|
||||
| ``%address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,basic`` because BASIC programs always have to start at ``$0801``. |
|
||||
|
||||
|
||||
### ZeroPage Options
|
||||
|
||||
You can tell the compiler how to treat the *zero page*. Normally it is considered a 'no-go' area
|
||||
except for the frew free locations mentioned under "Memory Model".
|
||||
However you can specify some options globally in your program by using the zp directive
|
||||
to change this behavior:
|
||||
|
||||
- ``%zp clobber``
|
||||
Use the whole zeropage for variables. It is not possible to exit your program
|
||||
correctly back to BASIC, other than resetting the machine.
|
||||
It does make your program smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
- ``%zp clobber, restore``
|
||||
Use the whole zeropage, but make a backup copy of the original values at program start.
|
||||
When your program exits, the original ZP is restored (except for the software jiffy clock
|
||||
in ``$a0 - $a2``) and you drop back to the BASIC prompt.
|
||||
Not that the default IRQ routine is *still enabled* when your program is entered!
|
||||
See the paragraph on the zero page for more info about this.
|
||||
|
||||
If you use ``%zp clobber``, you can no longer use most BASIC or KERNAL routines,
|
||||
because these depend on most of the locations in the ZP. This includes the floating-point
|
||||
logic and several utility routines that do I/O, such as ``print_string``.
|
||||
|
||||
|
||||
### Importing, Including and Binary-Including other files or raw assembly code
|
||||
|
||||
- ``%import "filename[.ill]"``
|
||||
Must be used *after* any global option directives, and outside of a block,
|
||||
but otherwise can be placed anywhere in the program.
|
||||
Reads and compiles the named IL65 file and stores the resulting definitions
|
||||
as if they were a part of your current program.
|
||||
- ``%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.
|
||||
@ -259,47 +270,6 @@ logic and several utility routines that do I/O, such as ``print_string``.
|
||||
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
|
||||
|
||||
|
||||
### Program Start and Entry Point
|
||||
|
||||
Every program has to have one entry point where code execution begins.
|
||||
The compiler looks for the ``start`` label in the ``main`` block for this.
|
||||
For proper program termination, this block has to end with a ``return`` statement (or a ``goto`` call).
|
||||
Blocks and other details are described below.
|
||||
|
||||
The initial values of your variables will be restored automatically when the program is (re)started,
|
||||
*except for string variables*. It is assumed they are unchanged by your program.
|
||||
If you do modify them in-place, you should take care yourself that they work as
|
||||
expected when the program is restarted.
|
||||
|
||||
|
||||
### Blocks and Scopes
|
||||
|
||||
~ blockname [address] {
|
||||
[directives...]
|
||||
[statements...]
|
||||
}
|
||||
|
||||
Blocks form the separate pieces of code and data of your program. They are combined and
|
||||
arranged to a single output program. No code or data can occur outside a block.
|
||||
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. You can refer to the symbols in a particular block
|
||||
by using a *dotted name*: ``blockname.symbolname``.
|
||||
Note that the *file* (or module) that blocks are defined in, is of no consequence to the
|
||||
full scope name; all blocks defined in it are simply added to the set of blocks that make up the
|
||||
entire program.
|
||||
|
||||
Hence block names must be unique in your entire program.
|
||||
Except "ZP": the contents of every block with that name are merged into one.
|
||||
This "ZP" block name is special and refers to the zero page. Its start address is always set to $04,
|
||||
because $00/$01 are used by the hardware and $02/$03 are reserved as general purpose scratch registers.
|
||||
Block address must be >= $0200 (because $00-$fff is the ZP and $100-$200 is the cpu stack)
|
||||
|
||||
When declaring a block in your code, 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.
|
||||
|
||||
|
||||
### Assignments
|
||||
|
||||
@ -402,59 +372,6 @@ as well clobber all three registers. Preserving the original values does result
|
||||
stack manipulation code to be inserted for every call like this, which can be quite slow.
|
||||
|
||||
|
||||
#### Calling Convention
|
||||
|
||||
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 (globally or 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 or even program. See the paragraph on Subroutine Calling
|
||||
for more info.
|
||||
|
||||
|
||||
### Conditional Execution Flow
|
||||
|
||||
Conditional execution flow 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.
|
||||
|
||||
|
||||
Debugging (with Vice)
|
||||
---------------------
|
||||
@ -462,17 +379,25 @@ 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, a NOP instruction is generated and in a special output file the list of breakpoints is written.
|
||||
Instead of this, a NOP instruction is generated and in a special output file the list of breakpoints is written.
|
||||
|
||||
This file is called "yourprogramname.vice-mon-list" and is meant to be used by the Vice C-64 emulator.
|
||||
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 the file: ``x64 -moncommands yourprogramname.vice-mon-list``
|
||||
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
|
||||
@ -625,26 +550,3 @@ to define CHARACTERS (8x8 monochrome or 4x8 multicolor = 8 bytes)
|
||||
and SPRITES (24x21 monochrome or 12x21 multicolor = 63 bytes)
|
||||
--> PLACE in memory on correct address (base+sprite pointer, 64-byte aligned)
|
||||
|
||||
|
||||
### More Datatypes
|
||||
|
||||
@todo pointers/addresses? (as opposed to normal WORDs)
|
||||
@todo signed integers (byte and word)?
|
||||
|
||||
|
||||
### Some support for simple arithmetic
|
||||
|
||||
A *= Y
|
||||
A = X * Y
|
||||
A /= Y
|
||||
A = Y / Y
|
||||
|
||||
|
||||
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``.
|
||||
|
137
docs/system.md
Normal file
137
docs/system.md
Normal file
@ -0,0 +1,137 @@
|
||||
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.
|
@ -103,12 +103,12 @@ class AssemblyGenerator:
|
||||
if zpblock:
|
||||
# if there's a Zeropage block, it always goes first
|
||||
self.cur_block = zpblock # type: ignore
|
||||
out("\n; ---- zero page block: '{:s}' ----".format(zpblock.name))
|
||||
out("\n; ---- ZeroPage block: '{:s}' ----".format(zpblock.name))
|
||||
out("; file: '{:s}' src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
|
||||
out("{:s}\t.proc\n".format(zpblock.label))
|
||||
generate_block_init(out, zpblock)
|
||||
generate_block_vars(out, zpblock, True)
|
||||
# there's no code in the zero page block.
|
||||
# there's no code in the ZeroPage block.
|
||||
out("\v.pend\n")
|
||||
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
|
||||
ctx = Context(out=out, stmt=None, scope=block.scope, floats_enabled=self.floats_enabled)
|
||||
|
@ -1,5 +1,5 @@
|
||||
; IL65 definitions for the Commodore-64
|
||||
; Including memory registers, I/O registers, Basic and Kernel subroutines, utility subroutines.
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines, utility subroutines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
; ;
|
||||
@ -454,7 +454,7 @@ sub float_sub_SW1_from_XY (mflt: XY) -> (?) {
|
||||
; ---- this block contains (character) Screen and text I/O related functions ----
|
||||
|
||||
|
||||
sub clear_screen (char:A, color: Y) -> () {
|
||||
sub clear_screen (char: A, color: Y) -> () {
|
||||
; ---- clear the character screen with the given fill character and character color.
|
||||
; (assumes screen is at $0400, could be altered in the future with self-modifying code)
|
||||
; @todo X = SCREEN ADDR HI BYTE
|
||||
|
@ -1,4 +1,4 @@
|
||||
; backup/restore the zero page
|
||||
; backup/restore the ZeroPage
|
||||
; this is in a separate file so it can be omitted completely if it's not needed.
|
||||
|
||||
_il65_save_zeropage
|
||||
@ -7,10 +7,12 @@ _il65_save_zeropage
|
||||
lda #%00100111
|
||||
sta _il65_zp_backup+1 ; default value for $01
|
||||
ldx #2
|
||||
sei
|
||||
- lda $00,x
|
||||
sta _il65_zp_backup,x
|
||||
inx
|
||||
bne -
|
||||
cli
|
||||
rts
|
||||
|
||||
_il65_restore_zeropage
|
||||
|
@ -775,7 +775,7 @@ class VarDef(AstNode):
|
||||
vartype = attr.ib()
|
||||
datatype = attr.ib()
|
||||
size = attr.ib(type=list, default=None)
|
||||
zp_address = attr.ib(type=int, default=None, init=False) # the address in the zero page if this var is there, will be set later
|
||||
zp_address = attr.ib(type=int, default=None, init=False) # the address in the ZeroPage if this var is there, will be set later
|
||||
|
||||
@property
|
||||
def value(self) -> Expression:
|
||||
|
@ -5,7 +5,12 @@
|
||||
|
||||
|
||||
%output basic ; create a c-64 program with basic SYS call to launch it
|
||||
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
|
||||
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernal rom, frees up more zp
|
||||
|
||||
options
|
||||
|
||||
|
||||
%blocks
|
||||
|
||||
|
||||
~ main
|
||||
@ -24,6 +29,7 @@
|
||||
var .word initword9876 = $9876
|
||||
var .array(10) bytes2 = $44
|
||||
var .wordarray(10) words2 = $bbcc
|
||||
]
|
||||
|
||||
|
||||
start: ;foo
|
||||
|
@ -25,7 +25,7 @@ if len(sys.argv) == 3:
|
||||
if len(sys.argv) not in (2, 3):
|
||||
raise SystemExit("provide 1 or 2 program file names as arguments")
|
||||
|
||||
# zero page and hardware stack of a 6502 cpu are off limits for now
|
||||
# ZeroPage and hardware stack of a 6502 cpu are off limits for now
|
||||
VM.readonly_mem_ranges = [(0x00, 0xff), (0x100, 0x1ff)]
|
||||
vm = VM(mainprogram, timerprogram)
|
||||
vm.enable_charscreen(0x0400, 40, 25)
|
||||
|
Loading…
x
Reference in New Issue
Block a user