This commit is contained in:
Irmen de Jong 2018-08-06 03:35:43 +02:00
parent bc8d56cbd7
commit 0e785fcfb3
24 changed files with 1443 additions and 847 deletions

3
.gitignore vendored
View File

@ -17,3 +17,6 @@ __pycache__/
parser.out
parsetab.py
!/il65/lib/*
.pytest_cache/
docs/build

20
docs/Makefile Normal file
View 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
View 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

View File

@ -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)

View 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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

175
docs/source/conf.py Normal file
View 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

View File

@ -0,0 +1,2 @@
[restructuredtext parser]
smart_quotes=true

68
docs/source/index.rst Normal file
View 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`

View 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``.

View 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.

View 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.

View 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
View 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)

View File

@ -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.

View File

@ -9,23 +9,27 @@
~ c64 {
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory .byte TIME_HI = $a0 ; software jiffy clock, hi byte
memory .byte TIME_MID = $a1 ; .. mid byte
memory .byte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
memory .byte STKEY = $91 ; various keyboard statuses (updated by IRQ)
memory .byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
memory byte TIME_HI = $a0 ; software jiffy clock, hi byte
memory byte TIME_MID = $a1 ; .. mid byte
memory byte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
memory byte STKEY = $91 ; various keyboard statuses (updated by IRQ)
memory byte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
memory .byte COLOR = $0286 ; cursor color
memory .byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
memory .word CINV = $0314 ; IRQ vector
memory .matrix(40, 25) Screen = $0400 ; default character screen matrix
memory .matrix(40, 25) Colors = $d800 ; character screen colors
memory byte COLOR = $0286 ; cursor color
memory byte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
memory word CINV = $0314 ; IRQ vector
memory word NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
memory word RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
memory word IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
memory matrix(40, 25) Screen = $0400 ; default character screen matrix
memory matrix(40, 25) Colors = $d800 ; character screen colors
; ---- VIC-II registers ----
@ -88,20 +92,20 @@
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM
memory .float FL_PIVAL = $aea8 ; 3.1415926...
memory .float FL_N32768 = $b1a5 ; -32768
memory .float FL_FONE = $b9bc ; 1
memory .float FL_SQRHLF = $b9d6 ; SQR(2) / 2
memory .float FL_SQRTWO = $b9db ; SQR(2)
memory .float FL_NEGHLF = $b9e0 ; -.5
memory .float FL_LOG2 = $b9e5 ; LOG(2)
memory .float FL_TENC = $baf9 ; 10
memory .float FL_NZMIL = $bdbd ; 1e9 (1 billion)
memory .float FL_FHALF = $bf11 ; .5
memory .float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
memory .float FL_PIHALF = $e2e0 ; PI / 2
memory .float FL_TWOPI = $e2e5 ; 2 * PI
memory .float FL_FR4 = $e2ea ; .25
memory float FL_PIVAL = $aea8 ; 3.1415926...
memory float FL_N32768 = $b1a5 ; -32768
memory float FL_FONE = $b9bc ; 1
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2
memory float FL_SQRTWO = $b9db ; SQR(2)
memory float FL_NEGHLF = $b9e0 ; -.5
memory float FL_LOG2 = $b9e5 ; LOG(2)
memory float FL_TENC = $baf9 ; 10
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion)
memory float FL_FHALF = $bf11 ; .5
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
memory float FL_PIHALF = $e2e0 ; PI / 2
memory float FL_TWOPI = $e2e5 ; 2 * PI
memory float FL_FR4 = $e2ea ; .25
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
@ -116,18 +120,26 @@ sub MOVFA () -> (A?, X?) = $bbfc ; copy fac2 to fac1
sub MOVAF () -> (A?, X?) = $bc0c ; copy fac1 to fac2 (rounded)
sub MOVEF () -> (A?, X?) = $bc0f ; copy fac1 to fac2
sub FTOMEMXY (mflpt: XY) -> (A?, Y?) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
sub FTOSWORDYA () -> (Y, A, X?) = $b1aa ; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; use c64flt.FTOSWRDAY to get A/Y output (lo/hi switched to normal order)
sub GETADR () -> (Y, A, X?) = $b7f7 ; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY)
; (result also in $14/15) use c64flt.GETADRAY to get A/Y output (lo/hi switched to normal order)
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal order)
sub FTOSWORDYA () -> (Y, A, X?) = $b1aa
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal order)
sub GETADR () -> (Y, A, X?) = $b7f7
sub QINT () -> (?) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
sub AYINT () -> (?) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
sub GIVAYF (lo: Y, hi: A) -> (?) = $b391 ; signed word in Y/A -> float in fac1
; use c64flt.GIVAYFAY to use A/Y input (lo/hi switched to normal order)
; signed word in Y/A -> float in fac1
; (use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also c64flt.GIVUAYF - unsigned word in A/Y (lo/hi) to fac1
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
sub GIVAYF (lo: Y, hi: A) -> (?) = $b391
sub FREADUY (ubyte: Y) -> (?) = $b3a2 ; 8 bit unsigned Y -> float in fac1
sub FREADSA (sbyte: A) -> (?) = $bc3c ; 8 bit signed A -> float in fac1
sub FREADSTR (len: A) -> (?) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
@ -223,10 +235,6 @@ sub IOBASE () -> (X, Y) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----
memory .word NMI_VEC = $FFFA ; nmi vector, set by the kernal if banked in
memory .word RESET_VEC = $FFFC ; reset vector, set by the kernal if banked in
memory .word IRQ_VEC = $FFFE ; interrupt vector, set by the kernal if banked in
; ----- utility functions ----
@ -735,7 +743,7 @@ hex_digits .str "0123456789abcdef" ; can probably be reused for other stuff as w
}
var .str word2hex_output = "1234" ; 0-terminated, to make printing easier
var str word2hex_output = "1234" ; 0-terminated, to make printing easier
sub word2hex (word: XY) -> (?) {
; ---- convert 16 bit word in X/Y into 4-character hexadecimal string into memory 'word2hex_output'
%asm {
@ -753,7 +761,7 @@ sub word2hex (word: XY) -> (?) {
}
var .array(3) word2bcd_bcdbuff
var array(3) word2bcd_bcdbuff
sub word2bcd (word: XY) -> (A?, X?) {
; Convert an 16 bit binary value to BCD
;
@ -791,7 +799,7 @@ sub word2bcd (word: XY) -> (A?, X?) {
}
var .array(5) word2decimal_output
var array(5) word2decimal_output
sub word2decimal (word: XY) -> (?) {
; ---- convert 16 bit word in X/Y into decimal string into memory 'word2decimal_output'
%asm {

View File

@ -7,10 +7,10 @@
~ il65_lib {
; note: the following ZP scratch registers must be the same as in c64lib
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
%asm {

View File

@ -12,10 +12,10 @@
~ math {
; note: the following ZP scratch registers must be the same as in c64lib
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory .word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory .word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)

View File

@ -1,4 +1,3 @@
attrs
ply
cbmcodecs >= 0.2.0

View File

@ -4,15 +4,15 @@
var .word var1 = 99
memory .word mem1 = $cff0
var word var1 = 99
memory word mem1 = $cff0
var .byte varb1 = 99
memory .byte memb1 = $cff0
const .word constw = $2355
const .byte constb = $23
const .float constf = 3.4556677
const .str constt = "derp"
var byte varb1 = 99
memory byte memb1 = $cff0
const word constw = $2355
const byte constb = $23
const float constf = 3.4556677
const str constt = "derp"
sub sub1 () -> (X?) = $ffdd
sub sub2 (A) -> (Y?) = $eecc

View File

@ -5,8 +5,8 @@
~ main {
var .word value
memory .word memvalue = $8000
var word value
memory word memvalue = $8000
start:
@ -78,9 +78,9 @@ label4:
~ conditionals {
var bytevar = 22 + 23
var .str name = "?"*80
var str name = "?"*80
var bytevar2 = 23
var .word wordvar = 22345
var word wordvar = 22345
start:

View File

@ -18,11 +18,11 @@
var zpvar2
memory zpmem1 = $f0
const zpconst = 1.234
var .word zpvarw1
var .word zpvarw2
var .float zpvarflt1 = 11.11
var .float zpvarflt2 = 22.22
var .float zpvarflt3
var word zpvarw1
var word zpvarw2
var float zpvarflt1 = 11.11
var float zpvarflt2 = 22.22
var float zpvarflt3
}
@ -43,96 +43,96 @@
~ main {
; variables
var uninitbyte1
var .byte uninitbyte2
var byte uninitbyte2
var initbyte1 = $12
var initbyte1b = true
var .byte initbyte2 = $12
var .byte initbyte2b = false
var .byte initbyte3 = 99.876
var byte initbyte2 = $12
var byte initbyte2b = false
var byte initbyte3 = 99.876
var initchar1 = '@'
var .byte initchar2 = '@'
var .word uninitword
var .word initword1 = $1234
var .word initword1b = true
var .word initword2 = false
var .word initword3 = 9876.554321
var .word initword5 = 20
var .float uninitfloat
var .float initfloat1 = 0
var .float initfloat1b = true
var .float initfloat2 = -1.234e-14
var .float initfloat3 = 9.87e+14
var .float initfloat4 = 1.70141183e+38
var .float initfloat5 = -1.70141183e+38
var .float initfloat6 = 1.234
var byte initchar2 = '@'
var word uninitword
var word initword1 = $1234
var word initword1b = true
var word initword2 = false
var word initword3 = 9876.554321
var word initword5 = 20
var float uninitfloat
var float initfloat1 = 0
var float initfloat1b = true
var float initfloat2 = -1.234e-14
var float initfloat3 = 9.87e+14
var float initfloat4 = 1.70141183e+38
var float initfloat5 = -1.70141183e+38
var float initfloat6 = 1.234
var .wordarray( 256 ) uninit_wordarray
var .wordarray(10) init_wordarray = $1234
var .wordarray(10) init_wordarrayb = true
var .array( 256) uninit_bytearray
var .array(10 ) init_bytearray =$12
var .array(10 ) init_bytearrayb =true
var .array(10 ) init_bytearrayc ='@'
var wordarray( 256 ) uninit_wordarray
var wordarray(10) init_wordarray = $1234
var wordarray(10) init_wordarrayb = true
var array( 256) uninit_bytearray
var array(10 ) init_bytearray =$12
var array(10 ) init_bytearrayb =true
var array(10 ) init_bytearrayc ='@'
var .str text = "hello-null"
var .strp ptext = 'hello-pascal'
var .strs stext = 'screencodes-null'
var .strps pstext = "screencodes-pascal"
var str text = "hello-null"
var strp ptext = 'hello-pascal'
var strs stext = 'screencodes-null'
var strps pstext = "screencodes-pascal"
var .matrix( 2, 128 ) uninitmatrix
var .matrix(10, 20) initmatrix1 = $12
var .matrix(10, 20) initmatrix1b = true
var .matrix(10, 20) initmatrix1c = '@'
var .matrix(10, 20) initmatrix1d = 123.456
var matrix( 2, 128 ) uninitmatrix
var matrix(10, 20) initmatrix1 = $12
var matrix(10, 20) initmatrix1b = true
var matrix(10, 20) initmatrix1c = '@'
var matrix(10, 20) initmatrix1d = 123.456
; memory-mapped variables
memory membyte1 = $cf01
memory .byte membyte2 = $c222
memory .word memword1 = $cf03
memory .float memfloat = $cf04
memory .array(10 ) membytes = $cf05
memory .wordarray( 10) memwords = $cf06
memory .matrix( 10, 20 ) memmatrix = $cf07
memory byte membyte2 = $c222
memory word memword1 = $cf03
memory float memfloat = $cf04
memory array(10 ) membytes = $cf05
memory wordarray( 10) memwords = $cf06
memory matrix( 10, 20 ) memmatrix = $cf07
; constants (= names for constant values, can never occur as lvalue)
const cbyte1 = 1
const cbyte1b = false
const .byte cbyte2 = 1
const .byte cbyte3 = '@'
const .byte cbyte4 = true
const .word cword1 = false
const .word cword2 = $1234
const .word cword5 = 9876.5432
const byte cbyte2 = 1
const byte cbyte3 = '@'
const byte cbyte4 = true
const word cword1 = false
const word cword2 = $1234
const word cword5 = 9876.5432
const cfloat1 = 1.2345
const .float cfloat2 = 2.3456
const .float cfloat2b = cfloat2*3.44
const .float cfloat3 = true
const .str ctext3 = "constant-text"
const .strp ctext4 = "constant-ptext"
const .strs ctext5 = "constant-stext"
const .strps ctext6 = "constant-pstext"
const float cfloat2 = 2.3456
const float cfloat2b = cfloat2*3.44
const float cfloat3 = true
const str ctext3 = "constant-text"
const strp ctext4 = "constant-ptext"
const strs ctext5 = "constant-stext"
const strps ctext6 = "constant-pstext"
; taking the address of various things:
var .word vmemaddr1 = &membyte1
var .word vmemaddr2 = &memword1
var .word vmemaddr3 = &memfloat
var .word vmemaddr4 = &membytes
var .word vmemaddr5 = &memwords
var .word vmemaddr6 = &memmatrix
var .word vmemaddr8 = 100*sin(cbyte1)
var .word vmemaddr9 = cword2+$5432
var .word vmemaddr10 = cfloat2b
var word vmemaddr1 = &membyte1
var word vmemaddr2 = &memword1
var word vmemaddr3 = &memfloat
var word vmemaddr4 = &membytes
var word vmemaddr5 = &memwords
var word vmemaddr6 = &memmatrix
var word vmemaddr8 = 100*sin(cbyte1)
var word vmemaddr9 = cword2+$5432
var word vmemaddr10 = cfloat2b
; taking the address of things from the ZP will work even when it is a var
; because zp-vars get assigned a specific address (from a pool). Also, it's a byte.
var .word initword0a = &ZP.zpmem1
var word initword0a = &ZP.zpmem1
var initbytea0 = &ZP.zpmem1
; (constant) expressions
var .word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
var .byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
var word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
var byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
sin:

View File

@ -6,15 +6,15 @@
~ main_testing {
start:
var .float myfloat1 = 1234.56789
var .float myfloat2 = 9876.54321
var .float myfloatneg1 = -555.666
var .float myfloat_large1 = 987654.1111
var .float myfloat_large2 = -123456.2222
var .str myfloatstr = "1234.998877"
var .float myfloatzero = 0
var .float myfloatsmall1 = 1.234
var .float myfloatsmall2 = 2.6677
var float myfloat1 = 1234.56789
var float myfloat2 = 9876.54321
var float myfloatneg1 = -555.666
var float myfloat_large1 = 987654.1111
var float myfloat_large2 = -123456.2222
var str myfloatstr = "1234.998877"
var float myfloatzero = 0
var float myfloatsmall1 = 1.234
var float myfloatsmall2 = 2.6677
return
}
@ -22,21 +22,25 @@ start:
~ main {
var .float flt_pi = 3.141592653589793
var .float flt_minus32768 = -32768
var .float flt_1 = 1
var .float flt_half_sqr2 = 0.7071067811865476
var .float flt_sqr2 = 1.4142135623730951
var .float flt_minus_half = -.5
var .float flt_log_2 = 0.6931471805599453
var .float flt_10 = 10
var .float flt_1e9 = 1000000000
var .float flt_half = .5
var .float flt_one_over_log_2 = 1.4426950408889634
var .float flt_half_pi = 1.5707963267948966
var .float flt_double_pi = 6.283185307179586
var .float flt_point25 = .25
var .float my_float
byte bbvar
float@
var float flt_pi = 3.141592653589793
var float flt_minus32768 = -32768
var float flt_1 = 1
var float flt_half_sqr2 = 0.7071067811865476
var float flt_sqr2 = 1.4142135623730951
var float flt_minus_half = -.5
var float flt_log_2 = 0.6931471805599453
var float flt_10 = 10
var float flt_1e9 = 1000000000
var float flt_half = .5
var float flt_one_over_log_2 = 1.4426950408889634
var float flt_half_pi = 1.5707963267948966
var float flt_double_pi = 6.283185307179586
var float flt_point25 = .25
var float my_float
memory .word some_address = $ccdd
memory .byte some_addressb = $ccee
var .byte bytevar = $cc