src_load reads a SEQ file from device 8 once into a bank-1 cache (D118). It enforces the 80-byte line, 599-line, and 18,999-byte limits as bytes arrive and reports stable codes with exact positions. src_rewind and src_next_line replay the cache one logical line at a time for every pass. diskio.s wraps the KERNAL file calls; channels close on every path. Verified in x128 with a 1581: eight fixtures, including the exact limits, each limit error, a missing file (DOS 62), and a missing final $0D, all match host-computed line counts, Fletcher-16 checksums, and positions. Adds a shared PowerShell test library for x128 scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>main
| @@ -4,8 +4,9 @@ | |||
| # | |||
| # Bank 0 (CR=$0E) Bank 1 (CR=$4E) | |||
| # $0000-$12FF system (BASIC/KERNAL) $0000-$1FFF hidden by common RAM | |||
| # $1300-$18FF COMMON (copied at init) $2000-$BFFF BANK1 compiler tables | |||
| # $1900-$19FF DISKBUF $C000-$FFFF system ROM (unmapped RAM) | |||
| # $1300-$18FF COMMON (copied at init) $2000-$6AFF SRCCACHE source cache | |||
| # $1900-$19FF DISKBUF $6B00-$BFFF BANK1 compiler tables | |||
| # $C000-$FFFF system ROM (unmapped RAM) | |||
| # $1A00-$1AFF private zero page (P0) | |||
| # $1B00-$1BEF SCRATCH (common) | |||
| # $1BF0-$1BFF MAILBOX | |||
| @@ -28,7 +29,9 @@ MEMORY { | |||
| MAILBOX: file = "", start = $1BF0, size = $0010, define = yes; | |||
| SWSTACK: file = "", start = $B800, size = $0800, define = yes; # to $BFFF | |||
| BANK1: file = "", start = $2000, size = $A000, define = yes; # to $BFFF | |||
| # Whole-file source cache (D118): >= 18,999 bytes, page aligned. | |||
| SRCCACHE: file = "", start = $2000, size = $4B00, define = yes; # to $6AFF | |||
| BANK1: file = "", start = $6B00, size = $5500, define = yes; # to $BFFF | |||
| } | |||
| SEGMENTS { | |||
| @@ -45,6 +48,7 @@ SEGMENTS { | |||
| SCRATCH: load = SCRATCH, type = bss, optional = yes; | |||
| MAILBOX: load = MAILBOX, type = bss, optional = yes; | |||
| SWSTACK: load = SWSTACK, type = bss, optional = yes; | |||
| SOURCE: load = SRCCACHE, type = bss, optional = yes, define = yes; | |||
| TABLES: load = BANK1, type = bss, optional = yes, define = yes; | |||
| } | |||
| @@ -141,7 +141,8 @@ and `docs/memory-map.md` explains it and gives the evidence. In summary: | |||
| - **Bank 0:** COMMON `$1300-$18FF` (copied at init), DISKBUF `$1900`, private | |||
| zero page `$1A00`, SCRATCH `$1B00-$1BEF`, MAILBOX `$1BF0-$1BFF`, MAIN | |||
| `$1C0D-$B7FF` (STARTUP first), SWSTACK `$B800-$BFFF`. | |||
| - **Bank 1:** compiler tables `$2000-$BFFF` (40 KB). | |||
| - **Bank 1:** source cache `$2000-$6AFF` (D118), compiler tables | |||
| `$6B00-$BFFF` (21,760 bytes). | |||
| - **Zero page:** the compiler owns logical `$02-$FF` via P0; KERNAL calls run | |||
| on the system zero page. | |||
| - **NMI:** guarded by `mmu_nmi_guard` through `$0318`; RESTORE is ignored | |||
| @@ -195,8 +196,10 @@ pointer-rich abstract syntax tree. | |||
| - write PRG and MAP output; | |||
| - delete or invalidate partial output on failure. | |||
| Repeated passes trade disk/source traversal time for bounded memory. The source | |||
| stream should be rewindable without reopening unsafe shared emulator state. | |||
| Repeated passes re-read the source, not the disk. `src_load` (`source.s`) | |||
| reads the file once into the bank-1 cache and enforces the editor-profile | |||
| limits. Each pass calls `src_rewind`, then `src_next_line` delivers one logical | |||
| line at a time into a common-RAM buffer (D118, E02-S01). | |||
| ## 6. Compiler Data Records | |||
| @@ -386,6 +389,5 @@ features depend on it. | |||
| - Generated-program memory map (the compiler map is frozen in section 4.4) | |||
| - Final interface-result convention | |||
| - Compiler table capacities and their user-visible resource limits | |||
| - Whether to cache the complete source in banked RAM when it fits | |||
| - Physical-hardware acceptance requirements | |||
| @@ -39,6 +39,7 @@ documented default. `Proposed` means a later change still needs approval. | |||
| | D115 | Character literals use single quotes and contain exactly one PETSCII character or supported escape. | The `char` type has a direct, unambiguous literal form while `//` remains the comment syntax. | | |||
| | D116 | Runtime faults report a stable code and the best available map-resolvable program address. | Rich stack traces are deferred; faults remain diagnosable within the runtime budget. | | |||
| | D117 | Freeze the compiler memory map in `cfg/compiler.cfg`: 8K bottom common RAM, COMMON at `$1300`, private zero page at bank 0 page `$1A` via P0, bank-1 tables at `$2000-$BFFF`, IRQs masked and NMIs guarded in compiler states, test mailbox at `$1BF0`. | Internal contract backed by E01-S05 x128 evidence (`docs/memory-map.md`); no user-visible behavior except that RESTORE is ignored while compiling. Generated-program maps remain open. | | |||
| | D118 | The compiler loads the whole source file once into a bank-1 cache (`$2000-$6AFF`) and replays it for every pass, instead of re-reading the disk. | Rewind is exact by construction and avoids reading a slow disk three times. Compiler tables get the remaining 21,760 bytes of bank 1 (`$6B00-$BFFF`). Internal; no user-visible behavior. | | |||
| ## Proposed | |||
| @@ -61,6 +62,6 @@ No proposed product changes are currently pending. | |||
| 1. What generated-program memory map should E04-S01/E04-S02 adopt? (The | |||
| compiler map is D117.) | |||
| 2. What compiler table capacities fit in the 40 KB bank-1 window and 254-byte | |||
| zero page now that the compiler map is frozen? | |||
| 2. What compiler table capacities fit in the 21,760-byte bank-1 table window | |||
| and 254-byte zero page (D117, D118)? | |||
| @@ -1,7 +1,8 @@ | |||
| # Nova128 Compiler Memory Map | |||
| Story: E01-S05 | |||
| Status: frozen for the compiler (D117); verified in VICE x128 3.10 | |||
| Status: frozen for the compiler (D117, source cache D118); verified in VICE | |||
| x128 3.10 | |||
| Authoritative source: `cfg/compiler.cfg`. This document explains it. | |||
| The generated-program memory map is **not** frozen here. It belongs to E04-S01 | |||
| @@ -42,12 +43,13 @@ COMMON, DISKBUF, private ZP, SCRATCH, and MAILBOX are visible from bank 1. | |||
| | Range | Size | Name | Contents | | |||
| |---|---|---|---| | |||
| | `$0000-$1FFF` | | hidden | Covered by bottom common RAM in compiler states | | |||
| | `$2000-$BFFF` | 40960 | BANK1 / `TABLES` | Names, symbols, class/interface/type records, pass workspaces | | |||
| | `$2000-$6AFF` | 19200 | SRCCACHE / `SOURCE` | Whole source file, loaded once by `src_load` (18,999-byte limit) | | |||
| | `$6B00-$BFFF` | 21760 | BANK1 / `TABLES` | Names, symbols, class/interface/type records, pass workspaces | | |||
| | `$C000-$FFFF` | | unmapped | System ROM is visible here instead; BASIC string space at the top of bank 1 is untouched | | |||
| Bank-1 data is reached only through `mmu.s` helpers (`mmu_peek1`/`mmu_poke1` | |||
| with the zero-page pointer `mmu_ptr`, and later block-copy routines), or by | |||
| code in COMMON. MAIN code never runs while bank 1 is mapped, because MAIN above | |||
| and `mmu_copy_to1`, using the zero-page pointers `mmu_ptr`/`mmu_src`), or by | |||
| code in COMMON such as `src_fetch_line`. MAIN code never runs while bank 1 is mapped, because MAIN above | |||
| `$1FFF` is not visible then. | |||
| BASIC 7 keeps variables in bank 1 from `$0400`. The compiler is started with | |||
| @@ -59,7 +61,7 @@ state. | |||
| In compiler states P0 points at bank 0 page `$1A`, so the compiler owns the | |||
| whole logical zero page `$02-$FF` (linker area `ZP`, 254 bytes). `$00/$01` are | |||
| the 8502 port and are never allocated. Modules allocate zero page only through | |||
| the `ZEROPAGE` segment. Current users: `mmu_ptr` (2 bytes). | |||
| the `ZEROPAGE` segment. Current users: `mmu_ptr` and `mmu_src` (4 bytes). | |||
| System zero page stays intact. KERNAL calls switch P0 back to `$0000` for the | |||
| call. While P0 is relocated, the system zero page is still readable at | |||
| @@ -117,7 +119,7 @@ Protocol: | |||
| |---|---| | |||
| | ZP | A store to logical ZP lands at `$1Axx`; system ZP is unchanged | | |||
| | KCALL | CHROUT via `kcall_inline` from both states; private ZP and CR/P0 intact afterwards | | |||
| | BANK1 | First and last TABLES bytes hold bank-1 values; bank 0 at the same addresses unchanged | | |||
| | BANK1 | First and last TABLES bytes (`$6B00`, `$BFFF`) hold bank-1 values; bank 0 at the same addresses unchanged | | |||
| | COMMON | DISKBUF/SCRATCH written in bank 0 read back in bank 1; mailbox written from bank 1 | | |||
| | NMI | A CIA #2 timer NMI taken in the bank-1 state returns with CR `$4E`, P0 `$1A`, sentinel `$C5`, and the same SP | | |||
| | RESTORE | After `mmu_shutdown`: CR/RCR/P0/P1/`$0318` and SP equal their entry values | | |||
| @@ -0,0 +1,90 @@ | |||
| --- | |||
| story: E02-S01 | |||
| owner: e02_source_reader | |||
| status: done | |||
| started: 2026-11-07 | |||
| depends-on: [E01-S03, E01-S05] | |||
| --- | |||
| # E02-S01 Claim | |||
| ## Intended change | |||
| Add the compiler's source reader. `src_load` reads a PETSCII SEQ file from | |||
| device 8 once into a bank-1 cache, enforcing the line, line-count, and file | |||
| size limits with stable error codes and exact positions. `src_rewind` and | |||
| `src_next_line` then replay the cached source identically for every pass, | |||
| one logical line at a time. Disk access goes through new `diskio.s` KERNAL | |||
| wrappers that always close their channels. | |||
| ## File area | |||
| - `src/compiler/source.s`, `src/compiler/diskio.s` | |||
| - `src/common/source.inc` | |||
| - `src/compiler/mmu.s` (bank-1 block copy only) | |||
| - `cfg/compiler.cfg` (bank-1 source cache area) | |||
| - `tests/platform/map-skeleton.s` (table-size constant only) | |||
| - `tests/source/` | |||
| - `scripts/build-source-test.ps1`, `scripts/verify-source-test.ps1`, | |||
| `scripts/lib/` | |||
| - `docs/memory-map.md`, `docs/architecture.md`, `docs/decisions.md` (D118) | |||
| - `planning/claims/E02-S01.md` | |||
| - generated files under `build/` | |||
| ## Acceptance evidence | |||
| - [x] Reads Vim-created SEQ source without host conversion: fixtures use | |||
| Vim's exact save format (PETSCII, `$0D` lines, both letter ranges in | |||
| `decls.n8`); the reader delivers the bytes unchanged, verified by a host | |||
| Fletcher-16 match. A live Vim edit is E07-S03. | |||
| - [x] Accepts `$0D` lines and a missing final `$0D`: `nofinal.n8` yields the | |||
| same 50 lines and checksum as `decls.n8` with one byte fewer. | |||
| - [x] Enforces line/file limits with stable errors: `edge.n8` (80-byte line, | |||
| 599 lines, 18,999 bytes) loads; `longln.n8` -> code 3 at 3:81, | |||
| `manyln.n8` -> code 4 at 600:1, `big.n8` -> code 2 at 500:38. | |||
| - [x] Rewinds identically for all compiler passes: two replays per load give | |||
| identical line counts and checksums, and `src_line_no` ends at the line | |||
| count. | |||
| - [x] Disk failure closes channels and leaves no partial output: `LDTND` is 0 | |||
| after every load; after each failure `src_len` is 0 and `src_next_line` | |||
| returns end of source at once; a later `decls.n8` load succeeds unchanged. | |||
| ## Coordination notes | |||
| Source error codes are reader return codes. E02-S04 maps them to stable `Exxx` | |||
| diagnostics. A true Vim round trip (edit in x64sc, compile in x128) is | |||
| E07-S03; this story proves the reader consumes Vim's exact byte format | |||
| (`../vim/NOTES.md`, `vedit.c` save path) with no conversion. | |||
| ## Work log | |||
| - Confirmed Vim's save format in `../vim/NOTES.md` and `vedit.c` (raw | |||
| buffer, `$0D` lines, final `$0D` appended, 80-column wrap on load). | |||
| - Recorded D118: one disk read into a bank-1 cache (`$2000-$6AFF`), tables | |||
| keep `$6B00-$BFFF` (21,760 bytes). Map skeleton re-verified with it. | |||
| - Added `mmu_copy_to1` and `mmu_src` to `mmu.s`, `diskio.s` with a COMMON | |||
| chunk reader run through `kcall`, `source.s`, and `diskio.inc`. | |||
| - Added `scripts/lib/Nova128Test.psm1` (link, PETSCII conversion, disk | |||
| images, remote monitor, mailbox polling). The monitor helper waits for the | |||
| `(C:$xxxx)` prompt; a fixed delay returned empty replies with a 1581. | |||
| - Negative control: allowing 81-byte lines makes the C128 report FAIL and the | |||
| host flag `longln.n8` (code 0, position 0:0, 100 bytes kept). Restored. | |||
| ## Completion | |||
| - Tests run: `scripts/verify-source-test.ps1` (pass plus mutation run), | |||
| `scripts/verify-map-skeleton.ps1`, `scripts/verify-kernal-probe.ps1`, | |||
| `tests/grammar/validate.ps1`, `tests/disk/prepare-disk.tests.ps1`. | |||
| - Results: all 8 fixture records match host expectations; screenshot shows | |||
| each result code and `NOVA128 SOURCE TEST OK`; regressions pass. | |||
| - Files changed: `src/compiler/source.s`, `src/compiler/diskio.s`, | |||
| `src/compiler/mmu.s`, `src/common/source.inc`, `src/common/diskio.inc`, | |||
| `cfg/compiler.cfg`, `tests/source/source-test.s`, | |||
| `tests/platform/map-skeleton.s`, `scripts/build-source-test.ps1`, | |||
| `scripts/verify-source-test.ps1`, `scripts/build-map-skeleton.ps1`, | |||
| `scripts/lib/Nova128Test.psm1`, `docs/memory-map.md`, | |||
| `docs/architecture.md`, `docs/decisions.md` (D118), this claim. | |||
| - Remaining risks: device 8 is fixed; a read error part-way through a file | |||
| (ST error other than EOI) is handled in code but not exercised, since VICE | |||
| offers no simple way to inject one; only the 1581 (D81) was tested; error | |||
| codes are reader codes until E02-S04 assigns user-facing `Exxx` IDs. | |||
| @@ -87,7 +87,7 @@ if ($labelText -notmatch "(?im)^al\s+001C0D\s+\.skel_entry\s*$") { | |||
| throw "skel_entry is not at `$1C0D (the SYS 7181 target)" | |||
| } | |||
| Require-Segment "SWSTACK" 0xB800 0xBFFF | |||
| Require-Segment "TABLES" 0x2000 0xBFFF | |||
| Require-Segment "TABLES" 0x6B00 0xBFFF | |||
| if ($segments["MAILBOX"].Start -ne 0x1BF0) { throw "mailbox is not at `$1BF0" } | |||
| $bytes = [IO.File]::ReadAllBytes($program) | |||
| @@ -95,7 +95,7 @@ if ($bytes[0] -ne 0x01 -or $bytes[1] -ne 0x1C) { throw "load address is not `$1C | |||
| Write-Host ("built {0} ({1} bytes)" -f $program, $bytes.Length) | |||
| $limits = [ordered]@{ | |||
| ZEROPAGE = 0x00FE; COMMON = 0x0600; SWSTACK = 0x0800; TABLES = 0xA000 | |||
| ZEROPAGE = 0x00FE; COMMON = 0x0600; SWSTACK = 0x0800; TABLES = 0x5500 | |||
| } | |||
| foreach ($name in ($segments.Keys | Sort-Object { $segments[$_].Start })) { | |||
| $seg = $segments[$name] | |||
| @@ -0,0 +1,117 @@ | |||
| # E02-S01: build the source-reader test PRG and a fresh D81 holding it plus | |||
| # generated PETSCII fixtures. Writes build/source-test/expected.json with the | |||
| # results the host computes independently of the C128 code. | |||
| [CmdletBinding()] | |||
| param() | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| $out = Join-Path $project "build\source-test" | |||
| New-Item -ItemType Directory -Force -Path $out | Out-Null | |||
| $link = Invoke-Nova128Link -Name "source-test" -BuildDir $out ` | |||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||
| (Join-Path $project "src\compiler\mmu.s"), | |||
| (Join-Path $project "src\compiler\diskio.s"), | |||
| (Join-Path $project "src\compiler\source.s"), | |||
| (Join-Path $project "tests\source\source-test.s")) | |||
| if ((Get-Nova128Label $link.Labels "test_entry") -ne 0x1C0D) { throw "test_entry is not at `$1C0D" } | |||
| # ---------------------------------------------------------------- fixtures | |||
| function Get-Lines([byte[]]$bytes) { | |||
| # Split exactly as src_next_line does: $0D ends a line; trailing bytes | |||
| # without $0D form a last line. | |||
| $lines = New-Object System.Collections.Generic.List[byte[]] | |||
| $start = 0 | |||
| for ($i = 0; $i -lt $bytes.Length; $i++) { | |||
| if ($bytes[$i] -eq 13) { | |||
| if ($i -gt $start) { $lines.Add([byte[]]$bytes[$start..($i - 1)]) } | |||
| else { $lines.Add([byte[]]@()) } | |||
| $start = $i + 1 | |||
| } | |||
| } | |||
| if ($start -lt $bytes.Length) { $lines.Add([byte[]]$bytes[$start..($bytes.Length - 1)]) } | |||
| return ,$lines | |||
| } | |||
| function Get-Fletcher([byte[]]$bytes) { | |||
| $s1 = 0; $s2 = 0 | |||
| foreach ($line in (Get-Lines $bytes)) { | |||
| foreach ($b in (@($line) + 13)) { | |||
| $s1 = ($s1 + $b) % 255 | |||
| $s2 = ($s2 + $s1) % 255 | |||
| } | |||
| } | |||
| return @($s1, $s2) | |||
| } | |||
| function New-Line([int]$length, [int]$seed) { | |||
| # Mixed case so both PETSCII letter ranges appear. | |||
| $alphabet = "abcdefghijKLMNOPQRST0123456789 =+()" | |||
| $chars = for ($i = 0; $i -lt $length; $i++) { $alphabet[($i + $seed) % $alphabet.Length] } | |||
| return (-join $chars) | |||
| } | |||
| $fixtures = [ordered]@{} | |||
| $decls = ConvertTo-Nova128Petscii (Get-Content -Raw (Join-Path $project "tests\grammar\accepted\decls.n8")) | |||
| if ($decls[-1] -ne 13) { throw "decls.n8 should end with a newline" } | |||
| $fixtures["decls.n8"] = $decls | |||
| $fixtures["nofinal.n8"] = [byte[]]$decls[0..($decls.Length - 2)] | |||
| $edge = New-Object System.Text.StringBuilder | |||
| [void]$edge.Append((New-Line 80 0) + "`n") # exactly the line limit | |||
| for ($i = 0; $i -lt 598; $i++) { | |||
| $len = if ($i -lt 380) { 31 } else { 30 } # totals exactly 18,999 bytes | |||
| [void]$edge.Append((New-Line $len $i) + "`n") | |||
| } | |||
| $fixtures["edge.n8"] = ConvertTo-Nova128Petscii $edge.ToString() | |||
| $long = "a = 1`nb = 2`n" + (New-Line 81 3) + "`nc = 3`n" | |||
| $fixtures["longln.n8"] = ConvertTo-Nova128Petscii $long | |||
| $many = (1..600 | ForEach-Object { "x = $_" }) -join "`n" | |||
| $fixtures["manyln.n8"] = ConvertTo-Nova128Petscii ($many + "`n") | |||
| $big = (1..500 | ForEach-Object { New-Line 37 $_ }) -join "`n" | |||
| $fixtures["big.n8"] = ConvertTo-Nova128Petscii ($big + "`n") | |||
| # Boundary sanity: these are the facts the expectations below rely on. | |||
| $edgeLines = Get-Lines $fixtures["edge.n8"] | |||
| if ($fixtures["edge.n8"].Length -ne 18999 -or $edgeLines.Count -ne 599 -or $edgeLines[0].Length -ne 80) { | |||
| throw "edge.n8 is not at the exact limits" | |||
| } | |||
| if ($fixtures["big.n8"].Length -ne 19000) { throw "big.n8 is not 19,000 bytes" } | |||
| $hasLow = $decls | Where-Object { $_ -ge 0x41 -and $_ -le 0x5A } | |||
| $hasHigh = $decls | Where-Object { $_ -ge 0xC1 -and $_ -le 0xDA } | |||
| if (-not $hasLow -or -not $hasHigh) { throw "decls.n8 lacks one PETSCII letter range" } | |||
| $files = @(@{ Name = "source-test"; Path = $link.Program; Type = "prg" }) | |||
| foreach ($name in $fixtures.Keys) { | |||
| $path = Join-Path $out ($name -replace '\.n8$', '.pet') | |||
| [IO.File]::WriteAllBytes($path, $fixtures[$name]) | |||
| $files += @{ Name = $name; Path = $path; Type = "seq" } | |||
| } | |||
| $disk = Join-Path $out "source-test.d81" | |||
| New-Nova128Disk -Path $disk -Files $files | |||
| # ------------------------------------------------------------ expectations | |||
| function Ok([string]$name) { | |||
| $b = $fixtures[$name]; $f = Get-Fletcher $b | |||
| return [ordered]@{ name = $name; code = 0; lines = (Get-Lines $b).Count; len = $b.Length; | |||
| s1 = $f[0]; s2 = $f[1] } | |||
| } | |||
| $expected = @( | |||
| (Ok "decls.n8"), | |||
| (Ok "nofinal.n8"), | |||
| (Ok "edge.n8"), | |||
| [ordered]@{ name = "longln.n8"; code = 3; line = 3; col = 81 }, | |||
| [ordered]@{ name = "manyln.n8"; code = 4; line = 600; col = 1 }, | |||
| [ordered]@{ name = "big.n8"; code = 2; line = 500; col = 38 }, | |||
| [ordered]@{ name = "missing.n8"; code = 1; dos = 62 }, | |||
| (Ok "decls.n8") | |||
| ) | |||
| $expected | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $out "expected.json") | |||
| Write-Host ("built {0}" -f $link.Program) | |||
| Write-Host ("disk {0} ({1} fixtures)" -f $disk, $fixtures.Count) | |||
| @@ -0,0 +1,233 @@ | |||
| # Shared helpers for Nova128 build and x128 verification scripts. | |||
| # Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| $script:ViceBin = "C:\Program Files\GTK3VICE-3.10-win64\bin" | |||
| function Get-ViceTool([string]$Name) { | |||
| $path = Join-Path $script:ViceBin "$Name.exe" | |||
| if (-not (Test-Path -LiteralPath $path)) { throw "$Name.exe not found at $path" } | |||
| return $path | |||
| } | |||
| # Assemble each source and link them with an ld65 config. | |||
| # Returns @{ Program; Map; Labels }. | |||
| function Invoke-Nova128Link { | |||
| param( | |||
| [Parameter(Mandatory)][string]$Name, | |||
| [Parameter(Mandatory)][string[]]$Sources, | |||
| [Parameter(Mandatory)][string]$Config, | |||
| [Parameter(Mandatory)][string]$BuildDir, | |||
| [string[]]$Defines = @() | |||
| ) | |||
| $ca65 = (Get-Command ca65 -ErrorAction Stop).Source | |||
| $ld65 = (Get-Command ld65 -ErrorAction Stop).Source | |||
| New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null | |||
| $out = @{ | |||
| Program = Join-Path $BuildDir "$Name.prg" | |||
| Map = Join-Path $BuildDir "$Name.map" | |||
| Labels = Join-Path $BuildDir "$Name.lbl" | |||
| } | |||
| foreach ($path in $out.Values) { | |||
| if (Test-Path -LiteralPath $path) { Remove-Item -LiteralPath $path -Force } | |||
| } | |||
| $defineArgs = @() | |||
| foreach ($d in $Defines) { $defineArgs += @("-D", $d) } | |||
| $objects = @() | |||
| foreach ($source in $Sources) { | |||
| $object = Join-Path $BuildDir ("$Name-" + [IO.Path]::GetFileNameWithoutExtension($source) + ".o") | |||
| & $ca65 --cpu 6502 @defineArgs -o $object $source | |||
| if ($LASTEXITCODE -ne 0) { throw "ca65 failed on $source" } | |||
| $objects += $object | |||
| } | |||
| $linkOutput = & $ld65 -C $Config -o $out.Program -m $out.Map -Ln $out.Labels @objects 2>&1 | |||
| if ($LASTEXITCODE -ne 0) { throw ("ld65 failed:`n" + ($linkOutput | Out-String)) } | |||
| return $out | |||
| } | |||
| function Get-Nova128Label { | |||
| param([Parameter(Mandatory)][string]$Labels, [Parameter(Mandatory)][string]$Name) | |||
| $text = Get-Content -Raw -LiteralPath $Labels | |||
| $m = [regex]::Match($text, "(?im)^al\s+([0-9a-f]+)\s+\." + [regex]::Escape($Name) + "\s*$") | |||
| if (-not $m.Success) { throw "label $Name not found in $Labels" } | |||
| return [Convert]::ToInt32($m.Groups[1].Value, 16) | |||
| } | |||
| # ASCII text -> PETSCII SEQ bytes, using the same rules as Vim and | |||
| # prepare-disk.ps1: a-z -> $41-$5A, A-Z -> $C1-$DA, CRLF/LF -> $0D. | |||
| function ConvertTo-Nova128Petscii { | |||
| param([Parameter(Mandatory)][AllowEmptyString()][string]$Text) | |||
| $bytes = [Text.Encoding]::ASCII.GetBytes($Text) | |||
| $out = New-Object System.Collections.Generic.List[byte] | |||
| for ($i = 0; $i -lt $bytes.Length; $i++) { | |||
| $v = $bytes[$i] | |||
| if ($v -eq 13) { | |||
| if ($i + 1 -lt $bytes.Length -and $bytes[$i + 1] -eq 10) { continue } | |||
| $out.Add(13); continue | |||
| } | |||
| if ($v -eq 10) { $out.Add(13); continue } | |||
| if ($v -ge 0x61 -and $v -le 0x7A) { $out.Add([byte]($v - 0x20)); continue } | |||
| if ($v -ge 0x41 -and $v -le 0x5A) { $out.Add([byte]($v + 0x80)); continue } | |||
| $out.Add($v) | |||
| } | |||
| return ,$out.ToArray() | |||
| } | |||
| # Create a fresh disk image and write files to it. | |||
| # $Files: ordered list of @{ Name = "decls.n8"; Path = host file; Type = "seq"|"prg" } | |||
| function New-Nova128Disk { | |||
| param( | |||
| [Parameter(Mandatory)][string]$Path, | |||
| [Parameter(Mandatory)][object[]]$Files, | |||
| [ValidateSet("d64", "d81")][string]$Type = "d81", | |||
| [string]$Label = "nova128,n8" | |||
| ) | |||
| $c1541 = Get-ViceTool "c1541" | |||
| if (Test-Path -LiteralPath $Path) { Remove-Item -LiteralPath $Path -Force } | |||
| $c1541Args = @("-format", $Label, $Type, $Path) | |||
| foreach ($f in $Files) { | |||
| $suffix = if ($f.Type -eq "seq") { ",s" } else { "" } | |||
| $c1541Args += @("-write", $f.Path, ($f.Name + $suffix)) | |||
| } | |||
| $output = & $c1541 @c1541Args 2>&1 | |||
| if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $Path)) { | |||
| throw ("c1541 failed:`n" + ($output | Out-String)) | |||
| } | |||
| $listing = (& $c1541 -attach $Path -list 2>&1) | Out-String | |||
| foreach ($f in $Files) { | |||
| if ($listing -notmatch ('"' + [regex]::Escape($f.Name) + '"\s+' + $f.Type)) { | |||
| throw "$($f.Name) ($($f.Type)) missing from $Path" | |||
| } | |||
| } | |||
| } | |||
| # Start x128 with a remote monitor. Returns a session object. | |||
| function Start-Nova128X128 { | |||
| param( | |||
| [Parameter(Mandatory)][string]$Autostart, | |||
| [int]$Port = 6530, | |||
| [string]$Drive8Image, | |||
| [string]$Drive8Type = "1581", | |||
| [string]$Screenshot | |||
| ) | |||
| $x128 = Get-ViceTool "x128" | |||
| $argList = @() | |||
| if ($Drive8Image) { | |||
| $argList += @("-drive8type", $Drive8Type, "-drive8truedrive", "-8", ('"' + $Drive8Image + '"')) | |||
| } | |||
| $argList += @("-autostart", ('"' + $Autostart + '"'), "-autostart-warp", "-warp", | |||
| "-sounddev", "dummy", "-remotemonitor", | |||
| "-remotemonitoraddress", "ip4://127.0.0.1:$Port") | |||
| if ($Screenshot) { | |||
| if (Test-Path -LiteralPath $Screenshot) { Remove-Item -LiteralPath $Screenshot -Force } | |||
| $argList += @("-exitscreenshotvicii", ('"' + $Screenshot + '"')) | |||
| } | |||
| $psi = [Diagnostics.ProcessStartInfo]::new() | |||
| $psi.FileName = $x128 | |||
| $psi.Arguments = $argList -join ' ' | |||
| $psi.UseShellExecute = $true | |||
| $psi.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden | |||
| $process = [Diagnostics.Process]::Start($psi) | |||
| $client = $null | |||
| $deadline = [DateTime]::UtcNow.AddSeconds(10) | |||
| while ([DateTime]::UtcNow -lt $deadline) { | |||
| if ($process.HasExited) { throw "x128 exited before the remote monitor connected" } | |||
| try { | |||
| $client = [Net.Sockets.TcpClient]::new() | |||
| $client.Connect("127.0.0.1", $Port) | |||
| break | |||
| } catch { | |||
| if ($client) { $client.Dispose(); $client = $null } | |||
| Start-Sleep -Milliseconds 200 | |||
| } | |||
| } | |||
| if (-not $client) { $process.Kill(); throw "could not connect to the VICE remote monitor" } | |||
| $stream = $client.GetStream() | |||
| $writer = [IO.StreamWriter]::new($stream, [Text.Encoding]::ASCII, 1024, $true) | |||
| $writer.NewLine = "`n" | |||
| $writer.AutoFlush = $true | |||
| return [pscustomobject]@{ Process = $process; Client = $client; Stream = $stream; Writer = $writer } | |||
| } | |||
| function Invoke-Nova128Monitor { | |||
| param([Parameter(Mandatory)]$Session, [Parameter(Mandatory)][string]$Command, | |||
| [int]$TimeoutMs = 5000) | |||
| $Session.Writer.WriteLine($Command) | |||
| $buffer = New-Object byte[] 65536 | |||
| $text = "" | |||
| # Every monitor reply ends with a prompt such as "(C:$1c0d) ". | |||
| $deadline = [DateTime]::UtcNow.AddMilliseconds($TimeoutMs) | |||
| while ([DateTime]::UtcNow -lt $deadline) { | |||
| if ($Session.Stream.DataAvailable) { | |||
| $n = $Session.Stream.Read($buffer, 0, $buffer.Length) | |||
| if ($n -le 0) { break } | |||
| $text += [Text.Encoding]::ASCII.GetString($buffer, 0, $n) | |||
| if ($text -match '\(C:\$[0-9a-f]{4}\)\s*$') { break } | |||
| } else { | |||
| Start-Sleep -Milliseconds 50 | |||
| } | |||
| } | |||
| return $text | |||
| } | |||
| # Read memory through the monitor. Returns byte[] (length = Count). | |||
| function Read-Nova128Memory { | |||
| param([Parameter(Mandatory)]$Session, [int]$Address, [int]$Count) | |||
| $text = Invoke-Nova128Monitor $Session ("m `${0:X4} `${1:X4}" -f $Address, ($Address + $Count - 1)) | |||
| $memory = @{} | |||
| foreach ($row in [regex]::Matches($text, "(?i)C:([0-9a-f]{4})((?:[ ]{1,2}[0-9a-f]{2}){1,16})")) { | |||
| $a = [Convert]::ToInt32($row.Groups[1].Value, 16) | |||
| foreach ($hex in ($row.Groups[2].Value.Trim() -split '\s+')) { | |||
| $memory[$a] = [Convert]::ToByte($hex, 16); $a++ | |||
| } | |||
| } | |||
| $bytes = New-Object byte[] $Count | |||
| for ($i = 0; $i -lt $Count; $i++) { | |||
| if (-not $memory.ContainsKey($Address + $i)) { | |||
| throw ("monitor did not return `${0:X4}; response: {1}" -f ($Address + $i), $text) | |||
| } | |||
| $bytes[$i] = $memory[$Address + $i] | |||
| } | |||
| return ,$bytes | |||
| } | |||
| # Poll the test mailbox ($1BF0) until it reports PASS or FAIL. Returns its | |||
| # 16 bytes. Resumes emulation between polls. | |||
| function Wait-Nova128Mailbox { | |||
| param([Parameter(Mandatory)]$Session, [int]$TimeoutSeconds = 60) | |||
| $deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds) | |||
| while ($true) { | |||
| Start-Sleep -Seconds 1 | |||
| $mb = Read-Nova128Memory $Session 0x1BF0 16 | |||
| if ($mb[0] -eq 0x4E -and $mb[1] -eq 0x38 -and ($mb[3] -eq 0xA5 -or $mb[3] -eq 0xFA)) { | |||
| return ,$mb | |||
| } | |||
| if ([DateTime]::UtcNow -ge $deadline) { | |||
| $state = if ($mb[0] -eq 0x4E) { "status `$" + $mb[3].ToString("X2") } else { "never initialized" } | |||
| throw "mailbox did not finish within $TimeoutSeconds s ($state)" | |||
| } | |||
| Invoke-Nova128Monitor $Session "x" | Out-Null | |||
| } | |||
| } | |||
| # Let the program finish printing, then quit x128 (writes any exit screenshot). | |||
| function Stop-Nova128X128 { | |||
| param([Parameter(Mandatory)]$Session, [int]$SettleSeconds = 2) | |||
| try { | |||
| if ($SettleSeconds -gt 0) { | |||
| Invoke-Nova128Monitor $Session "x" | Out-Null | |||
| Start-Sleep -Seconds $SettleSeconds | |||
| Invoke-Nova128Monitor $Session "m 0000 0000" | Out-Null | |||
| } | |||
| $Session.Writer.WriteLine("quit") | |||
| $Session.Writer.Dispose() | |||
| $Session.Client.Dispose() | |||
| if (-not $Session.Process.WaitForExit(10000)) { throw "x128 did not exit" } | |||
| } finally { | |||
| if (-not $Session.Process.HasExited) { $Session.Process.Kill(); $Session.Process.WaitForExit() } | |||
| } | |||
| } | |||
| Export-ModuleMember -Function Get-ViceTool, Invoke-Nova128Link, Get-Nova128Label, | |||
| ConvertTo-Nova128Petscii, New-Nova128Disk, Start-Nova128X128, Invoke-Nova128Monitor, | |||
| Read-Nova128Memory, Wait-Nova128Mailbox, Stop-Nova128X128 | |||
| @@ -0,0 +1,64 @@ | |||
| # E02-S01: run the source-reader test in x128 (1581 true drive) and compare | |||
| # every result record with the host-computed expectations. | |||
| [CmdletBinding()] | |||
| param([int]$Port = 6531, [int]$TimeoutSeconds = 180) | |||
| $ErrorActionPreference = "Stop" | |||
| $project = Split-Path $PSScriptRoot -Parent | |||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||
| & (Join-Path $PSScriptRoot "build-source-test.ps1") | |||
| $out = Join-Path $project "build\source-test" | |||
| $disk = Join-Path $out "source-test.d81" | |||
| $screenshot = Join-Path $out "source-test-vicii.png" | |||
| $expected = Get-Content -Raw (Join-Path $out "expected.json") | ConvertFrom-Json | |||
| $session = Start-Nova128X128 -Autostart $disk -Drive8Image $disk -Port $Port -Screenshot $screenshot | |||
| try { | |||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||
| $count = $mb[8] | |||
| $table = $mb[9] + 256 * $mb[10] | |||
| if ($count -ne $expected.Count) { throw "program ran $count fixtures, host expects $($expected.Count)" } | |||
| $raw = Read-Nova128Memory $session $table (16 * $count) | |||
| } finally { | |||
| Stop-Nova128X128 $session | |||
| } | |||
| $failures = @() | |||
| for ($i = 0; $i -lt $count; $i++) { | |||
| $r = $raw[(16 * $i)..(16 * $i + 15)] | |||
| $e = $expected[$i] | |||
| $got = [ordered]@{ | |||
| code = $r[0]; dos = $r[1]; kernal = $r[2]; line = $r[3] + 256 * $r[4]; col = $r[5] | |||
| ldtnd = $r[6]; lines = $r[7] + 256 * $r[8]; len = $r[9] + 256 * $r[10] | |||
| s1 = $r[11]; s2 = $r[12]; flag = $r[13]; lastline = $r[14] + 256 * $r[15] | |||
| } | |||
| $problems = @() | |||
| if ($got.code -ne $e.code) { $problems += "code $($got.code) expected $($e.code)" } | |||
| if ($got.ldtnd -ne 0) { $problems += "$($got.ldtnd) file(s) left open" } | |||
| if ($got.flag -ne 1) { | |||
| $problems += $(if ($e.code -eq 0) { "passes differ" } else { "source delivered after failure" }) | |||
| } | |||
| if ($e.code -eq 0) { | |||
| foreach ($k in "lines", "len", "s1", "s2") { | |||
| if ($got[$k] -ne $e.$k) { $problems += "$k $($got[$k]) expected $($e.$k)" } | |||
| } | |||
| if ($got.lastline -ne $e.lines) { $problems += "src_line_no $($got.lastline) expected $($e.lines)" } | |||
| $detail = "{0} lines, {1} bytes, fletcher {2:X2}{3:X2}" -f $got.lines, $got.len, $got.s1, $got.s2 | |||
| } else { | |||
| if ($got.len -ne 0) { $problems += "src_len $($got.len) after failure" } | |||
| if ($null -ne $e.line -and ($got.line -ne $e.line -or $got.col -ne $e.col)) { | |||
| $problems += "position $($got.line):$($got.col) expected $($e.line):$($e.col)" | |||
| } | |||
| if ($null -ne $e.dos -and $got.dos -ne $e.dos) { $problems += "DOS $($got.dos) expected $($e.dos)" } | |||
| $detail = if ($e.code -eq 1) { "DOS {0}" -f $got.dos } else { "at {0}:{1}" -f $got.line, $got.col } | |||
| } | |||
| $status = if ($problems) { "FAIL" } else { "ok" } | |||
| Write-Host (" {0,-11} code {1} {2,-36} {3}" -f $e.name, $got.code, $detail, $status) | |||
| if ($problems) { $failures += "$($e.name): " + ($problems -join "; ") } | |||
| } | |||
| if ($mb[3] -ne 0xA5) { $failures += ("C128 reported FAIL ({0} fixture(s))" -f $mb[4]) } | |||
| if ($failures) { throw ("source-reader verification failed:`n " + ($failures -join "`n ")) } | |||
| Write-Host "verified source reader: $count fixtures match host expectations" | |||
| Write-Host "screenshot: $screenshot" | |||
| @@ -0,0 +1,12 @@ | |||
| ; diskio.inc - logical file numbers and device used by src/compiler/diskio.s. | |||
| .ifndef DISKIO_INC | |||
| DISKIO_INC = 1 | |||
| DIO_DEVICE = 8 | |||
| DIO_LFN_CMD = 15 ; command/status channel: opened first, closed last | |||
| DIO_LFN_IN = 2 ; source input | |||
| DIO_SA_IN = 2 | |||
| DIO_STATUS_MAX = 40 ; bytes kept from a drive status line | |||
| .endif | |||
| @@ -0,0 +1,20 @@ | |||
| ; source.inc - source-reader limits and result codes (E02-S01). | |||
| ; | |||
| ; These are the reader's return codes. They are stable: E02-S04 maps each to | |||
| ; a user-facing Exxx diagnostic without renumbering them. | |||
| .ifndef SOURCE_INC | |||
| SOURCE_INC = 1 | |||
| SRC_MAX_LINE_BYTES = 80 ; logical line, excluding the $0D | |||
| SRC_MAX_FILE_BYTES = 18999 ; whole file, including every $0D | |||
| SRC_MAX_LINES = 599 | |||
| SRC_EOL = $0D | |||
| SRC_OK = 0 | |||
| SRC_ERR_DISK = 1 ; src_kernal_err / src_dos_code say why | |||
| SRC_ERR_FILE_TOO_LARGE = 2 ; position of byte 19,000 | |||
| SRC_ERR_LINE_TOO_LONG = 3 ; position of the 81st byte of the line | |||
| SRC_ERR_TOO_MANY_LINES = 4 ; line 600, column 1 | |||
| .endif | |||
| @@ -0,0 +1,168 @@ | |||
| ; diskio.s - KERNAL file and command-channel wrappers for the compiler. | |||
| ; | |||
| ; All routines are called from the bank-0 compiler state and reach the KERNAL | |||
| ; only through kcall_inline (mmu.s). They return C=1 on a KERNAL error with | |||
| ; A = the KERNAL error number. Callers own the open/close ordering: open the | |||
| ; command channel first, close it last (docs/architecture.md section 9). | |||
| .setcpu "6502" | |||
| .include "../common/c128.inc" | |||
| .include "../common/diskio.inc" | |||
| .import kcall_inline | |||
| .export dio_open_cmd, dio_open_in, dio_chkin_in, dio_read_status | |||
| .export dio_read_chunk, dio_clrchn, dio_close | |||
| .export dio_count, dio_eof, dio_st, dio_dos_code, dio_status | |||
| .import __DISKBUF_START__ | |||
| ; ------------------------------------------------------------------ CODE | |||
| .segment "CODE" | |||
| ; OPEN 15,8,15 with no name. Nothing is sent on the bus until it is read. | |||
| dio_open_cmd: | |||
| jsr dio_setbank | |||
| lda #DIO_LFN_CMD | |||
| ldx #DIO_DEVICE | |||
| ldy #15 | |||
| jsr kcall_inline | |||
| .word SETLFS | |||
| lda #$00 | |||
| jsr kcall_inline | |||
| .word SETNAM | |||
| jsr kcall_inline | |||
| .word OPEN | |||
| rts | |||
| ; OPEN 2,8,2,name. In: A/X = name address (bank 0), Y = name length. | |||
| dio_open_in: | |||
| sta dio_tmp | |||
| stx dio_tmp+1 | |||
| sty dio_tmp+2 | |||
| jsr dio_setbank | |||
| lda dio_tmp+2 ; SETNAM: A=len, X/Y=address | |||
| ldx dio_tmp | |||
| ldy dio_tmp+1 | |||
| jsr kcall_inline | |||
| .word SETNAM | |||
| lda #DIO_LFN_IN | |||
| ldx #DIO_DEVICE | |||
| ldy #DIO_SA_IN | |||
| jsr kcall_inline | |||
| .word SETLFS | |||
| jsr kcall_inline | |||
| .word OPEN | |||
| rts | |||
| dio_chkin_in: | |||
| ldx #DIO_LFN_IN | |||
| jsr kcall_inline | |||
| .word CHKIN | |||
| rts | |||
| ; Read the drive status line from channel 15 into dio_status (CR ended) and | |||
| ; its leading number into dio_dos_code. Leaves the default channels selected. | |||
| dio_read_status: | |||
| lda #$FF | |||
| sta dio_dos_code | |||
| ldx #DIO_LFN_CMD | |||
| jsr kcall_inline | |||
| .word CHKIN | |||
| bcs @out | |||
| ldx #$00 | |||
| @read: stx dio_tmp | |||
| jsr kcall_inline | |||
| .word CHRIN | |||
| ldx dio_tmp | |||
| sta dio_status,x | |||
| cmp #$0D | |||
| beq @parse | |||
| inx | |||
| cpx #DIO_STATUS_MAX - 1 | |||
| bcc @read | |||
| lda #$0D | |||
| sta dio_status,x | |||
| @parse: lda dio_status ; two decimal digits -> binary | |||
| sec | |||
| sbc #'0' | |||
| cmp #10 | |||
| bcs @bad | |||
| sta dio_tmp | |||
| asl a ; *10 = *8 + *2 | |||
| asl a | |||
| adc dio_tmp | |||
| asl a | |||
| sta dio_tmp | |||
| lda dio_status+1 | |||
| sec | |||
| sbc #'0' | |||
| cmp #10 | |||
| bcs @bad | |||
| adc dio_tmp | |||
| sta dio_dos_code | |||
| @bad: jsr dio_clrchn | |||
| clc | |||
| @out: rts | |||
| ; Read up to 256 bytes from the current input channel into DISKBUF. | |||
| ; Out: dio_count (0 = 256), dio_eof ($40 at end of file), dio_st (ST with | |||
| ; the EOI bit removed; non-zero means a read error). | |||
| dio_read_chunk: | |||
| jsr kcall_inline | |||
| .word dio_chunk_sys | |||
| rts | |||
| dio_clrchn: | |||
| jsr kcall_inline | |||
| .word CLRCHN | |||
| rts | |||
| ; CLOSE the logical file in A. | |||
| dio_close: | |||
| jsr kcall_inline | |||
| .word CLOSE | |||
| rts | |||
| dio_setbank: | |||
| lda #$00 ; data and filename in bank 0 | |||
| tax | |||
| jsr kcall_inline | |||
| .word SETBNK | |||
| rts | |||
| ; ---------------------------------------------------------------- COMMON | |||
| ; Runs in the system state through kcall (system zero page, IRQs enabled), | |||
| ; so it uses only absolute variables. | |||
| .segment "COMMON" | |||
| dio_chunk_sys: | |||
| lda #$00 | |||
| sta dio_count | |||
| sta dio_eof | |||
| sta dio_st | |||
| @loop: jsr CHRIN | |||
| ldx dio_count | |||
| sta __DISKBUF_START__,x | |||
| inc dio_count | |||
| jsr READST | |||
| beq @more | |||
| tax | |||
| and #$BF ; everything except EOI is an error | |||
| sta dio_st | |||
| txa | |||
| and #$40 | |||
| sta dio_eof | |||
| rts | |||
| @more: lda dio_count | |||
| bne @loop ; stop after 256 bytes (count wraps to 0) | |||
| rts | |||
| dio_count: .byte $00 | |||
| dio_eof: .byte $00 | |||
| dio_st: .byte $00 | |||
| ; ------------------------------------------------------------------- BSS | |||
| .segment "BSS" | |||
| dio_dos_code: .res 1 | |||
| dio_tmp: .res 3 | |||
| dio_status: .res DIO_STATUS_MAX | |||
| @@ -28,13 +28,14 @@ | |||
| .export mmu_init, mmu_bank0, mmu_bank1, mmu_system, mmu_shutdown | |||
| .export kcall, kcall_inline, kc_target | |||
| .export mmu_nmi_guard, mmu_nmi_count | |||
| .export mmu_peek1, mmu_poke1 | |||
| .exportzp mmu_ptr | |||
| .export mmu_peek1, mmu_poke1, mmu_copy_to1 | |||
| .exportzp mmu_ptr, mmu_src | |||
| .export mmu_sys_cr, mmu_sys_rcr, mmu_sys_p0l, mmu_sys_p0h | |||
| .export mmu_sys_p1l, mmu_sys_p1h, mmu_sys_nmi, mmu_comp_rcr | |||
| .segment "ZEROPAGE" | |||
| mmu_ptr: .res 2 ; bank-1 access pointer (private page) | |||
| mmu_src: .res 2 ; common-RAM source for mmu_copy_to1 | |||
| ; ------------------------------------------------------------------ CODE | |||
| .segment "CODE" | |||
| @@ -229,6 +230,19 @@ mmu_poke1: | |||
| sta (mmu_ptr),y | |||
| jmp mmu_bank0 | |||
| ; Copy X bytes (X=0 means 256) from (mmu_src) to bank1[(mmu_ptr)]. The | |||
| ; source must be visible from bank 1: common RAM or $0000-$1FFF. Call from | |||
| ; the bank-0 state; returns in it. Clobbers A/X/Y; pointers unchanged. | |||
| mmu_copy_to1: | |||
| jsr mmu_bank1 | |||
| ldy #$00 | |||
| @copy: lda (mmu_src),y | |||
| sta (mmu_ptr),y | |||
| iny | |||
| dex | |||
| bne @copy | |||
| jmp mmu_bank0 | |||
| ; Compact form: JSR kcall_inline / .word target. Preserves A/X/Y in, then | |||
| ; behaves exactly like kcall. | |||
| kcall_inline: | |||
| @@ -0,0 +1,341 @@ | |||
| ; source.s - buffered PETSCII source reader (E02-S01). | |||
| ; | |||
| ; src_load reads one SEQ file from device 8 into the bank-1 source cache | |||
| ; (SOURCE segment, D118), checking the editor-profile limits as bytes arrive. | |||
| ; Every pass then calls src_rewind and src_next_line, which replay the cache | |||
| ; one logical line at a time into src_line (common RAM). Bytes are not | |||
| ; translated: PETSCII in, PETSCII out. Only the line terminator is consumed. | |||
| ; | |||
| ; Lines and columns are 1-based. A file that ends without $0D still has its | |||
| ; last line. All routines run in the bank-0 compiler state. | |||
| .setcpu "6502" | |||
| .include "../common/source.inc" | |||
| .include "../common/diskio.inc" | |||
| .import mmu_copy_to1, mmu_bank0, mmu_bank1 | |||
| .importzp mmu_ptr, mmu_src | |||
| .import dio_open_cmd, dio_open_in, dio_chkin_in, dio_read_status | |||
| .import dio_read_chunk, dio_clrchn, dio_close | |||
| .import dio_count, dio_eof, dio_st, dio_dos_code | |||
| .import __DISKBUF_START__ | |||
| .export src_load, src_rewind, src_next_line | |||
| .export src_line, src_line_len, src_line_no | |||
| .export src_valid, src_len, src_lines | |||
| .export src_err_line, src_err_col, src_kernal_err, src_dos_code | |||
| ; ------------------------------------------------------------------ CODE | |||
| .segment "CODE" | |||
| ; Load a source file. | |||
| ; in: A/X = filename address (bank 0), Y = filename length | |||
| ; out: C=0, A=SRC_OK, src_len/src_lines set, src_valid=1 | |||
| ; C=1, A=SRC_ERR_*, src_err_line/col (limit errors) or | |||
| ; src_kernal_err/src_dos_code (disk errors); src_valid=0, src_len=0 | |||
| ; Channels 2 and 15 are closed on every path. | |||
| src_load: | |||
| sta name_ptr | |||
| stx name_ptr+1 | |||
| sty name_len | |||
| lda #$00 | |||
| ldx #state_end - state_start - 1 | |||
| @clr: sta state_start,x | |||
| dex | |||
| bpl @clr | |||
| jsr open_source | |||
| bcc @chunk | |||
| cmp #$00 ; A=0: command channel never opened | |||
| beq @fail_closed_disk | |||
| lda #SRC_ERR_DISK | |||
| bne @fail | |||
| @chunk: jsr dio_read_chunk | |||
| jsr validate_chunk | |||
| bcs @limit_err | |||
| lda #<__DISKBUF_START__ ; append the chunk to the bank-1 cache | |||
| sta mmu_src | |||
| lda #>__DISKBUF_START__ | |||
| sta mmu_src+1 | |||
| clc | |||
| lda #<src_cache | |||
| adc src_len | |||
| sta mmu_ptr | |||
| lda #>src_cache | |||
| adc src_len+1 | |||
| sta mmu_ptr+1 | |||
| ldx dio_count | |||
| jsr mmu_copy_to1 | |||
| lda dio_count ; src_len += count (0 means 256) | |||
| bne @add | |||
| inc src_len+1 | |||
| bne @added | |||
| @add: clc | |||
| adc src_len | |||
| sta src_len | |||
| bcc @added | |||
| inc src_len+1 | |||
| @added: lda dio_st | |||
| bne @read_err | |||
| lda dio_eof | |||
| beq @chunk | |||
| jsr close_all | |||
| lda v_lines | |||
| sta src_lines | |||
| lda v_lines+1 | |||
| sta src_lines+1 | |||
| lda #$01 | |||
| sta src_valid | |||
| jsr src_rewind | |||
| lda #SRC_OK | |||
| clc | |||
| rts | |||
| @read_err: ; ST error mid-file: ask the drive why | |||
| jsr dio_clrchn | |||
| jsr dio_read_status | |||
| lda dio_dos_code | |||
| sta src_dos_code | |||
| lda #SRC_ERR_DISK | |||
| bne @fail | |||
| @fail_closed_disk: | |||
| lda #SRC_ERR_DISK | |||
| bne @fail_closed | |||
| @limit_err: ; A = SRC_ERR_* from validate_chunk | |||
| @fail: pha | |||
| jsr close_all | |||
| pla | |||
| @fail_closed: | |||
| sta src_err | |||
| lda #$00 ; no partial source survives a failure | |||
| sta src_valid | |||
| sta src_len | |||
| sta src_len+1 | |||
| sta src_lines | |||
| sta src_lines+1 | |||
| jsr src_rewind | |||
| lda src_err | |||
| sec | |||
| rts | |||
| ; Open channel 15, then the source on channel 2, and select it for input. | |||
| ; C=0 on success. C=1 on failure with src_kernal_err/src_dos_code set and | |||
| ; A=0 if channel 15 could not be opened (nothing to close), else A=1. | |||
| open_source: | |||
| jsr dio_open_cmd | |||
| bcc @cmd_open | |||
| sta src_kernal_err | |||
| lda #DIO_LFN_CMD ; OPEN may have entered the file table | |||
| jsr dio_close | |||
| lda #$00 | |||
| sec | |||
| rts | |||
| @cmd_open: | |||
| lda name_ptr | |||
| ldx name_ptr+1 | |||
| ldy name_len | |||
| jsr dio_open_in | |||
| bcs @kernal | |||
| jsr dio_read_status ; e.g. 62,FILE NOT FOUND | |||
| bcs @kernal | |||
| lda dio_dos_code | |||
| sta src_dos_code | |||
| cmp #20 | |||
| bcs @failed | |||
| jsr dio_chkin_in | |||
| bcc @ok | |||
| @kernal: | |||
| sta src_kernal_err | |||
| @failed: | |||
| lda #$01 | |||
| sec | |||
| @ok: rts | |||
| close_all: | |||
| jsr dio_clrchn | |||
| lda #DIO_LFN_IN | |||
| jsr dio_close | |||
| lda #DIO_LFN_CMD ; command channel last | |||
| jmp dio_close | |||
| ; Check every byte of the chunk in DISKBUF against the limits. | |||
| ; C=1 with A=SRC_ERR_* and src_err_line/col on the first violation. | |||
| validate_chunk: | |||
| ldx #$00 | |||
| @byte: lda v_col | |||
| bne @in_line | |||
| inc v_lines ; first byte of a new line | |||
| bne :+ | |||
| inc v_lines+1 | |||
| : lda v_lines+1 | |||
| cmp #>(SRC_MAX_LINES + 1) | |||
| bcc @lines_ok | |||
| bne @too_many | |||
| lda v_lines | |||
| cmp #<(SRC_MAX_LINES + 1) | |||
| bcc @lines_ok | |||
| @too_many: | |||
| lda #$01 | |||
| sta src_err_col | |||
| lda #SRC_ERR_TOO_MANY_LINES | |||
| bne @error | |||
| @lines_ok: | |||
| @in_line: | |||
| inc v_total | |||
| bne :+ | |||
| inc v_total+1 | |||
| : lda v_total+1 | |||
| cmp #>(SRC_MAX_FILE_BYTES + 1) | |||
| bcc @size_ok | |||
| bne @too_big | |||
| lda v_total | |||
| cmp #<(SRC_MAX_FILE_BYTES + 1) | |||
| bcc @size_ok | |||
| @too_big: | |||
| ldy v_col | |||
| iny | |||
| sty src_err_col | |||
| lda #SRC_ERR_FILE_TOO_LARGE | |||
| bne @error | |||
| @size_ok: | |||
| lda __DISKBUF_START__,x | |||
| cmp #SRC_EOL | |||
| bne @not_eol | |||
| lda #$00 | |||
| sta v_col | |||
| beq @next | |||
| @not_eol: | |||
| inc v_col | |||
| lda v_col | |||
| cmp #SRC_MAX_LINE_BYTES + 1 | |||
| bcc @next | |||
| sta src_err_col | |||
| lda #SRC_ERR_LINE_TOO_LONG | |||
| bne @error | |||
| @next: inx | |||
| cpx dio_count ; dio_count 0 = 256: stops when X wraps | |||
| bne @byte | |||
| clc | |||
| rts | |||
| @error: pha | |||
| lda v_lines | |||
| sta src_err_line | |||
| lda v_lines+1 | |||
| sta src_err_line+1 | |||
| pla | |||
| sec | |||
| rts | |||
| ; Restart line delivery at the first line. Valid after any src_load. | |||
| src_rewind: | |||
| lda #$00 | |||
| sta src_pos | |||
| sta src_pos+1 | |||
| sta src_line_no | |||
| sta src_line_no+1 | |||
| sta src_line_len | |||
| rts | |||
| ; Deliver the next logical line. | |||
| ; out: C=0: src_line = bytes without $0D, src_line_len, src_line_no | |||
| ; C=1: end of source (or no valid source loaded) | |||
| src_next_line: | |||
| lda src_valid | |||
| beq @eof | |||
| sec ; remaining = src_len - src_pos | |||
| lda src_len | |||
| sbc src_pos | |||
| sta remaining | |||
| lda src_len+1 | |||
| sbc src_pos+1 | |||
| sta remaining+1 | |||
| ora remaining | |||
| beq @eof | |||
| lda remaining+1 ; fetch at most one line plus its $0D | |||
| bne @cap | |||
| lda remaining | |||
| cmp #SRC_MAX_LINE_BYTES + 1 | |||
| bcc @limit | |||
| @cap: lda #SRC_MAX_LINE_BYTES + 1 | |||
| @limit: sta fetch_limit | |||
| clc | |||
| lda #<src_cache | |||
| adc src_pos | |||
| sta mmu_ptr | |||
| lda #>src_cache | |||
| adc src_pos+1 | |||
| sta mmu_ptr+1 | |||
| jsr src_fetch_line ; Y = content length, X = bytes consumed | |||
| sty src_line_len | |||
| txa | |||
| clc | |||
| adc src_pos | |||
| sta src_pos | |||
| bcc :+ | |||
| inc src_pos+1 | |||
| : inc src_line_no | |||
| bne :+ | |||
| inc src_line_no+1 | |||
| : clc | |||
| rts | |||
| @eof: sec | |||
| rts | |||
| ; ---------------------------------------------------------------- COMMON | |||
| ; Runs with bank 1 mapped, so it lives in common RAM. | |||
| .segment "COMMON" | |||
| src_fetch_line: | |||
| jsr mmu_bank1 | |||
| ldy #$00 | |||
| @copy: cpy fetch_limit | |||
| beq @no_eol ; last line without a final $0D | |||
| lda (mmu_ptr),y | |||
| cmp #SRC_EOL | |||
| beq @eol | |||
| sta src_line,y | |||
| iny | |||
| bne @copy | |||
| @eol: tya | |||
| tax | |||
| inx ; the $0D is consumed too | |||
| jmp mmu_bank0 | |||
| @no_eol: | |||
| tya | |||
| tax | |||
| jmp mmu_bank0 | |||
| fetch_limit: .byte $00 | |||
| ; ---------------------------------------------------------------- SOURCE | |||
| ; Bank-1 cache for the whole file. Never touched directly from bank 0. | |||
| .segment "SOURCE" | |||
| src_cache: .res SRC_MAX_FILE_BYTES | |||
| ; --------------------------------------------------------------- SCRATCH | |||
| .segment "SCRATCH" | |||
| src_line: .res SRC_MAX_LINE_BYTES | |||
| ; ------------------------------------------------------------------- BSS | |||
| .segment "BSS" | |||
| name_ptr: .res 2 | |||
| name_len: .res 1 | |||
| remaining: .res 2 | |||
| src_line_len: .res 1 | |||
| src_line_no: .res 2 | |||
| src_pos: .res 2 | |||
| state_start: ; cleared at the start of every src_load | |||
| src_valid: .res 1 | |||
| src_len: .res 2 | |||
| src_lines: .res 2 | |||
| src_err: .res 1 | |||
| src_err_line: .res 2 | |||
| src_err_col: .res 1 | |||
| src_kernal_err: .res 1 | |||
| src_dos_code: .res 1 | |||
| v_col: .res 1 | |||
| v_lines: .res 2 | |||
| v_total: .res 2 | |||
| state_end: | |||
| @@ -70,7 +70,7 @@ swstack_last: .res 1 | |||
| .segment "TABLES" | |||
| tables_first: .res 1 | |||
| .res $A000 - 2 | |||
| .res $5500 - 2 | |||
| tables_last: .res 1 | |||
| .ifdef OVERFLOW_TABLES | |||
| .res 1 | |||
| @@ -0,0 +1,346 @@ | |||
| ; E02-S01: source-reader test in x128. | |||
| ; | |||
| ; Loads each fixture from drive 8 with src_load. For a successful load it | |||
| ; replays the source twice (src_rewind + src_next_line) and records the line | |||
| ; count and a Fletcher-16 checksum of the delivered lines, each terminated by | |||
| ; $0D. For a failed load it records the error and checks that no source | |||
| ; survives. Every record is 16 bytes in `results`: | |||
| ; +0 result code +1 DOS status code +2 KERNAL error | |||
| ; +3 error line (2) +5 error column +6 LDTND after src_load | |||
| ; +7 lines (2) +9 src_len (2) +11 Fletcher s1, +12 s2 | |||
| ; +13 flag: OK -> both passes identical; error -> next_line gave EOF | |||
| ; +14 src_line_no after the last line (2) | |||
| ; Mailbox: PASS when every result code matches its expectation and every | |||
| ; flag is 1; MB_DATA+0 = fixture count, MB_DATA+1/2 = address of results. | |||
| ; The host checks every other field against values it computes itself. | |||
| .setcpu "6502" | |||
| .include "../../src/common/c128.inc" | |||
| .include "../../src/common/mailbox.inc" | |||
| .include "../../src/common/source.inc" | |||
| .import mmu_init, mmu_bank0, mmu_shutdown, kcall_inline | |||
| .import src_load, src_rewind, src_next_line | |||
| .import src_line, src_line_len, src_line_no, src_len | |||
| .import src_err_line, src_err_col, src_kernal_err, src_dos_code | |||
| .import __COMPILER_ZP_PAGE__ | |||
| REC_SIZE = 16 | |||
| .segment "LOADADDR" | |||
| .word $1C01 | |||
| .segment "EXEHDR" | |||
| .word basic_end | |||
| .word 10 | |||
| .byte $9E | |||
| .byte "7181" | |||
| .byte $00 | |||
| basic_end: | |||
| .word $0000 | |||
| .segment "MAILBOX" | |||
| .res MB_SIZE | |||
| .segment "ZEROPAGE" | |||
| fx_ptr: .res 2 ; current fixture table entry | |||
| rec_ptr: .res 2 ; current result record | |||
| .segment "STARTUP" | |||
| .export test_entry, results | |||
| test_entry: | |||
| php | |||
| sei | |||
| ldx #MB_SIZE - 1 | |||
| lda #$00 | |||
| @clr: sta MB_BASE,x | |||
| dex | |||
| bpl @clr | |||
| lda #MB_MAGIC0_VALUE | |||
| sta MB_MAGIC0 | |||
| lda #MB_MAGIC1_VALUE | |||
| sta MB_MAGIC1 | |||
| lda #MB_PROD_PLATFORM | |||
| sta MB_PRODUCER | |||
| lda #MB_ST_RUNNING | |||
| sta MB_STATUS | |||
| jsr mmu_init | |||
| jsr mmu_bank0 | |||
| lda #$00 | |||
| sta failures | |||
| sta fx_index | |||
| lda #<fixtures | |||
| sta fx_ptr | |||
| lda #>fixtures | |||
| sta fx_ptr+1 | |||
| lda #<results | |||
| sta rec_ptr | |||
| lda #>results | |||
| sta rec_ptr+1 | |||
| @next: ldy #0 ; entry: name lo, name hi, length, expected | |||
| lda (fx_ptr),y | |||
| sta name_lo | |||
| iny | |||
| lda (fx_ptr),y | |||
| sta name_hi | |||
| iny | |||
| lda (fx_ptr),y | |||
| sta name_len | |||
| iny | |||
| lda (fx_ptr),y | |||
| sta expected | |||
| jsr run_fixture | |||
| clc | |||
| lda fx_ptr | |||
| adc #4 | |||
| sta fx_ptr | |||
| bcc :+ | |||
| inc fx_ptr+1 | |||
| : clc | |||
| lda rec_ptr | |||
| adc #REC_SIZE | |||
| sta rec_ptr | |||
| bcc :+ | |||
| inc rec_ptr+1 | |||
| : inc fx_index | |||
| lda fx_index | |||
| cmp #FIXTURE_COUNT | |||
| bne @next | |||
| jsr mmu_shutdown | |||
| lda #FIXTURE_COUNT | |||
| sta MB_DATA+0 | |||
| lda #<results | |||
| sta MB_DATA+1 | |||
| lda #>results | |||
| sta MB_DATA+2 | |||
| lda failures | |||
| sta MB_CODE | |||
| ldy #ok_msg - messages | |||
| lda failures | |||
| bne @fail | |||
| lda #MB_ST_PASS | |||
| bne @report | |||
| @fail: ldy #fail_msg - messages | |||
| lda #MB_ST_FAIL | |||
| @report: | |||
| sta MB_STATUS | |||
| plp | |||
| @print: lda messages,y | |||
| beq @end | |||
| sty print_y | |||
| jsr CHROUT | |||
| ldy print_y | |||
| iny | |||
| bne @print | |||
| @end: rts | |||
| .segment "CODE" | |||
| ; Load one fixture and fill its record (rec_ptr). | |||
| run_fixture: | |||
| ldx #REC_SIZE - 1 | |||
| lda #$00 | |||
| @zero: sta record,x | |||
| dex | |||
| bpl @zero | |||
| jsr show_name | |||
| lda name_lo | |||
| ldx name_hi | |||
| ldy name_len | |||
| jsr src_load | |||
| sta record+0 | |||
| lda src_dos_code | |||
| sta record+1 | |||
| lda src_kernal_err | |||
| sta record+2 | |||
| lda src_err_line | |||
| sta record+3 | |||
| lda src_err_line+1 | |||
| sta record+4 | |||
| lda src_err_col | |||
| sta record+5 | |||
| lda __COMPILER_ZP_PAGE__ * 256 + LDTND ; system ZP via the swap | |||
| sta record+6 | |||
| lda src_len | |||
| sta record+9 | |||
| lda src_len+1 | |||
| sta record+10 | |||
| lda record+0 | |||
| bne @failed | |||
| jsr replay ; pass A | |||
| lda count | |||
| sta record+7 | |||
| lda count+1 | |||
| sta record+8 | |||
| lda s1 | |||
| sta record+11 | |||
| lda s2 | |||
| sta record+12 | |||
| lda src_line_no | |||
| sta record+14 | |||
| lda src_line_no+1 | |||
| sta record+15 | |||
| jsr replay ; pass B must match exactly | |||
| lda count | |||
| cmp record+7 | |||
| bne @check | |||
| lda count+1 | |||
| cmp record+8 | |||
| bne @check | |||
| lda s1 | |||
| cmp record+11 | |||
| bne @check | |||
| lda s2 | |||
| cmp record+12 | |||
| bne @check | |||
| inc record+13 | |||
| bne @check | |||
| @failed: | |||
| jsr src_rewind ; nothing may be delivered after a failure | |||
| jsr src_next_line | |||
| bcc @check | |||
| inc record+13 | |||
| @check: lda record+0 | |||
| cmp expected | |||
| bne @bad | |||
| lda record+13 | |||
| cmp #$01 | |||
| beq @store | |||
| @bad: inc failures | |||
| @store: ldy #REC_SIZE - 1 | |||
| @copy: lda record,y | |||
| sta (rec_ptr),y | |||
| dey | |||
| bpl @copy | |||
| lda record+0 | |||
| ora #'0' | |||
| jsr kcall_inline | |||
| .word CHROUT | |||
| lda #$0D | |||
| jsr kcall_inline | |||
| .word CHROUT | |||
| rts | |||
| ; One pass over the source: count lines, Fletcher-16 over line bytes + $0D. | |||
| replay: lda #$00 | |||
| sta count | |||
| sta count+1 | |||
| sta s1 | |||
| sta s2 | |||
| jsr src_rewind | |||
| @line: jsr src_next_line | |||
| bcs @done | |||
| inc count | |||
| bne :+ | |||
| inc count+1 | |||
| : ldy #$00 | |||
| @byte: cpy src_line_len | |||
| beq @eol | |||
| lda src_line,y | |||
| jsr fletcher | |||
| iny | |||
| bne @byte | |||
| @eol: lda #SRC_EOL | |||
| jsr fletcher | |||
| jmp @line | |||
| @done: rts | |||
| ; s1 = (s1 + A) mod 255; s2 = (s2 + s1) mod 255. Preserves Y. | |||
| fletcher: | |||
| clc | |||
| adc s1 | |||
| adc #$00 ; end-around carry: 256 = 1 (mod 255) | |||
| cmp #$FF | |||
| bne :+ | |||
| lda #$00 | |||
| : sta s1 | |||
| clc | |||
| adc s2 | |||
| adc #$00 | |||
| cmp #$FF | |||
| bne :+ | |||
| lda #$00 | |||
| : sta s2 | |||
| rts | |||
| show_name: | |||
| ldy #$00 | |||
| @ch: cpy name_len | |||
| beq @sp | |||
| lda name_lo | |||
| sta @rd+1 | |||
| lda name_hi | |||
| sta @rd+2 | |||
| @rd: lda $FFFF,y ; patched | |||
| sty print_y | |||
| jsr kcall_inline | |||
| .word CHROUT | |||
| ldy print_y | |||
| iny | |||
| bne @ch | |||
| @sp: lda #' ' | |||
| jsr kcall_inline | |||
| .word CHROUT | |||
| rts | |||
| .segment "RODATA" | |||
| ; name address, name length, expected result code | |||
| fixtures: | |||
| .word f_decls | |||
| .byte f_decls_end - f_decls, SRC_OK | |||
| .word f_nofinal | |||
| .byte f_nofinal_end - f_nofinal, SRC_OK | |||
| .word f_edge | |||
| .byte f_edge_end - f_edge, SRC_OK | |||
| .word f_longln | |||
| .byte f_longln_end - f_longln, SRC_ERR_LINE_TOO_LONG | |||
| .word f_manyln | |||
| .byte f_manyln_end - f_manyln, SRC_ERR_TOO_MANY_LINES | |||
| .word f_big | |||
| .byte f_big_end - f_big, SRC_ERR_FILE_TOO_LARGE | |||
| .word f_missing | |||
| .byte f_missing_end - f_missing, SRC_ERR_DISK | |||
| .word f_decls | |||
| .byte f_decls_end - f_decls, SRC_OK ; again, after failures | |||
| FIXTURE_COUNT = (* - fixtures) / 4 | |||
| ; Unshifted PETSCII letters ($41-$5A) are what c1541 stores for lowercase. | |||
| f_decls: .byte "DECLS.N8" | |||
| f_decls_end: | |||
| f_nofinal: .byte "NOFINAL.N8" | |||
| f_nofinal_end: | |||
| f_edge: .byte "EDGE.N8" | |||
| f_edge_end: | |||
| f_longln: .byte "LONGLN.N8" | |||
| f_longln_end: | |||
| f_manyln: .byte "MANYLN.N8" | |||
| f_manyln_end: | |||
| f_big: .byte "BIG.N8" | |||
| f_big_end: | |||
| f_missing: .byte "MISSING.N8" | |||
| f_missing_end: | |||
| messages: | |||
| ok_msg: .byte "NOVA128 SOURCE TEST OK", $0D, $00 | |||
| fail_msg: .byte "NOVA128 SOURCE TEST FAIL", $0D, $00 | |||
| .segment "BSS" | |||
| results: .res REC_SIZE * FIXTURE_COUNT | |||
| record: .res REC_SIZE | |||
| failures: .res 1 | |||
| fx_index: .res 1 | |||
| name_lo: .res 1 | |||
| name_hi: .res 1 | |||
| name_len: .res 1 | |||
| expected: .res 1 | |||
| count: .res 2 | |||
| s1: .res 1 | |||
| s2: .res 1 | |||
| print_y: .res 1 | |||
Powered by TurnKey Linux.