diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b7718732 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "endlogical" + ] +} \ No newline at end of file diff --git a/modules/_scripts/makebuild.py b/modules/_scripts/makebuild.py index b6efcbb5..dc859c3e 100644 --- a/modules/_scripts/makebuild.py +++ b/modules/_scripts/makebuild.py @@ -11,7 +11,6 @@ import os import re -import sys from pathlib import Path from typing import TextIO @@ -19,6 +18,17 @@ export_re = re.compile(r"^\s*Export_(\w+)\s*:\s*(;.*)?$") +PAGE2_BEGIN = """ +.section page2 +.logical * + $2000 +""" + +PAGE2_END = """ +.endlogical +.send page2 +""" + + def main(*, module_name: str, build_dir: Path, page: int = 1) -> None: """Generate a single assembly build file for the specified module.""" if not build_dir.exists(): @@ -59,12 +69,10 @@ def process_source(path: Path, out: TextIO, *, page: int = 1) -> list[str]: if page == 2: normalized = " ".join(line.split()) if normalized == ".section code": - out.write("\t\t.section page2\n") - out.write("\t\t.logical * + $2000\n") + out.write(PAGE2_BEGIN) continue elif normalized == ".send code": - out.write("\t\t.here\n") - out.write("\t\t.send page2\n") + out.write(PAGE2_END) continue out.write(f"{line.rstrip()}\n") @@ -72,13 +80,17 @@ def process_source(path: Path, out: TextIO, *, page: int = 1) -> list[str]: return exports -def dump_exports(build_dir: Path, module_name: str, exports: list[str], *, page: int = 1) -> None: +def dump_exports( + build_dir: Path, module_name: str, exports: list[str], *, page: int = 1 +) -> None: """Save module exports to the corresponding `.exports` file.""" if not exports: return suffix = "_p2" if page == 2 else "" - with open(build_dir / (module_name + suffix + ".exports"), "w", encoding="utf-8") as out: + with open( + build_dir / (module_name + suffix + ".exports"), "w", encoding="utf-8" + ) as out: for export in exports: out.write(f"{export}\n") @@ -109,8 +121,12 @@ def collect_sources(module_name: str) -> list[Path]: parser = argparse.ArgumentParser(description="Build module assembly file") parser.add_argument("module_name", help="Name of the module to build") - parser.add_argument("build_dir", nargs="?", default=".build", help="Build output directory") - parser.add_argument("--page", type=int, default=1, choices=[1, 2], help="Module page (1 or 2)") + parser.add_argument( + "build_dir", nargs="?", default=".build", help="Build output directory" + ) + parser.add_argument( + "--page", type=int, default=1, choices=[1, 2], help="Module page (1 or 2)" + ) args = parser.parse_args() main( diff --git a/modules/_scripts/makeexport.py b/modules/_scripts/makeexport.py index 607d2eae..e9735a80 100644 --- a/modules/_scripts/makeexport.py +++ b/modules/_scripts/makeexport.py @@ -22,48 +22,7 @@ def main(*, build_dir: Path) -> None: print(f"PagingEnabled = {1 if paging else 0}") has_page2 = any(exports[m]["page"] == 2 for m in exports) - - if paging and has_page2: - # These routines are included inside an existing .section code block - # in 00start.asm, so no .section/.send wrappers are needed here. - # Slot3ModulePage/Slot3Depth/Slot3Saved are declared in 04data.inc - # (placed before numberBuffer/decimalBuffer to survive buffer overflows). - print("") - print("; --- Slot 3 module bank switching ---") - print("") - print("Slot3Init:") - print("\tstz Slot3Depth") - print("\tlda 8+4") - print("\tclc") - print("\tadc #3") - print("\tsta Slot3ModulePage") - print("\trts") - print("") - print("Slot3BankIn:") - print("\tphy") - print("\tpha") - print("\tinc Slot3Depth") - print("\tlda Slot3Depth") - print("\tcmp #1") - print("\tbne +") - print("\tldy 8+3") - print("\tsty Slot3Saved") - print("\tldy Slot3ModulePage") - print("\tsty 8+3") - print("+\tpla") - print("\tply") - print("\trts") - print("") - print("Slot3BankOut:") - print("\tpha") - print("\tphy") - print("\tdec Slot3Depth") - print("\tbne +") - print("\tldy Slot3Saved") - print("\tsty 8+3") - print("+\tply") - print("\tpla") - print("\trts") + print(f"HasPage2 = {1 if has_page2 else 0}") for module in exports: page = exports[module]["page"] @@ -74,25 +33,39 @@ def main(*, build_dir: Path) -> None: print(f"{routine}:") if paging: if page == 1: - print("\tinc 8+5") - print(f"\tjsr\tExport_{routine}") - print("\tphp") - print("\tdec 8+5") - print("\tplp") - print("\trts") + print(page1_thunk(routine)) elif page == 2: - print("\tjsr Slot3BankIn") - print(f"\tjsr\tExport_{routine}") - print("\tphp") - print("\tjsr Slot3BankOut") - print("\tplp") - print("\trts") + print(page2_thunk(routine)) + else: + raise RuntimeError(f"Unknown page #{page}") else: print(f"\tjmp\tExport_{routine}") print("\t.endif") +def page1_thunk(routine: str) -> str: + return f""" + inc 8+5 + jsr Export_{routine} + php + dec 8+5 + plp + rts +""" + + +def page2_thunk(routine: str) -> str: + return f""" + jsr Slot3BankIn + jsr Export_{routine} + php + jsr Slot3BankOut + plp + rts +""" + + def read_exports(build_dir: Path) -> dict[str, dict]: """Read all `.exports` files from the build directory. diff --git a/source/_basic.asm b/source/_basic.asm index 5a921871..38a272ef 100644 --- a/source/_basic.asm +++ b/source/_basic.asm @@ -159,16 +159,25 @@ .section code + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $C000, "BROKEN BUILD: non-paged code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes" + StartModuleCode: - .if PagingEnabled==1 - * = $A000 - .offs $2000 - .endif + .if PagingEnabled==1 + * = $A000 + .offs $2000 + .endif .send code + .include "../modules/.build/hardware.module.asm" .include "../modules/.build/tokeniser.module.asm" .include "../modules/.build/graphics.module.asm" +.section code + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $C000, "BROKEN BUILD: page 1 code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes" +.send code + ; --- Startup banner data in boot section ($6000-$7FFF) --- .include "../modules/hardware/startup/.build/banner.dat" @@ -180,3 +189,10 @@ StartModuleCode: .include "./common/generated/_errortext_p2.asm" .include "../modules/.build/kernel_p2.module.asm" .include "../modules/.build/sound_p2.module.asm" + +.section page2 +.logical * + $2000 + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $8000, "BROKEN BUILD: page 2 code overflows into SuperBASIC ROM space ($8000-$BFFF) by ", * - $8000," bytes" +.endlogical +.send page2 diff --git a/source/common/aa.system/00start.asm b/source/common/aa.system/00start.asm index 031cd76e..8565f0d6 100644 --- a/source/common/aa.system/00start.asm +++ b/source/common/aa.system/00start.asm @@ -33,6 +33,7 @@ KernelHeader: ;; Boot: jmp Start .include "../../../modules/.build/_exports.module.asm" + .include "./_slot3banking.asm" Start: ldx #$FF ; stack reset txs @@ -45,11 +46,12 @@ Start: ldx #$FF ; stack reset jsr EXTInitialize ; hardware initialization jsr EXTShowStartupBanner ; reads banner data from slot 3 - jsr DisplayBannerText ; print hardware, ROM & build info lda #3 ; remap slot 3 to RAM page 3 sta $0008+3 ; (frees $6000-$7FFF for arrays/temp buffers) + jsr DisplayBannerText ; print hardware, ROM & build info + lda 0 ; turn on editing of MMU LUT ora #$80 sta 0 diff --git a/source/common/aa.system/_slot3banking.asm b/source/common/aa.system/_slot3banking.asm new file mode 100644 index 00000000..ce7e2b32 --- /dev/null +++ b/source/common/aa.system/_slot3banking.asm @@ -0,0 +1,48 @@ +;; +; Slot 3 module bank switching +; +; Note that slot 3 (page 2) banking is about 4x slower than slot 5 (page 1), +; ~29 cycles vs ~112 cycles per thunk. +; +; `Slot3ModulePage`, `Slot3Depth`, and `Slot3Saved` are declared in +; `04data.inc` (placed before `numberBuffer`/`decimalBuffer` to survive +; buffer overflows). +;; + +.if PagingEnabled==1 && HasPage2==1 + +Slot3Init: + stz Slot3Depth + lda 8+4 + clc + adc #3 + sta Slot3ModulePage + rts + +Slot3BankIn: + phy + pha + inc Slot3Depth + lda Slot3Depth + cmp #1 + bne + + ldy 8+3 + sty Slot3Saved + ldy Slot3ModulePage + sty 8+3 ++ pla + ply + rts + +Slot3BankOut: + pha + phy + dec Slot3Depth + bne + + ldy Slot3Saved + sty 8+3 ++ ply + pla + rts + +.endif diff --git a/source/common/generated/_errortext.asm b/source/common/generated/_errortext.asm index 43901284..fd5650b8 100644 --- a/source/common/generated/_errortext.asm +++ b/source/common/generated/_errortext.asm @@ -1,6 +1,7 @@ ; ; This is automatically generated. ; + .section code ErrorText: .text "Break",0 diff --git a/source/common/generated/_errortext_p2.asm b/source/common/generated/_errortext_p2.asm index 939bc1ff..9af24005 100644 --- a/source/common/generated/_errortext_p2.asm +++ b/source/common/generated/_errortext_p2.asm @@ -1,8 +1,9 @@ ; ; This is automatically generated. ; - .section page2 - .logical * + $2000 + +.section page2 +.logical * + $2000 ErrorText: .text "Break",0 .text "Syntax error",0 @@ -35,5 +36,5 @@ ErrorText: .text "Too many parameters",0 .text "Formula too complex",0 .text "Initialization error",0 - .here - .send page2 +.endlogical +.send page2 diff --git a/source/scripts/errors.py b/source/scripts/errors.py index fe26e420..3ab220fc 100644 --- a/source/scripts/errors.py +++ b/source/scripts/errors.py @@ -1,50 +1,63 @@ -# ******************************************************************************************* -# ******************************************************************************************* -# -# Name : errors.py -# Purpose : Build the error files -# Date : 20th September 2022 -# Author : Paul Robson (paul@robsons.org.uk) -# -# ******************************************************************************************* -# ******************************************************************************************* +"""Generate error handling code and data""" + +import sys -import os,sys eText = [] -errors = open("common/errors/text/errors."+sys.argv[1],"r").readlines() -errors = [x.replace("\t"," ").strip() for x in errors if not x.startswith("#") and x.strip() != ""] +errors = open("common/errors/text/errors." + sys.argv[1], "r").readlines() +errors = [ + x.replace("\t", " ").strip() + for x in errors + if not x.startswith("#") and x.strip() != "" +] note = ";\n;\tThis is automatically generated.\n;\n" -h1 = open("common/generated/errors.inc","w") -h2 = open("common/generated/errors.asm","w") -h3 = open("common/generated/_errortext.asm","w") + +h1 = open("common/generated/errors.inc", "w") +h2 = open("common/generated/errors.asm", "w") h1.write(note) h2.write(note) -h3.write(note) h2.write(".section code\n") -for i in range(0,len(errors)): - e = [x.strip() for x in errors[i].split(":")] - if e[0].startswith("!"): - e[0] = e[0][1:] - h2.write("{0}Error:\n\t.error_{1}\n".format(e[0],e[0].lower())) - h1.write("ERRID_{0} = {1}\n".format(e[0].upper(),i+1)) - h1.write("error_{0} .macro\n\tlda\t#{1}\n\tjmp\tErrorHandler\n\t.endm\n".format(e[0].lower(),str(i+1))) - eText.append(e[1]) +for i in range(0, len(errors)): + e = [x.strip() for x in errors[i].split(":")] + if e[0].startswith("!"): + e[0] = e[0][1:] + h2.write("{0}Error:\n\t.error_{1}\n".format(e[0], e[0].lower())) + h1.write("ERRID_{0} = {1}\n".format(e[0].upper(), i + 1)) + h1.write( + "error_{0} .macro\n\tlda\t#{1}\n\tjmp\tErrorHandler\n\t.endm\n".format( + e[0].lower(), str(i + 1) + ) + ) + eText.append(e[1]) h2.write(".send code\n") -h3.write(".section code\n") -h3.write("ErrorText:\n") -h3.write("\n".join(['\t.text\t"{0}",0'.format(x) for x in eText])) -h3.write("\n.send code\n") -# page2 variant for slot 3 module page -h4 = open("common/generated/_errortext_p2.asm","w") -h4.write(note) -h4.write("\t.section page2\n") -h4.write("\t.logical * + $2000\n") -h4.write("ErrorText:\n") -h4.write("\n".join(['\t.text\t"{0}",0'.format(x) for x in eText])) -h4.write("\n\t.here\n") -h4.write("\t.send page2\n") -h4.close() h1.close() h2.close() -h3.close() \ No newline at end of file + +# error messages +error_messages = "\n".join(['\t.text\t"{0}",0'.format(x) for x in eText]) +h3 = open("common/generated/_errortext.asm", "w") +h3.write(note) +h3.write(""" +.section code +ErrorText: +""") +h3.write(error_messages) +h3.write(""" +.send code +""") +h3.close() + +# error messages, page2 variant for slot 3 module page +h4 = open("common/generated/_errortext_p2.asm", "w") +h4.write(note) +h4.write(""" +.section page2 +.logical * + $2000 +ErrorText: +""") +h4.write(error_messages) +h4.write(""" +.endlogical +.send page2 +""") +h4.close() diff --git a/source/scripts/makebuild.py b/source/scripts/makebuild.py index 15a11bd9..c12612da 100644 --- a/source/scripts/makebuild.py +++ b/source/scripts/makebuild.py @@ -68,6 +68,7 @@ def sortKey(k): for f in includeFiles: h.write('\t.include\t"{0}"\n'.format(f.replace(os.sep, "/"))) h.write("\n\n") + # Place fn.asm last: FunctionCall is safe at the $9FFF/$A000 page boundary # because it is never called by paged modules. lateFiles = [] @@ -76,18 +77,24 @@ def sortKey(k): lateFiles.append(f) else: h.write('\t.include\t"{0}"\n'.format(f.replace(os.sep, "/"))) + for f in lateFiles: h.write('\t.include\t"{0}"\n'.format(f.replace(os.sep, "/"))) -h.write("\n\n") - - -h.write(".section code\n") -h.write("StartModuleCode:\n") -h.write("\t.if PagingEnabled==1\n") -h.write("\t* = $A000\n") -h.write("\t.offs $2000\n") -h.write("\t.endif\n") -h.write(".send code\n") +h.write("\n") + +h.write(""" +.section code + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $C000, "BROKEN BUILD: non-paged code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes" + +StartModuleCode: + .if PagingEnabled==1 + * = $A000 + .offs $2000 + .endif +.send code +""") +h.write("\n") for f in moduleFiles: if f.startswith("!"): @@ -95,6 +102,14 @@ def sortKey(k): else: h.write('\t.include\t"{0}"\n'.format(f.replace(os.sep, "/"))) + +h.write(""" +.section code + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $C000, "BROKEN BUILD: page 1 code overflows into kernel-reserved I/O space ($C000-$DFFF) by ", * - $C000," bytes" +.send code +""") + h.write("\n; --- Startup banner data in boot section ($6000-$7FFF) ---\n") h.write('\t.include\t"../modules/hardware/startup/.build/banner.dat"\n') @@ -109,4 +124,14 @@ def sortKey(k): for f in page2ModuleFiles: h.write('\t.include\t"{0}"\n'.format(f.replace(os.sep, "/"))) + h.write(""" +.section page2 +.logical * + $2000 + ; issuing a warning instead of an error so one can examine output listing for details + .cwarn * > $8000, "BROKEN BUILD: page 2 code overflows into SuperBASIC ROM space ($8000-$BFFF) by ", * - $8000," bytes" +.endlogical +.send page2 +""") + + h.close()