This commit is contained in:
Irmen de Jong 2017-12-30 13:34:52 +01:00
parent a5b4849058
commit a7465f480a
13 changed files with 375 additions and 171 deletions

View File

@ -126,14 +126,7 @@ class CodeGenerator:
def initialize_variables(self) -> None:
must_save_zp = self.parsed.clobberzp and self.parsed.restorezp
if must_save_zp:
self.p("; save zp")
self.p("\t\tsei")
self.p("\t\tldx #2")
self.p("-\t\tlda $00,x")
self.p("\t\tsta _il65_zp_backup-2,x")
self.p("\t\tinx")
self.p("\t\tbne -")
self.p("\t\tjsr il65_lib_zp.save_zeropage")
# Only the vars from the ZeroPage need to be initialized here,
# the vars in all other blocks are just defined and pre-filled there.
zpblocks = [b for b in self.parsed.blocks if b.name == "ZP"]
@ -173,17 +166,8 @@ class CodeGenerator:
main_block_label = [b.label for b in self.parsed.blocks if b.name == "main"][0]
if must_save_zp:
self.p("\t\tjsr {:s}.start\t\t; call user code".format(main_block_label))
self.p("; restore zp")
self.p("\t\tcld")
self.p("\t\tphp\n\t\tpha\n\t\ttxa\n\t\tpha\n\t\tsei")
self.p("\t\tldx #2")
self.p("-\t\tlda _il65_zp_backup-2,x")
self.p("\t\tsta $00,x")
self.p("\t\tinx")
self.p("\t\tbne -")
self.p("\t\tcli\n\t\tpla\n\t\ttax\n\t\tpla\n\t\tplp")
self.p("\t\trts")
self.p("_il65_zp_backup\t\t.fill 254, 0")
self.p("\t\tjmp il65_lib_zp.restore_zeropage")
else:
self.p("\t\tjmp {:s}.start\t\t; call user code".format(main_block_label))
@ -532,6 +516,7 @@ class CodeGenerator:
self.p("\t\tpla")
elif what.datatype == DataType.WORD:
if stmt.howmuch == 1:
# mem.word +=/-= 1
if is_incr:
self.p("\t\tinc " + r_str)
self.p("\t\tbne +")
@ -545,7 +530,25 @@ class CodeGenerator:
self.p("+\t\tdec " + r_str)
self.p("\t\tpla")
else:
raise CodeError("cannot yet incr/decr 16 bit memory by more than 1") # @todo 16-bit incr/decr
# mem.word +=/-= 2..255
if is_incr:
self.p("\t\tpha")
self.p("\t\tclc")
self.p("\t\tlda " + r_str)
self.p("\t\tadc #{:d}".format(stmt.howmuch))
self.p("\t\tsta " + r_str)
self.p("\t\tbcc +")
self.p("\t\tinc {:s}+1".format(r_str))
self.p("+\t\tpla")
else:
self.p("\t\tpha")
self.p("\t\tsec")
self.p("\t\tlda " + r_str)
self.p("\t\tsbc #{:d}".format(stmt.howmuch))
self.p("\t\tsta " + r_str)
self.p("\t\tbcs +")
self.p("\t\tdec {:s}+1".format(r_str))
self.p("+\t\tpla")
else:
raise CodeError("cannot in/decrement memory of type " + str(what.datatype))
else:
@ -718,13 +721,23 @@ class CodeGenerator:
self.p(line_after_branch)
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
print("EMIT", stmt.target, stmt.condition, is_goto, target_indirect)
assert is_goto and not stmt.condition.comparison_op
assert stmt.condition.lvalue and not stmt.condition.rvalue
assert stmt.condition.ifstatus in ("true", "not", "zero")
assert not target_indirect
cv = stmt.condition.lvalue.value # type: ignore
if isinstance(cv, ParseResult.RegisterValue):
raise CodeError("indirect registers not yet supported") # @todo indirect reg
branch = "bne" if stmt.condition.ifstatus == "true" else "beq"
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
if cv.register in REGISTER_BYTES:
self.p("\t\tst{:s} *+2\t; self-modify".format(cv.register.lower()))
self.p("\t\tlda $ff")
else:
self.p("\t\tst{:s} (+)+1\t; self-modify".format(cv.register[0].lower()))
self.p("\t\tst{:s} (+)+2\t; self-modify".format(cv.register[1].lower()))
self.p("+\t\tlda $ffff")
self.p("\t\t{:s} {:s}".format(branch, targetstr))
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
elif isinstance(cv, ParseResult.MemMappedValue):
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
elif isinstance(cv, ParseResult.IntegerValue) and cv.constant:
@ -917,27 +930,15 @@ class CodeGenerator:
unclobber_result_registers(preserve_regs, stmt.desugared_output_assignments)
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a()):
generate_param_assignments()
# @todo optimize this with RTS_trick? https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations#Use_Jump_tables_with_RTS_instruction_instead_of_JMP_indirect_instruction
if targetstr in REGISTER_WORDS:
print("warning: {:s}:{:d}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
.format(self.cur_block.sourceref.file, stmt.lineno))
if stmt.preserve_regs:
# cannot use zp scratch. This is very inefficient code!
print("warning: {:s}:{:d}: indirect register pair call, this is very inefficient"
.format(self.cur_block.sourceref.file, stmt.lineno))
self.p("\t\tst{:s} ++".format(targetstr[0].lower()))
self.p("\t\tst{:s} +++".format(targetstr[1].lower()))
self.p("\t\tjsr +")
self.p("\t\tjmp ++++")
self.p("+\t\tjmp (+)")
self.p("+\t\t.byte 0\t; lo")
self.p("+\t\t.byte 0\t; hi")
self.p("+")
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
else:
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
self.p("+\t\tjmp ({:s})".format(Parser.to_hex(Zeropage.SCRATCH_B1)))
self.p("+")
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
else:
self.p("\t\tjsr +")
self.p("\t\tjmp ++")
@ -1720,17 +1721,14 @@ class CodeGenerator:
raise CodeError("can only assign a byte or word to a word", str(rvalue))
elif lv.datatype == DataType.FLOAT:
if rvalue.datatype == DataType.FLOAT:
with self.preserving_registers({'A'}, loads_a_within=True):
self.p("\t\tlda " + r_str)
self.p("\t\tsta " + l_str)
self.p("\t\tlda {:s}+1".format(r_str))
self.p("\t\tsta {:s}+1".format(l_str))
self.p("\t\tlda {:s}+2".format(r_str))
self.p("\t\tsta {:s}+2".format(l_str))
self.p("\t\tlda {:s}+3".format(r_str))
self.p("\t\tsta {:s}+3".format(l_str))
self.p("\t\tlda {:s}+4".format(r_str))
self.p("\t\tsta {:s}+4".format(l_str))
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
self.p("\t\tlda #<" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD")
self.p("\t\tlda #>" + r_str)
self.p("\t\tsta c64.SCRATCH_ZPWORD+1")
self.p("\t\tldx #<" + l_str)
self.p("\t\tldy #>" + l_str)
self.p("\t\tjsr c64_lib.copy_mflt")
elif rvalue.datatype == DataType.BYTE:
with self.preserving_registers({'A', 'X', 'Y'}):
self.p("\t\tldy " + r_str)

View File

@ -35,11 +35,11 @@ def main() -> None:
print("\n" + description)
start = time.perf_counter()
pp = PreprocessingParser(args.sourcefile, )
pp = PreprocessingParser(args.sourcefile, set())
sourcelines, symbols = pp.preprocess()
# symbols.print_table()
p = Parser(args.sourcefile, args.output, sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
p = Parser(args.sourcefile, args.output, set(), sourcelines=sourcelines, ppsymbols=symbols, sub_usage=pp.result.subroutine_usage)
parsed = p.parse()
if parsed:
if args.nooptimize:

View File

@ -472,12 +472,29 @@ class ParseResult:
if not assignment.is_identity():
assignment.lineno = self.lineno
self.desugared_call_arguments.append(assignment)
for register, value in self.outputvars or []:
rvalue = parser.parse_expression(register)
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
# note: we need the identity assignment here or the output register handling generates buggy code
assignment.lineno = self.lineno
self.desugared_output_assignments.append(assignment)
if all(not isinstance(v, ParseResult.RegisterValue) for r, v in self.outputvars or []):
# if none of the output variables are registers, we can simply generate the assignments without issues
for register, value in self.outputvars or []:
rvalue = parser.parse_expression(register)
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
assignment.lineno = self.lineno
self.desugared_output_assignments.append(assignment)
else:
result_reg_mapping = [(register, value.register, value) for register, value in self.outputvars or []
if isinstance(value, ParseResult.RegisterValue)]
if any(r[0] != r[1] for r in result_reg_mapping):
# not all result parameter registers line up with the correct order of registers in the statement,
# reshuffling call results is not supported yet.
raise parser.PError("result registers and/or their ordering is not the same as in the "
"subroutine definition, this isn't supported yet")
else:
# no register alignment issues, just generate the assignments
# note: do not remove the identity assignment here or the output register handling generates buggy code
for register, value in self.outputvars or []:
rvalue = parser.parse_expression(register)
assignment = ParseResult.AssignmentStmt([value], rvalue, self.lineno)
assignment.lineno = self.lineno
self.desugared_output_assignments.append(assignment)
class InlineAsm(_AstNode):
def __init__(self, asmlines: List[str], lineno: int) -> None:
@ -552,8 +569,8 @@ class ParseResult:
class Parser:
def __init__(self, filename: str, outputdir: str, sourcelines: List[Tuple[int, str]] = None, parsing_import: bool = False,
ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None:
def __init__(self, filename: str, outputdir: str, existing_imports: Set[str], parsing_import: bool = False,
sourcelines: List[Tuple[int, str]] = None, ppsymbols: SymbolTable = None, sub_usage: Dict=None) -> None:
self.result = ParseResult(filename)
if sub_usage is not None:
# re-use the (global) subroutine usage tracking
@ -571,6 +588,7 @@ class Parser:
self.root_scope.set_zeropage(self.result.zeropage)
self.ppsymbols = ppsymbols # symboltable from preprocess phase
self.print_block_parsing = True
self.existing_imports = existing_imports
def load_source(self, filename: str) -> List[Tuple[int, str]]:
with open(filename, "rU") as source:
@ -624,6 +642,7 @@ class Parser:
def parse_file(self) -> ParseResult:
print("\nparsing", self.sourceref.file)
self._parse_1()
self._parse_import_file("il65lib") # compiler support library is always imported.
self._parse_2()
return self.result
@ -658,7 +677,7 @@ class Parser:
block = self.parse_block()
if block:
self.result.add_block(block)
elif next_line.startswith("import"):
elif next_line.startswith(("import ", "import\t")):
self.parse_import()
else:
break
@ -861,7 +880,7 @@ class Parser:
def parse_import(self) -> None:
line = self.next_line()
line = line.lstrip()
if not line.startswith("import"):
if not line.startswith(("import ", "import\t")):
raise self.PError("expected import")
try:
_, arg = line.split(maxsplit=1)
@ -872,6 +891,9 @@ class Parser:
filename = arg[1:-1]
if not filename:
raise self.PError("invalid filename")
self._parse_import_file(filename)
def _parse_import_file(self, filename: str) -> None:
filename_at_source_location = os.path.join(os.path.split(self.sourceref.file)[0], filename)
filename_at_libs_location = os.path.join(os.getcwd(), "lib", filename)
candidates = [filename,
@ -882,10 +904,12 @@ class Parser:
filename_at_libs_location+".ill"]
for filename in candidates:
if os.path.isfile(filename):
print("importing", filename)
if not self.check_import_okay(filename):
return
self.print_import_progress("importing", filename)
parser = self.create_import_parser(filename, self.outputdir)
result = parser.parse()
print("\ncontinuing", self.sourceref.file)
self.print_import_progress("\ncontinuing", self.sourceref.file)
if result:
# merge the symbol table of the imported file into our own
try:
@ -898,8 +922,11 @@ class Parser:
raise self.PError("Error while parsing imported file")
raise self.PError("imported file not found")
def print_import_progress(self, message: str, *args: str) -> None:
print(message, *args)
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
return Parser(filename, outputdir, parsing_import=True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
return Parser(filename, outputdir, self.existing_imports, True, ppsymbols=self.ppsymbols, sub_usage=self.result.subroutine_usage)
def parse_block(self) -> Optional[ParseResult.Block]:
# first line contains block header "~ [name] [addr]" followed by a '{'
@ -1276,7 +1303,7 @@ class Parser:
raise self.PError("all call arguments should have a name or be matched on a named parameter")
if isinstance(target, (type(None), ParseResult.Value)):
# special case for the C-64 lib's print function, to be able to use it with a single character argument
if target.name == "c64util.print_string" and len(arguments) == 1 and len(arguments[0][0]) > 1:
if target.name == "c64util.print_string" and len(arguments) == 1 and isinstance(arguments[0], str):
if arguments[0][1].startswith("'") and arguments[0][1].endswith("'"):
target = self.parse_expression("c64.CHROUT")
address = target.address
@ -1662,6 +1689,14 @@ class Parser:
self.print_warning("if_not condition inverted to if")
return result
def check_import_okay(self, filename: str) -> bool:
if filename == self.sourceref.file and not filename.endswith("il65lib.ill"):
raise self.PError("can't import itself")
if filename in self.existing_imports:
return False
self.existing_imports.add(filename)
return True
class Optimizer:
def __init__(self, parseresult: ParseResult) -> None:
@ -1765,8 +1800,7 @@ class Optimizer:
block.symbols.discard_sub(sub.name)
discarded.append(sub.name)
if discarded:
print("{}: discarded unused subroutines from block '{:s}': ".format(block.sourceref, block.name), end="")
print(", ".join(discarded))
print("{}: discarded {:d} unused subroutines from block '{:s}'".format(block.sourceref, len(discarded), block.name))
def _value_sortkey(value: ParseResult.Value) -> int:

View File

@ -6,14 +6,14 @@ Written by Irmen de Jong (irmen@razorvine.net)
License: GNU GPL 3.0, see LICENSE
"""
from typing import List, Tuple
from typing import List, Tuple, Set
from .parse import Parser, ParseResult, SymbolTable, SymbolDefinition
from .symbols import SourceRef
class PreprocessingParser(Parser):
def __init__(self, filename: str, parsing_import: bool=False) -> None:
super().__init__(filename, "", parsing_import=parsing_import)
def __init__(self, filename: str, existing_imports: Set[str], parsing_import: bool=False) -> None:
super().__init__(filename, "", existing_imports=existing_imports, parsing_import=parsing_import)
self.print_block_parsing = False
def preprocess(self) -> Tuple[List[Tuple[int, str]], SymbolTable]:
@ -37,7 +37,7 @@ class PreprocessingParser(Parser):
return lines
def parse_file(self) -> ParseResult:
print("\npreprocessing", self.sourceref.file)
print("preprocessing", self.sourceref.file)
self._parse_1()
return self.result
@ -63,4 +63,7 @@ class PreprocessingParser(Parser):
super().parse_subroutine_def(line)
def create_import_parser(self, filename: str, outputdir: str) -> 'Parser':
return PreprocessingParser(filename, parsing_import=True)
return PreprocessingParser(filename, parsing_import=True, existing_imports=self.existing_imports)
def print_import_progress(self, message: str, *args: str) -> None:
pass

View File

@ -208,6 +208,7 @@ class SubroutineDef(SymbolDefinition):
class Zeropage:
SCRATCH_B1 = 0x02
SCRATCH_B2 = 0x03
SCRATCH_W1 = 0xfd # $fd/$fe
def __init__(self) -> None:
self.unused_bytes = [] # type: List[int]
@ -218,13 +219,13 @@ class Zeropage:
if self._configured:
raise SymbolError("cannot configure the ZP multiple times")
if clobber_zp:
self.unused_bytes = list(range(0x04, 0x80))
self.unused_words = list(range(0x80, 0x100, 2))
self.unused_bytes = list(range(0x04, 0x80)) + [0xfc, 0xff]
self.unused_words = list(range(0x80, 0xfc, 2))
else:
# these are valid for the C-64:
# ($02 and $03 are reserved as scratch addresses for various routines)
self.unused_bytes = [0x06, 0x0a, 0x2a, 0x52, 0x93] # 5 zp variables (8 bits each)
self.unused_words = [0x04, 0xf7, 0xf9, 0xfb, 0xfd] # 5 zp variables (16 bits each)
# these are valid for the C-64 (when no RS232 I/O is performed):
# ($02, $03, $fd-$fe are reserved as scratch addresses for various routines)
self.unused_bytes = [0x04, 0x05, 0x06, 0x2a, 0x52] # 5 zp variables (1 byte each)
self.unused_words = [0xf7, 0xf9, 0xfb] # 3 zp word variables (2 bytes each)
assert self.SCRATCH_B1 not in self.unused_bytes and self.SCRATCH_B1 not in self.unused_words
assert self.SCRATCH_B2 not in self.unused_bytes and self.SCRATCH_B2 not in self.unused_words
self._configured = True
@ -252,7 +253,7 @@ class SymbolTable:
self.parent = parent
self.owning_block = owning_block
self.eval_dict = None
self._zeropage = parent._zeropage if parent else None
self._zeropage = parent._zeropage if parent else None # type: Zeropage
def set_zeropage(self, zp: Zeropage) -> None:
if self._zeropage is None:

View File

@ -10,8 +10,9 @@
output raw
~ c64 {
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory .byte SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory .byte SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
memory .word SCRATCH_ZPWORD = $fd ; scratch word in ZP ($fd/$fe)
memory .byte COLOR = $0286 ; cursor color
memory .word CINV = $0314 ; IRQ vector
@ -335,6 +336,9 @@ sub GETADRAY () -> (AY, X?) {
sub print_string (address: XY) -> (A?, Y?) {
; ---- print null terminated string from X/Y
; note: the IL65 compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
asm {
stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZP2
@ -433,7 +437,7 @@ hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}
; var .array(4) word2hex_output_array @todo support to use array/matrix type by address
var .text word2hex_output = "123" ; 0-terminated, 4 bytes total @todo remove if array works
var .text word2hex_output = "123" ; 0-terminated, 4 bytes total @todo remove once array works
sub word2hex (word: XY) -> (A?, X?, Y?) {
; ---- convert 16 bit word in X/Y into hexadecimal string into memory 'word2hex_output'
asm {
@ -706,3 +710,33 @@ sub input_chars (buffer: AX) -> (A?, Y) {
}
~ c64_lib {
asm {
; ---- copy a 5 byte MFLT floating point variable to another place
; input: X/Y = source address, SCRATCH_ZPWORD = destination address
copy_mflt stx c64.SCRATCH_ZP1
sty c64.SCRATCH_ZPWORD+1
ldy #0
lda (c64.SCRATCH_ZP1),y
sta (c64.SCRATCH_ZPWORD),y
iny
lda (c64.SCRATCH_ZP1),y
sta (c64.SCRATCH_ZPWORD),y
iny
lda (c64.SCRATCH_ZP1),y
sta (c64.SCRATCH_ZPWORD),y
iny
lda (c64.SCRATCH_ZP1),y
sta (c64.SCRATCH_ZPWORD),y
iny
lda (c64.SCRATCH_ZP1),y
sta (c64.SCRATCH_ZPWORD),y
ldy c64.SCRATCH_ZPWORD+1
rts
}
}

88
lib/il65lib.ill Normal file
View File

@ -0,0 +1,88 @@
; IL65 internal library routines
;
; Written by Irmen de Jong (irmen@razorvine.net)
; License: GNU GPL 3.0, see LICENSE
;
; indent format: TABS, size=8
output raw
~ il65_lib_zp {
; note: separate block so the 64tass assembler can remove this when no zp restore is required
asm {
; ---- store the Zeropage in a backup area
save_zeropage
sei
ldx #2
- lda $00,x
sta zp_backup-2,x
inx
bne -
rts
restore_zeropage
php
pha
txa
pha
sei
ldx #2
- lda zp_backup-2,x
sta $00,x
inx
bne -
cli
pla
tax
pla
plp
rts
zp_backup .fill 254, 0
}
}
~ il65_lib {
; note: the following two ZP scratch registers must be the same as in c64lib
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
asm {
; ---- jmp (indirect) routines for register pairs containing the indirect address
jsr_indirect_nozpuse_AX
sta jsr_indirect_tmp
stx jsr_indirect_tmp+1
jmp (jsr_indirect_tmp)
jsr_indirect_nozpuse_AY
sta jsr_indirect_tmp
sty jsr_indirect_tmp+1
jmp (jsr_indirect_tmp)
jsr_indirect_nozpuse_XY
stx jsr_indirect_tmp
sty jsr_indirect_tmp+1
jmp (jsr_indirect_tmp)
jsr_indirect_tmp
.byte 0, 0
jsr_indirect_AX
sta SCRATCH_ZP1
stx SCRATCH_ZP2
jmp (SCRATCH_ZP1)
jsr_indirect_AY
sta SCRATCH_ZP1
sty SCRATCH_ZP2
jmp (SCRATCH_ZP1)
jsr_indirect_XY
stx SCRATCH_ZP1
sty SCRATCH_ZP2
jmp (SCRATCH_ZP1)
}
}

View File

@ -1,6 +1,8 @@
; IL65 integer math library for 6502
; (floating point math is done via the C-64's BASIC ROM routines)
;
; some more interesting routines can be found here http://6502org.wikidot.com/software-math
;
; Written by Irmen de Jong (irmen@razorvine.net)
; License: GNU GPL 3.0, see LICENSE
;
@ -10,9 +12,79 @@
output raw
~ math {
; note: the following two ZP scratch registers must be the same as in c64lib
memory SCRATCH_ZP1 = $02 ; scratch register #1 in ZP
memory SCRATCH_ZP2 = $03 ; scratch register #2 in ZP
; @todo some interesting routines can be found here http://6502org.wikidot.com/software-math
sub multiply_bytes (byte1: X, byte2: Y) -> (A, X?) {
; ---- multiply 2 bytes, result as byte in A (signed or unsigned)
asm {
stx SCRATCH_ZP1
sty SCRATCH_ZP2
ldx #8
- asl a
asl SCRATCH_ZP1
bcc +
clc
adc SCRATCH_ZP2
+ dex
bne -
rts
}
}
sub multiply_bytes_16 (byte1: X, byte2: Y) -> (A?, XY) {
; ---- multiply 2 bytes, result as word in X/Y (unsigned)
asm {
lda #0
_m_with_add stx SCRATCH_ZP1
sty SCRATCH_ZP2
ldx #8
lsr SCRATCH_ZP1
- bcc +
clc
adc SCRATCH_ZP2
+ ror a
ror SCRATCH_ZP1
dex
bne -
tay
ldx SCRATCH_ZP1
rts
}
}
sub multiply_bytes_addA_16 (byte1: X, byte2: Y, add: A) -> (A?, XY) {
; ---- multiply 2 bytes and add A, result as word in X/Y (unsigned)
asm {
jmp multiply_bytes_16._m_with_add
}
}
sub divide_bytes (numerator: X, denominator: Y) -> (X, A) {
; ---- divide X by Y, result quotient in X, remainder in A (unsigned)
; division by zero will result in quotient = 255 and remainder = original number
asm {
stx SCRATCH_ZP1
sty SCRATCH_ZP2
lda #0
ldx #8
asl SCRATCH_ZP1
- rol a
cmp SCRATCH_ZP2
bcc +
sbc SCRATCH_ZP2
+ rol SCRATCH_ZP1
dex
bne -
ldx SCRATCH_ZP1
rts
}
}
}

View File

@ -86,7 +86,7 @@ The following 6502 hardware registers are directly accessible in your code (and
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:
- ``$02`` and ``$03`` are reserved for internal use as scratch registers by IL65
- the four locations ``$02``, ``$03``, ``$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.
@ -95,10 +95,11 @@ Theoretically you can use all of them in your program but there are a few limita
For the Commodore-64 here is a list of free-to-use zero page locations even when its BASIC and KERNAL are active:
``$02`` - ``$03`` (but see remark above); ``$04`` - ``$05``; ``$06``;
``$0a``; ``$2a``; ``$52``; ``$93``;
``$02``; ``$03``; ``$04``; ``$05``; ``$06``; ``$2a``; ``$52``;
``$f7`` - ``$f8``; ``$f9`` - ``$fa``; ``$fb`` - ``$fc``; ``$fd`` - ``$fe``
The four reserved locations mentioned above are subtracted from this set, leaving you with
five 1-byte and three 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 all of the zero page locations are suddenly available for variables.
@ -193,6 +194,9 @@ However you can specify some options globally in your program to change this beh
- ``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 and you drop back to the BASIC prompt.
Not that the interrupts are *disabled* when your program is entered!
(you want/have to set your own IRQ routine because the default one will write to
various locations in the zeropage)
If you use ``zp clobber``, you can no longer use BASIC or KERNAL routines,
because these depend on most of the locations in the ZP. This includes most of the floating-point
@ -231,7 +235,8 @@ want to work on later, because the contents of the ignored block are not syntact
### Importing, Including and Binary-Including Files
import "filename[.ill]"
Can only be used outside of a block (usually at the top of your file).
Must be used *after* any global option statements such as ``output``,
and can only be used outside of a block. But can otherwise occur between any blocks.
Reads everything from the named IL65 file at this point and compile it as a normal part of the program.
asminclude "filename.txt", scopelabel

View File

@ -21,13 +21,13 @@ start
if_ge goto label
if_gt goto label
if_cc goto value
if_cc goto memvalue
;if_cc goto memvalue
if_cc goto #value
if_cc goto #memvalue
;if_cc goto #memvalue
if_cc goto [value]
if_cc goto [memvalue]
if_cc goto $c000
if_cc goto [$c000.word]
;if_cc goto [memvalue]
;if_cc goto $c000
;if_cc goto [$c000.word]
label
; conditional if with a single 'truth' value (register)
@ -59,14 +59,20 @@ label2
if_zero memvalue goto label3
if memvalue goto label3
; conditional if with a single 'truth' value (indirect address)
if_true [$c000] goto label
if_not [$c000] goto label
if_zero [$c000] goto label
if [$c000] goto label
; if_true [XY] goto label ; @todo support indirect reg
label3
; conditional if with a single 'truth' value (indirect address)
if_true [$c000] goto label4
if_not [$c000] goto label4
if_zero [$c000] goto label4
if [$c000] goto label4
if_true [XY] goto label4 ; @todo support indirect reg
if_true [AY] goto label4 ; @todo support indirect reg
if_true [AX] goto label4 ; @todo support indirect reg
if_true [X] goto label4 ; @todo support indirect reg
if_true [A] goto label4 ; @todo support indirect reg
if_true [Y] goto label4 ; @todo support indirect reg
label4
return
}
@ -134,4 +140,4 @@ label1
label2
return
}
}

View File

@ -1,5 +1,5 @@
output prg,basic
;reg_preserve off ; @todo global option
;reg_preserve off ; @todo global option off/on default off?
import "c64lib"
@ -14,70 +14,6 @@ import "c64lib"
start
c64util.init_system()
XY = $0401
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
XY--
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
XY--
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
AX = $0401
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AX--
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AX--
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AY = $0401
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
AY--
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
AY--
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
c64.CHROUT('\n')
XY = $03ff
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
XY++
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
XY++
c64util.print_word_decimal(XY)
c64.CHROUT('\n')
AX = $03ff
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AX++
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AX++
c64util.print_word_decimal(AX)
c64.CHROUT('\n')
AY = $03ff
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
AY++
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
AY++
c64util.print_word_decimal(AY)
c64.CHROUT('\n')
c64.CHROUT('\n')
A = c64.VMCSB
A |= 2 ; @todo c64.VMCSB |= 2
c64.VMCSB = A
@ -115,7 +51,7 @@ printloop
c64util.print_string("es")
ask_guess
c64util.print_string(" left.\nWhat is your next guess? ")
A = c64util.input_chars(guess)
Y = c64util.input_chars(guess)
c64.CHROUT('\n')
[$22.word] = guess
c64.FREADSTR(A)

View File

@ -32,14 +32,14 @@ start2
c64util.print_string(greeting)
c64util.print_pstring(p_greeting)
c64util.print_byte_decimal(0)
c64util.print_byte_hex(0)
c64util.print_byte_hex(0, 0)
c64.CHROUT(13)
c64util.print_byte_decimal(13)
c64util.print_byte_hex(13)
c64util.print_byte_hex(0, 13)
c64.CHROUT(13)
c64util.print_byte_decimal(255)
c64util.print_byte_hex(254)
c64util.print_byte_hex(129)
c64util.print_byte_hex(0, 254)
c64util.print_byte_hex(0, 129)
c64.CHROUT(13)
c64.CHROUT(13)

27
todo.ill Normal file
View File

@ -0,0 +1,27 @@
output prg,basic
import "c64lib"
import "mathlib"
~ main {
; zpvar myvar @todo allow for zp vars like this
var bytevar
var bytevar2
var .word wordvar
var .float fl1
var .float fl2
start
; XY() ; @todo better syntax error, need [XY]
[AX]()
[AY]()
[XY]()
[AX]!()
[AY]!()
[XY]!()
return
}