diff --git a/il65/codegen.py b/il65/codegen.py index 127623700..07c5e5873 100644 --- a/il65/codegen.py +++ b/il65/codegen.py @@ -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) diff --git a/il65/main.py b/il65/main.py index 4022e6249..4f9da96b3 100644 --- a/il65/main.py +++ b/il65/main.py @@ -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: diff --git a/il65/parse.py b/il65/parse.py index 4dbd4a1e1..952f6b820 100644 --- a/il65/parse.py +++ b/il65/parse.py @@ -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: diff --git a/il65/preprocess.py b/il65/preprocess.py index e11dcb232..edf028f21 100644 --- a/il65/preprocess.py +++ b/il65/preprocess.py @@ -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 diff --git a/il65/symbols.py b/il65/symbols.py index 09843d178..f3c30d73a 100644 --- a/il65/symbols.py +++ b/il65/symbols.py @@ -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: diff --git a/lib/c64lib.ill b/lib/c64lib.ill index 63724ce63..fe027af02 100644 --- a/lib/c64lib.ill +++ b/lib/c64lib.ill @@ -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 + } + +} diff --git a/lib/il65lib.ill b/lib/il65lib.ill new file mode 100644 index 000000000..634338211 --- /dev/null +++ b/lib/il65lib.ill @@ -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) + + } +} diff --git a/lib/mathlib.ill b/lib/mathlib.ill index 83bcc2e9e..9fbd6f90f 100644 --- a/lib/mathlib.ill +++ b/lib/mathlib.ill @@ -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 + } +} } diff --git a/reference.md b/reference.md index a42ff0f1f..397f20444 100644 --- a/reference.md +++ b/reference.md @@ -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 diff --git a/testsource/conditionals.ill b/testsource/conditionals.ill index 652ad199e..aa3b1e371 100644 --- a/testsource/conditionals.ill +++ b/testsource/conditionals.ill @@ -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 -} \ No newline at end of file +} diff --git a/testsource/numbergame.ill b/testsource/numbergame.ill index 6cf444a4f..5855912b5 100644 --- a/testsource/numbergame.ill +++ b/testsource/numbergame.ill @@ -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) diff --git a/testsource/source4.ill b/testsource/source4.ill index 8caac1b4c..e03c5cbaa 100644 --- a/testsource/source4.ill +++ b/testsource/source4.ill @@ -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) diff --git a/todo.ill b/todo.ill new file mode 100644 index 000000000..379ff6301 --- /dev/null +++ b/todo.ill @@ -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 +}