Emit bounded documented instructions for scalar assignments, word and integer arithmetic, comparisons, and short-circuit booleans, with checked forward branch fixups. Add multiply and signed/unsigned divide helpers with a stable divide-by-zero result, plus a reviewed golden byte stream and disassembly. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>main
| @@ -406,6 +406,24 @@ generation relies on it. | |||||
| `delete` through an interface uses the object pointer and its class descriptor, | `delete` through an interface uses the object pointer and its class descriptor, | ||||
| not the interface table, to find the optional destructor and object size. | not the interface table, to find the optional destructor and object size. | ||||
| ### 7.6 Scalar code generation (E04-S03) | |||||
| `src/compiler/codegen.s` emits documented 6502 instructions into a bounded | |||||
| 256-byte buffer. One-byte values are in A; two-byte values are A (low) and X | |||||
| (high). The emitter provides immediate/absolute loads and stores, absolute | |||||
| `ADC`/`SBC`/`CMP` operands, `JSR`, and forward relative branches with checked | |||||
| fixups. Forward branches are limited to +0..+127 bytes; E04-S04 supplies the | |||||
| control-flow and long-branch expansion. | |||||
| Multiplication and signed/unsigned division use `src/runtime/arithmetic.s`. | |||||
| Operands are in `ar_a`/`ar_b`, the result is A/X, and division leaves its | |||||
| remainder in `ar_rem`. Division by zero sets provisional `ar_fault` to | |||||
| `AR_FAULT_DIV0`, returns A=X=0 with C=1, and is mapped to the final runtime | |||||
| fault namespace by E04-S05. The scalar fixture executes the emitted shapes, | |||||
| checks short-circuit evaluation, and compares a reviewed golden byte stream | |||||
| and da65 disassembly. Parser integration into statements and frames is | |||||
| E04-S04's responsibility. | |||||
| ## 8. Heap and Ownership | ## 8. Heap and Ownership | ||||
| - The generated-program heap initially resides in the same RAM bank as program | - The generated-program heap initially resides in the same RAM bank as program | ||||
| @@ -56,6 +56,7 @@ documented default. `Proposed` means a later change still needs approval. | |||||
| | D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. | | | D127 | `return` with a value in a program is E151. A method that declares a result must end every path in `return`; an `if` satisfies this only when both branches do, a `while` never does, and a missing return is reported at `end` as E150. | Spec 7 permits `return` but does not say what a program return means; a program has no caller or result. Deterministic and observable, and lets E04 emit a single exit path. Implemented by `close_block` and `set_ret`. | | ||||
| | D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. | | | D128 | The generated PRG container loads at `$1C01` with the same 12-byte BASIC 7 stub as the compiler and enters at `$1C0D`. Sections follow in ascending address order; gaps are zero-filled. `NAME.MAP` is CR-terminated text: `NOVA128 MAP 1`, `ENTRY hhhh`, then `SECTION name start size` in hex. Existing `NAME.PRG`/`NAME.MAP` is refused with E202 instead of overwritten; outputs created by the run are scratched on abort or failure. The higher generated-program map (banking, heap, software stack) remains E04-S02. | Gives E04-S01 a deterministic, testable container while preserving a previously successful build. Refusing an existing output is explicit; the user must erase it before recompiling. Implemented by `output.s`, `startup.s`, and `formats.inc`. | | ||||
| | D129 | Generated code executes with CR `$0E` and IRQs masked, then restores the entry CR. The software stack is bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP`/`FP` use `$FB-$FE`. A frame stores the previous FP at `FP`, arguments from `FP+2`, and locals below `FP`; callers push arguments right-to-left and discard them after return. Results are A or A/X. `frame_enter` reserves the old FP and locals transactionally, refuses when the 6502 stack pointer is below `$40`, and returns C=1/`FRAME_ERR_STACK` with `SP`/`FP` restored on overflow. | Makes recursion and nested calls testable without consuming BASIC's zero page or the compiler's private stack. The $40 hardware-stack guard prevents a JAM before the software bound, and the transactional failure lets callers unwind cleanly. The 2 KB software stack, guard threshold, and mapping install are provisional until codegen and later runtime stories. Implemented by `frames.s` and `frames.inc`. | | | D129 | Generated code executes with CR `$0E` and IRQs masked, then restores the entry CR. The software stack is bank 0 `$B800-$BFFF`, growing down from `$C000`; `SP`/`FP` use `$FB-$FE`. A frame stores the previous FP at `FP`, arguments from `FP+2`, and locals below `FP`; callers push arguments right-to-left and discard them after return. Results are A or A/X. `frame_enter` reserves the old FP and locals transactionally, refuses when the 6502 stack pointer is below `$40`, and returns C=1/`FRAME_ERR_STACK` with `SP`/`FP` restored on overflow. | Makes recursion and nested calls testable without consuming BASIC's zero page or the compiler's private stack. The $40 hardware-stack guard prevents a JAM before the software bound, and the transactional failure lets callers unwind cleanly. The 2 KB software stack, guard threshold, and mapping install are provisional until codegen and later runtime stories. Implemented by `frames.s` and `frames.inc`. | | ||||
| | D130 | Scalar codegen emits documented 6502 instructions into a bounded 256-byte buffer. Values are A (one byte) or A/X low/high (two bytes); assignments use absolute stores, comparisons produce a 0/1 byte, and forward branches use checked fixups. Multiplication and signed/unsigned division call runtime helpers with operands in `ar_a`/`ar_b`; results are A/X, division remainder is `ar_rem`, and division by zero sets provisional `ar_fault=AR_FAULT_DIV0`, A=X=0, C=1. E04-S04 owns parser-to-emitter integration, control flow, and long-branch expansion. | Gives the backend a small, directly testable instruction layer and a deterministic golden stream before the control-flow shell exists. The 256-byte buffer and forward-only branches are deliberate limits; E04-S05 maps the provisional arithmetic fault to the final R-code namespace. Implemented by `codegen.s`, `codegen.inc`, `arithmetic.s`, and `arithmetic.inc`. | | |||||
| ## Deferred | ## Deferred | ||||
| @@ -0,0 +1,98 @@ | |||||
| --- | |||||
| story: E04-S03 | |||||
| owner: e04_scalar | |||||
| status: done | |||||
| started: 2026-11-09 | |||||
| depends-on: [E04-S02] | |||||
| --- | |||||
| # E04-S03 Claim | |||||
| ## Intended change | |||||
| Add the scalar instruction emitter in `src/compiler/codegen.s` and the | |||||
| provisional arithmetic helpers in `src/runtime/arithmetic.s`. The emitter | |||||
| writes documented 6502 instructions into a bounded code buffer, supports | |||||
| absolute loads/stores and arithmetic operands, immediates, calls, forward | |||||
| branches with checked fixups, and returns the byte length for golden tests. | |||||
| The x128 fixture drives the emitter directly (parser integration is E04-S04) | |||||
| to build representative byte, word, integer, boolean, comparison, assignment, | |||||
| and short-circuit shapes, copies the golden byte stream, and executes it. It | |||||
| also calls the multiply/divide helpers, including unsigned and signed | |||||
| division, and verifies that division by zero sets a stable fault result. | |||||
| ## File area | |||||
| - `src/compiler/codegen.s` (new), `src/runtime/arithmetic.s` (new) | |||||
| - `src/common/codegen.inc`, `src/common/arithmetic.inc` (new) | |||||
| - `tests/codegen/scalar-test.s` | |||||
| - `tests/codegen/scalar.expected.dis`, `tests/codegen/scalar.expected.hex` | |||||
| - `scripts/build-scalar-test.ps1`, `scripts/verify-scalar-test.ps1` | |||||
| - `docs/decisions.md`, `docs/architecture.md` | |||||
| - `planning/claims/E04-S03.md` | |||||
| - generated files under `build/` | |||||
| ## Acceptance evidence | |||||
| - [x] Emits correct byte, word, integer, boolean, comparison, and assignment | |||||
| code: the fixture executes byte assignment/widening, word addition, integer | |||||
| subtraction, six comparisons, and boolean short-circuit shapes. Results: | |||||
| `$5A`, `$0E`, `$2221`, `$ECBB`. | |||||
| - [x] Short-circuit `and`/`or` avoids evaluating the unused operand: both | |||||
| shapes leave `V_EVAL=0` while producing the correct `V_FLAG`. | |||||
| - [x] Division by zero faults predictably: the generated call to `ar_div_u16` | |||||
| returns C=1, sets `ar_fault=AR_FAULT_DIV0`, and stores a zero result. | |||||
| - [x] Golden disassembly covers representative expression shapes: a reviewed | |||||
| 231-byte stream and canonical da65 listing are committed as | |||||
| `tests/codegen/scalar.expected.hex` and `.dis`; the verifier compares both. | |||||
| ## Coordination notes | |||||
| - E04-S02 is `done`; this story adds the scalar backend primitives but does not | |||||
| integrate them into the parser/control-flow shell. | |||||
| - D130 records the provisional scalar emitter interface and arithmetic helper | |||||
| data flow. | |||||
| - Runtime arithmetic faults use a provisional `AR_FAULT_DIV0` value until | |||||
| E04-S05 freezes the R-code namespace. | |||||
| - The fixture runs in the compiler mapping; its emitter buffer is writable BSS | |||||
| below `$A000`. Generated code is executed directly in that buffer. | |||||
| ## Work log | |||||
| - Added `cg_*` byte emitters, absolute operand forms, forward branch fixups, | |||||
| and a 256-byte bounded buffer. | |||||
| - Added 16-bit unsigned multiply, restoring unsigned divide, and signed divide | |||||
| with truncation toward zero. | |||||
| - Fixed ca65 operator precedence in the fixture macros: `#>addr+1` became | |||||
| `(>addr)+1`, so high-byte operands pointed at `$1Cxx` instead of `$1Bxx`. | |||||
| Parenthesized `#<(addr)`/`#>(addr)` fixed word high bytes and the apparent | |||||
| OR-tail misalignment. | |||||
| - Increased the fixup table to 32 entries because comparison branches remain | |||||
| recorded after patching. | |||||
| - Corrected fixture expectations: widened `$5A` is `$005A`, and the short- | |||||
| circuit cases now set their left operands explicitly. | |||||
| ## Completion | |||||
| - Tests run: | |||||
| - `scripts/build-scalar-test.ps1` | |||||
| - `scripts/verify-scalar-test.ps1 -Port 6530` | |||||
| - `scripts/verify-output-test.ps1 -Port 6530 -RunPort 6531` | |||||
| - `scripts/verify-frame-test.ps1 -Port 6530` | |||||
| - `scripts/verify-diag-test.ps1 -Port 6530` | |||||
| - `scripts/verify-parser-test.ps1 -Port 6530` | |||||
| - `git diff --check` | |||||
| - Results: scalar mailbox PASS, data `5A 0E 21 22 BB EC E0 01`; | |||||
| golden 231 bytes; canonical disassembly and hex match committed files. | |||||
| - Files changed: `src/compiler/codegen.s`, `src/runtime/arithmetic.s`, | |||||
| `src/common/codegen.inc`, `src/common/arithmetic.inc`, | |||||
| `tests/codegen/scalar-test.s`, golden expectations, | |||||
| `scripts/build-scalar-test.ps1`, `scripts/verify-scalar-test.ps1`, | |||||
| `docs/decisions.md`, `docs/architecture.md`, this claim. | |||||
| - Remaining risks: parser/statement integration and long-branch expansion are | |||||
| E04-S04; the emitter buffer is 255 bytes and only forward branches are | |||||
| supported; signed remainder is a magnitude, not sign-extended; byte | |||||
| multiply/divide and integer comparisons are not separately exercised; the | |||||
| divide fault is provisional until E04-S05; generated code is currently | |||||
| executed in the compiler mapping, not installed by a generated startup. | |||||
| @@ -0,0 +1,16 @@ | |||||
| # E04-S03: build the scalar emitter/arithmetic fixture. | |||||
| [CmdletBinding()] | |||||
| param() | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| $out = Join-Path $project "build\scalar-test" | |||||
| $link = Invoke-Nova128Link -Name "scalar-test" -BuildDir $out ` | |||||
| -Config (Join-Path $project "cfg\compiler.cfg") -Sources @( | |||||
| (Join-Path $project "src\compiler\codegen.s"), | |||||
| (Join-Path $project "src\runtime\arithmetic.s"), | |||||
| (Join-Path $project "tests\codegen\scalar-test.s")) | |||||
| Write-Host ("built {0}" -f $link.Program) | |||||
| @@ -0,0 +1,99 @@ | |||||
| # E04-S03: run the scalar emitter fixture, check results, capture golden bytes, | |||||
| # disassemble them with da65, and compare committed expectations when present. | |||||
| [CmdletBinding()] | |||||
| param( | |||||
| [int]$Port = 6530, | |||||
| [int]$TimeoutSeconds = 90 | |||||
| ) | |||||
| $ErrorActionPreference = "Stop" | |||||
| $project = Split-Path $PSScriptRoot -Parent | |||||
| Import-Module (Join-Path $PSScriptRoot "lib\Nova128Test.psm1") -Force | |||||
| & (Join-Path $PSScriptRoot "build-scalar-test.ps1") | |||||
| $out = Join-Path $project "build\scalar-test" | |||||
| $prg = Join-Path $out "scalar-test.prg" | |||||
| $labels = Join-Path $out "scalar-test.lbl" | |||||
| $session = Start-Nova128X128 -Autostart $prg -Port $Port ` | |||||
| -Screenshot (Join-Path $out "scalar-test-vicii.png") | |||||
| try { | |||||
| $mb = Wait-Nova128Mailbox $session -TimeoutSeconds $TimeoutSeconds | |||||
| $lenAddr = Get-Nova128Label -Labels $labels -Name "golden_len" | |||||
| $copyAddr = Get-Nova128Label -Labels $labels -Name "golden_copy" | |||||
| $lenBytes = Read-Nova128Memory -Session $session -Address $lenAddr -Count 1 | |||||
| $goldenLen = [int]$lenBytes[0] | |||||
| if ($goldenLen -eq 0) { throw "golden_len is zero" } | |||||
| $golden = Read-Nova128Memory -Session $session -Address $copyAddr -Count $goldenLen | |||||
| } finally { | |||||
| Stop-Nova128X128 $session | |||||
| } | |||||
| Write-Host ('mailbox: ' + (($mb | ForEach-Object { $_.ToString('X2') }) -join ' ')) | |||||
| Write-Host ("results: byte={0:X2} cmp={1:X2} add={2:X2}{3:X2} sub={4:X2}{5:X2} mul={6:X2} fault={7}" -f ` | |||||
| $mb[8], $mb[9], $mb[11], $mb[10], $mb[13], $mb[12], $mb[14], $mb[15]) | |||||
| $failures = @() | |||||
| if ($mb[2] -ne 0x03) { $failures += "producer $($mb[2]) is not a generated program" } | |||||
| if ($mb[3] -ne 0xA5) { $failures += "fixture status is not PASS" } | |||||
| if ($mb[4] -ne 0 -or $mb[5] -ne 0) { $failures += "code is $($mb[4] + 256 * $mb[5])" } | |||||
| $want = [ordered]@{ | |||||
| byte = 0x5A | |||||
| cmp = 0x0E | |||||
| addLow = 0x21; addHigh = 0x22 | |||||
| subLow = 0xBB; subHigh = 0xEC | |||||
| mulLow = 0xE0 | |||||
| fault = 1 | |||||
| } | |||||
| if ($mb[8] -ne $want.byte) { $failures += ('byte assignment is {0:X2}, expected {1:X2}' -f $mb[8], $want.byte) } | |||||
| if ($mb[9] -ne $want.cmp) { $failures += ('comparisons are {0:X2}, expected {1:X2}' -f $mb[9], $want.cmp) } | |||||
| if ($mb[10] -ne $want.addLow -or $mb[11] -ne $want.addHigh) { | |||||
| $failures += ('add is {0:X2}{1:X2}, expected {2:X2}{3:X2}' -f $mb[11], $mb[10], $want.addHigh, $want.addLow) | |||||
| } | |||||
| if ($mb[12] -ne $want.subLow -or $mb[13] -ne $want.subHigh) { | |||||
| $failures += ('sub is {0:X2}{1:X2}, expected {2:X2}{3:X2}' -f $mb[13], $mb[12], $want.subHigh, $want.subLow) | |||||
| } | |||||
| if ($mb[14] -ne $want.mulLow) { $failures += ('mul low is {0:X2}, expected {1:X2}' -f $mb[14], $want.mulLow) } | |||||
| if ($mb[15] -ne $want.fault) { $failures += "fault byte is $($mb[15]), expected 1" } | |||||
| $binPath = Join-Path $out "scalar.golden.bin" | |||||
| [IO.File]::WriteAllBytes($binPath, $golden) | |||||
| $disPath = Join-Path $out "scalar.golden.dis" | |||||
| $da65 = (Get-Command da65 -ErrorAction Stop).Source | |||||
| & $da65 --cpu 6502 --start-addr 0000 -o $disPath $binPath 2>&1 | Out-Null | |||||
| if ($LASTEXITCODE -ne 0) { throw "da65 failed on $binPath" } | |||||
| # Remove da65's timestamp/path header so the committed listing is stable. | |||||
| $disLines = Get-Content -LiteralPath $disPath | Where-Object { | |||||
| $_.Trim() -ne "" -and -not $_.StartsWith(";") | |||||
| } | |||||
| [IO.File]::WriteAllText($disPath, (($disLines -join "`n") + "`n")) | |||||
| $hex = (($golden | ForEach-Object { $_.ToString("X2") }) -join " ") + "`n" | |||||
| $actualHex = Join-Path $out "scalar.golden.hex" | |||||
| [IO.File]::WriteAllText($actualHex, $hex) | |||||
| Write-Host "golden bytes: $goldenLen" | |||||
| Write-Host "hex: $actualHex" | |||||
| Write-Host "disassembly: $disPath" | |||||
| $expectedHexPath = Join-Path $project "tests\codegen\scalar.expected.hex" | |||||
| if (Test-Path -LiteralPath $expectedHexPath) { | |||||
| $expectedHex = ([IO.File]::ReadAllText($expectedHexPath)).Trim() | |||||
| if ($hex.Trim() -ne $expectedHex) { | |||||
| $failures += "golden bytes differ from tests/codegen/scalar.expected.hex" | |||||
| } | |||||
| } else { | |||||
| Write-Host "no tests/codegen/scalar.expected.hex yet" | |||||
| } | |||||
| $expectedDisPath = Join-Path $project "tests\codegen\scalar.expected.dis" | |||||
| if (Test-Path -LiteralPath $expectedDisPath) { | |||||
| $expectedDis = ([IO.File]::ReadAllText($expectedDisPath)).Replace("`r`n", "`n").TrimEnd() | |||||
| $actualDis = ([IO.File]::ReadAllText($disPath)).Replace("`r`n", "`n").TrimEnd() | |||||
| if ($actualDis -ne $expectedDis) { | |||||
| $failures += "golden disassembly differs from tests/codegen/scalar.expected.dis" | |||||
| } | |||||
| } else { | |||||
| Write-Host "no tests/codegen/scalar.expected.dis yet" | |||||
| } | |||||
| if ($failures) { throw ("scalar verification failed:`n " + ($failures -join "`n ")) } | |||||
| Write-Host "verified scalar assignments, comparisons, short-circuit, arithmetic, and divide-by-zero" | |||||
| @@ -0,0 +1,14 @@ | |||||
| ; arithmetic.inc - provisional runtime scalar arithmetic (E04-S03). | |||||
| ; | |||||
| ; The operands live in ar_a/ar_b. The 16-bit result is A (low), X (high). | |||||
| ; Division also leaves the remainder in ar_rem. On division by zero the helper | |||||
| ; sets ar_fault=AR_FAULT_DIV0, returns A=X=0 and C=1. E04-S05 maps this to the | |||||
| ; final stable R-code namespace. | |||||
| .ifndef ARITHMETIC_INC | |||||
| ARITHMETIC_INC = 1 | |||||
| AR_FAULT_NONE = 0 | |||||
| AR_FAULT_DIV0 = 1 | |||||
| .endif | |||||
| @@ -0,0 +1,12 @@ | |||||
| ; codegen.inc - scalar instruction emitter limits (E04-S03). | |||||
| .ifndef CODEGEN_INC | |||||
| CODEGEN_INC = 1 | |||||
| CG_MAX = 255 ; bytes in the emitter buffer (len is 1 byte) | |||||
| CG_FIX_MAX = 32 ; forward branches per buffer | |||||
| CG_ERR_FULL = 1 ; buffer full | |||||
| CG_ERR_FIX = 2 ; fixup table full or bad index | |||||
| CG_ERR_RANGE = 3 ; relative branch out of range | |||||
| .endif | |||||
| @@ -0,0 +1,216 @@ | |||||
| ; codegen.s - documented scalar instruction emitter (E04-S03). | |||||
| ; | |||||
| ; The emitter writes into cg_code, a 256-byte RAM buffer. cg_len is the byte | |||||
| ; count (0-254); every failure sets cg_err and returns C=1. It does not emit a | |||||
| ; whole function yet: parser/codegen integration is E04-S04. | |||||
| ; | |||||
| ; API: | |||||
| ; cg_init | |||||
| ; cg_put A = byte | |||||
| ; cg_lda_imm A = value | |||||
| ; cg_ldx_imm A = value | |||||
| ; cg_ora_imm A = value | |||||
| ; cg_lda_abs A/X = address | |||||
| ; cg_ldx_abs A/X = address | |||||
| ; cg_sta_abs A/X = address | |||||
| ; cg_stx_abs A/X = address | |||||
| ; cg_adc_abs A/X = address | |||||
| ; cg_sbc_abs A/X = address | |||||
| ; cg_cmp_abs A/X = address | |||||
| ; cg_jsr_abs A/X = address | |||||
| ; cg_clc, cg_sec, cg_rts | |||||
| ; cg_branch_fwd A = branch opcode; emits opcode + placeholder, returns | |||||
| ; C=0 and A = fixup index | |||||
| ; cg_patch_here A = fixup index; target is the current end of buffer | |||||
| ; cg_here returns A = current length | |||||
| ; | |||||
| ; Only forward relative branches within +0..+127 bytes are accepted. This is | |||||
| ; enough for the scalar shapes and gives E04-S04 the checked primitive it | |||||
| ; needs for a later long-branch expansion. | |||||
| .setcpu "6502" | |||||
| .include "../common/codegen.inc" | |||||
| .export cg_init, cg_put, cg_here, cg_branch_fwd, cg_patch_here | |||||
| .export cg_lda_imm, cg_ldx_imm, cg_ora_imm, cg_lda_abs, cg_ldx_abs | |||||
| .export cg_sta_abs, cg_stx_abs, cg_adc_abs, cg_sbc_abs, cg_cmp_abs | |||||
| .export cg_jsr_abs, cg_clc, cg_sec, cg_rts | |||||
| .export cg_code, cg_len, cg_err | |||||
| .segment "CODE" | |||||
| cg_init: | |||||
| lda #$00 | |||||
| sta cg_len | |||||
| sta cg_err | |||||
| sta cg_fix_count | |||||
| clc | |||||
| rts | |||||
| cg_here: | |||||
| lda cg_len | |||||
| rts | |||||
| ; A = byte to append. C=1 on overflow. | |||||
| cg_put: | |||||
| ldy cg_len | |||||
| cpy #CG_MAX | |||||
| bcs @full | |||||
| sta cg_code,y | |||||
| inc cg_len | |||||
| clc | |||||
| rts | |||||
| @full: lda #CG_ERR_FULL | |||||
| sta cg_err | |||||
| sec | |||||
| rts | |||||
| ; Immediate forms. | |||||
| cg_lda_imm: | |||||
| pha | |||||
| lda #$A9 | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| pla | |||||
| jmp cg_put | |||||
| @out: pla | |||||
| sec | |||||
| rts | |||||
| cg_ldx_imm: | |||||
| pha | |||||
| lda #$A2 | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| pla | |||||
| jmp cg_put | |||||
| @out: pla | |||||
| sec | |||||
| rts | |||||
| cg_ora_imm: | |||||
| pha | |||||
| lda #$09 | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| pla | |||||
| jmp cg_put | |||||
| @out: pla | |||||
| sec | |||||
| rts | |||||
| ; Absolute-address forms. Each wrapper supplies the opcode in Y; the shared | |||||
| ; routine stores the address before Y is reused by cg_put. | |||||
| cg_lda_abs: | |||||
| ldy #$AD | |||||
| jmp cg_emit_addr | |||||
| cg_ldx_abs: | |||||
| ldy #$AE | |||||
| jmp cg_emit_addr | |||||
| cg_sta_abs: | |||||
| ldy #$8D | |||||
| jmp cg_emit_addr | |||||
| cg_stx_abs: | |||||
| ldy #$8E | |||||
| jmp cg_emit_addr | |||||
| cg_adc_abs: | |||||
| ldy #$6D | |||||
| jmp cg_emit_addr | |||||
| cg_sbc_abs: | |||||
| ldy #$ED | |||||
| jmp cg_emit_addr | |||||
| cg_cmp_abs: | |||||
| ldy #$CD | |||||
| jmp cg_emit_addr | |||||
| cg_jsr_abs: | |||||
| ldy #$20 | |||||
| jmp cg_emit_addr | |||||
| cg_emit_addr: | |||||
| sta cg_addr | |||||
| stx cg_addr+1 | |||||
| tya | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| lda cg_addr | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| lda cg_addr+1 | |||||
| jmp cg_put | |||||
| @out: sec | |||||
| rts | |||||
| cg_clc: | |||||
| lda #$18 | |||||
| jmp cg_put | |||||
| cg_sec: | |||||
| lda #$38 | |||||
| jmp cg_put | |||||
| cg_rts: | |||||
| lda #$60 | |||||
| jmp cg_put | |||||
| ; A = branch opcode; forward target must be patched later. | |||||
| cg_branch_fwd: | |||||
| sta cg_op | |||||
| lda cg_fix_count | |||||
| cmp #CG_FIX_MAX | |||||
| bcs @fix_full | |||||
| sta cg_idx | |||||
| lda cg_op | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| lda cg_len | |||||
| sta cg_pos | |||||
| ldy cg_idx | |||||
| sta cg_fixpos,y | |||||
| inc cg_fix_count | |||||
| lda #$00 | |||||
| jsr cg_put | |||||
| bcs @out | |||||
| lda cg_idx | |||||
| clc | |||||
| rts | |||||
| @fix_full: | |||||
| lda #CG_ERR_FIX | |||||
| sta cg_err | |||||
| sec | |||||
| rts | |||||
| @out: sec | |||||
| rts | |||||
| ; A = fixup index. Patch its placeholder so the branch targets cg_len. | |||||
| cg_patch_here: | |||||
| cmp cg_fix_count | |||||
| bcs @bad | |||||
| tax | |||||
| lda cg_len | |||||
| sec | |||||
| sbc cg_fixpos,x | |||||
| sec | |||||
| sbc #1 | |||||
| cmp #128 | |||||
| bcs @range | |||||
| ldy cg_fixpos,x | |||||
| sta cg_code,y | |||||
| clc | |||||
| rts | |||||
| @bad: lda #CG_ERR_FIX | |||||
| sta cg_err | |||||
| sec | |||||
| rts | |||||
| @range: lda #CG_ERR_RANGE | |||||
| sta cg_err | |||||
| sec | |||||
| rts | |||||
| .segment "BSS" | |||||
| cg_code: .res 256 | |||||
| cg_len: .res 1 | |||||
| cg_err: .res 1 | |||||
| cg_fix_count: .res 1 | |||||
| cg_fixpos: .res CG_FIX_MAX | |||||
| cg_addr: .res 2 | |||||
| cg_op: .res 1 | |||||
| cg_idx: .res 1 | |||||
| cg_pos: .res 1 | |||||
| @@ -0,0 +1,169 @@ | |||||
| ; arithmetic.s - provisional 16-bit scalar arithmetic (E04-S03). | |||||
| ; | |||||
| ; Operands: ar_a (left/dividend), ar_b (right/divisor), little-endian words. | |||||
| ; Results: A=low, X=high; division remainder in ar_rem. | |||||
| ; Multiplication wraps to 16 bits. Unsigned and signed division truncate toward | |||||
| ; zero. Division by zero sets ar_fault=AR_FAULT_DIV0, returns A=X=0 and C=1. | |||||
| ; | |||||
| ; The helpers modify ar_a and ar_b while shifting; callers must reload | |||||
| ; operands for a second operation. The signed remainder is left as a magnitude | |||||
| ; for now; quotient behavior is the tested contract. | |||||
| .setcpu "6502" | |||||
| .include "../common/arithmetic.inc" | |||||
| .export ar_mul_u16, ar_div_u16, ar_div_i16 | |||||
| .export ar_a, ar_b, ar_rem, ar_fault | |||||
| .segment "CODE" | |||||
| ; ar_a * ar_b -> A/X (low 16 bits). | |||||
| ar_mul_u16: | |||||
| lda #AR_FAULT_NONE | |||||
| sta ar_fault | |||||
| lda #$00 | |||||
| sta ar_res | |||||
| sta ar_res+1 | |||||
| ldx #16 | |||||
| @loop: lda ar_b | |||||
| and #$01 | |||||
| beq @noadd | |||||
| clc | |||||
| lda ar_res | |||||
| adc ar_a | |||||
| sta ar_res | |||||
| lda ar_res+1 | |||||
| adc ar_a+1 | |||||
| sta ar_res+1 | |||||
| @noadd: | |||||
| asl ar_a | |||||
| rol ar_a+1 | |||||
| lsr ar_b+1 | |||||
| ror ar_b | |||||
| dex | |||||
| bne @loop | |||||
| lda ar_res | |||||
| ldx ar_res+1 | |||||
| rts | |||||
| ; ar_a / ar_b -> A/X, remainder ar_rem. C=1 on divide by zero. | |||||
| ar_div_u16: | |||||
| lda ar_b | |||||
| ora ar_b+1 | |||||
| bne @nonzero | |||||
| lda #AR_FAULT_DIV0 | |||||
| sta ar_fault | |||||
| lda #$00 | |||||
| sta ar_rem | |||||
| sta ar_rem+1 | |||||
| sta ar_res | |||||
| sta ar_res+1 | |||||
| ldx #$00 | |||||
| sec | |||||
| rts | |||||
| @nonzero: | |||||
| lda #$00 | |||||
| sta ar_rem | |||||
| sta ar_rem+1 | |||||
| sta ar_res | |||||
| sta ar_res+1 | |||||
| ldx #16 | |||||
| @loop: | |||||
| ; rem = (rem << 1) | (dividend >> 15) | |||||
| asl ar_rem | |||||
| rol ar_rem+1 | |||||
| asl ar_a | |||||
| rol ar_a+1 | |||||
| bcc @nobit | |||||
| inc ar_rem | |||||
| @nobit: | |||||
| ; quotient <<= 1 | |||||
| asl ar_res | |||||
| rol ar_res+1 | |||||
| ; if rem >= divisor, subtract and set quotient bit 0 | |||||
| lda ar_rem+1 | |||||
| cmp ar_b+1 | |||||
| bcc @next | |||||
| bne @sub | |||||
| lda ar_rem | |||||
| cmp ar_b | |||||
| bcc @next | |||||
| @sub: sec | |||||
| lda ar_rem | |||||
| sbc ar_b | |||||
| sta ar_rem | |||||
| lda ar_rem+1 | |||||
| sbc ar_b+1 | |||||
| sta ar_rem+1 | |||||
| inc ar_res | |||||
| @next: dex | |||||
| bne @loop | |||||
| lda #AR_FAULT_NONE | |||||
| sta ar_fault | |||||
| lda ar_res | |||||
| ldx ar_res+1 | |||||
| clc | |||||
| rts | |||||
| ; Signed ar_a / ar_b -> A/X, quotient truncated toward zero. | |||||
| ar_div_i16: | |||||
| lda ar_b | |||||
| ora ar_b+1 | |||||
| bne @nonzero | |||||
| jmp ar_div_u16 | |||||
| @nonzero: | |||||
| lda ar_a+1 | |||||
| eor ar_b+1 | |||||
| and #$80 | |||||
| sta ar_sign | |||||
| jsr ar_abs_a | |||||
| jsr ar_abs_b | |||||
| jsr ar_div_u16 | |||||
| sta ar_res | |||||
| stx ar_res+1 | |||||
| lda ar_sign | |||||
| bpl @done | |||||
| sec | |||||
| lda #$00 | |||||
| sbc ar_res | |||||
| sta ar_res | |||||
| lda #$00 | |||||
| sbc ar_res+1 | |||||
| sta ar_res+1 | |||||
| @done: lda ar_res | |||||
| ldx ar_res+1 | |||||
| clc | |||||
| rts | |||||
| ar_abs_a: | |||||
| lda ar_a+1 | |||||
| bpl @done | |||||
| sec | |||||
| lda #$00 | |||||
| sbc ar_a | |||||
| sta ar_a | |||||
| lda #$00 | |||||
| sbc ar_a+1 | |||||
| sta ar_a+1 | |||||
| @done: rts | |||||
| ar_abs_b: | |||||
| lda ar_b+1 | |||||
| bpl @done | |||||
| sec | |||||
| lda #$00 | |||||
| sbc ar_b | |||||
| sta ar_b | |||||
| lda #$00 | |||||
| sbc ar_b+1 | |||||
| sta ar_b+1 | |||||
| @done: rts | |||||
| .segment "BSS" | |||||
| ar_a: .res 2 | |||||
| ar_b: .res 2 | |||||
| ar_rem: .res 2 | |||||
| ar_res: .res 2 | |||||
| ar_fault: .res 1 | |||||
| ar_sign: .res 1 | |||||
| @@ -0,0 +1,743 @@ | |||||
| ; E04-S03: scalar emitter and arithmetic fixture in x128. | |||||
| ; | |||||
| ; The fixture drives codegen.s directly (parser integration is E04-S04), | |||||
| ; executes the emitted shapes, copies the golden bytes to golden_copy, then | |||||
| ; tests the arithmetic helpers. It runs in the compiler/BASIC mapping; all | |||||
| ; variables and the code buffer are below $A000. | |||||
| ; | |||||
| ; Mailbox data: | |||||
| ; +0 byte assignment (5A) | |||||
| ; +1 comparison bit mask (EQ..GE, expected 0E with 5,7) | |||||
| ; +2/+3 word add ($2221) | |||||
| ; +4/+5 integer sub ($ECBB) | |||||
| ; +6 multiply result low ($2EE0 -> E0) | |||||
| ; +7 arithmetic fault (1 = divide by zero) | |||||
| ; Failure bits: 1 emitter/assignment, 2 add/sub, 4 comparisons, | |||||
| ; 8 short-circuit, 16 multiply, 32 unsigned divide, 64 signed divide, | |||||
| ; 128 divide-by-zero. | |||||
| .setcpu "6502" | |||||
| .include "../../src/common/c128.inc" | |||||
| .include "../../src/common/mailbox.inc" | |||||
| .include "../../src/common/codegen.inc" | |||||
| .include "../../src/common/arithmetic.inc" | |||||
| .import cg_init, cg_len, cg_err, cg_code, cg_here, cg_patch_here | |||||
| .import cg_lda_imm, cg_ldx_imm, cg_ora_imm | |||||
| .import cg_lda_abs, cg_ldx_abs, cg_sta_abs, cg_stx_abs | |||||
| .import cg_adc_abs, cg_sbc_abs, cg_cmp_abs, cg_jsr_abs | |||||
| .import cg_clc, cg_sec, cg_rts, cg_branch_fwd | |||||
| .import ar_mul_u16, ar_div_u16, ar_div_i16, ar_a, ar_b, ar_rem, ar_fault | |||||
| ; Fixed bank-0 scratch variables (SCRATCH is unused by this fixture). | |||||
| V_SRC = $1B00 | |||||
| V_DST = $1B01 | |||||
| V_WIDE = $1B02 | |||||
| V_ADD_A = $1B04 | |||||
| V_ADD_B = $1B06 | |||||
| V_ADD_R = $1B08 | |||||
| V_SUB_A = $1B0A | |||||
| V_SUB_B = $1B0C | |||||
| V_SUB_R = $1B0E | |||||
| V_BYTE_A = $1B10 | |||||
| V_BYTE_B = $1B11 | |||||
| V_CMP = $1B12 | |||||
| V_FLAG = $1B13 | |||||
| V_EVAL = $1B14 | |||||
| V_MUL_A = $1B16 | |||||
| V_MUL_B = $1B18 | |||||
| V_MUL_R = $1B1A | |||||
| V_DIV_A = $1B1C | |||||
| V_DIV_B = $1B1E | |||||
| V_DIV_R = $1B20 | |||||
| V_SDIV_A = $1B22 | |||||
| V_SDIV_B = $1B24 | |||||
| V_SDIV_R = $1B26 | |||||
| .macro EMIT_LDA addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_lda_abs | |||||
| .endmacro | |||||
| .macro EMIT_LDX addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_ldx_abs | |||||
| .endmacro | |||||
| .macro EMIT_STA addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_sta_abs | |||||
| .endmacro | |||||
| .macro EMIT_STX addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_stx_abs | |||||
| .endmacro | |||||
| .macro EMIT_ADC addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_adc_abs | |||||
| .endmacro | |||||
| .macro EMIT_SBC addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_sbc_abs | |||||
| .endmacro | |||||
| .macro EMIT_CMP addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_cmp_abs | |||||
| .endmacro | |||||
| .macro EMIT_JSR addr | |||||
| lda #<(addr) | |||||
| ldx #>(addr) | |||||
| jsr cg_jsr_abs | |||||
| .endmacro | |||||
| .macro EMIT_LDA_IMM value | |||||
| lda #value | |||||
| jsr cg_lda_imm | |||||
| .endmacro | |||||
| .macro EMIT_ORA_IMM value | |||||
| lda #value | |||||
| jsr cg_ora_imm | |||||
| .endmacro | |||||
| .macro EMIT_BIT value | |||||
| EMIT_LDA V_CMP | |||||
| EMIT_ORA_IMM value | |||||
| EMIT_STA V_CMP | |||||
| .endmacro | |||||
| .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 "STARTUP" | |||||
| .export test_entry | |||||
| 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_PROGRAM | |||||
| sta MB_PRODUCER | |||||
| lda #MB_ST_RUNNING | |||||
| sta MB_STATUS | |||||
| lda #$00 | |||||
| sta fail_mask | |||||
| sta MB_CODE | |||||
| sta MB_CODE+1 | |||||
| jsr init_vars | |||||
| jsr cg_init | |||||
| ; ---------------------------------------------------- golden shape 1: byte assignment | |||||
| EMIT_LDA V_SRC | |||||
| EMIT_STA V_DST | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s2 | |||||
| ; ---------------------------------------------------- shape 2: byte -> word widening | |||||
| EMIT_LDA V_SRC | |||||
| EMIT_STA V_WIDE | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_WIDE+1 | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s3 | |||||
| ; ---------------------------------------------------- shape 3: word addition | |||||
| EMIT_LDA V_ADD_A | |||||
| jsr cg_clc | |||||
| EMIT_ADC V_ADD_B | |||||
| EMIT_STA V_ADD_R | |||||
| EMIT_LDA V_ADD_A+1 | |||||
| EMIT_ADC V_ADD_B+1 | |||||
| EMIT_STA V_ADD_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s4 | |||||
| ; ---------------------------------------------------- shape 4: integer subtraction | |||||
| EMIT_LDA V_SUB_A | |||||
| jsr cg_sec | |||||
| EMIT_SBC V_SUB_B | |||||
| EMIT_STA V_SUB_R | |||||
| EMIT_LDA V_SUB_A+1 | |||||
| EMIT_SBC V_SUB_B+1 | |||||
| EMIT_STA V_SUB_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s5 | |||||
| ; ---------------------------------------------------- shape 5: six comparisons | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_CMP | |||||
| ; EQ bit 1 | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$D0 ; BNE skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_eq | |||||
| EMIT_BIT $01 | |||||
| lda fix_eq | |||||
| jsr cg_patch_here | |||||
| ; NE bit 2 | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$F0 ; BEQ skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_ne | |||||
| EMIT_BIT $02 | |||||
| lda fix_ne | |||||
| jsr cg_patch_here | |||||
| ; LT bit 4 | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$B0 ; BCS skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_lt | |||||
| EMIT_BIT $04 | |||||
| lda fix_lt | |||||
| jsr cg_patch_here | |||||
| ; LE bit 8: equal or below goes to set, above skips | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$F0 ; BEQ set | |||||
| jsr cg_branch_fwd | |||||
| sta fix_le1 | |||||
| lda #$90 ; BCC set | |||||
| jsr cg_branch_fwd | |||||
| sta fix_le2 | |||||
| lda #$B0 ; BCS skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_le3 | |||||
| lda fix_le1 | |||||
| jsr cg_patch_here | |||||
| lda fix_le2 | |||||
| jsr cg_patch_here | |||||
| EMIT_BIT $08 | |||||
| lda fix_le3 | |||||
| jsr cg_patch_here | |||||
| ; GT bit 16: equal or below skips | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$F0 ; BEQ skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_gt1 | |||||
| lda #$90 ; BCC skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_gt2 | |||||
| EMIT_BIT $10 | |||||
| lda fix_gt1 | |||||
| jsr cg_patch_here | |||||
| lda fix_gt2 | |||||
| jsr cg_patch_here | |||||
| ; GE bit 32 | |||||
| EMIT_LDA V_BYTE_A | |||||
| EMIT_CMP V_BYTE_B | |||||
| lda #$90 ; BCC skip | |||||
| jsr cg_branch_fwd | |||||
| sta fix_ge | |||||
| EMIT_BIT $20 | |||||
| lda fix_ge | |||||
| jsr cg_patch_here | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s6 | |||||
| ; ---------------------------------------------------- shape 6: short-circuit AND | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_EVAL | |||||
| EMIT_LDA V_BYTE_A | |||||
| lda #$F0 ; BEQ false | |||||
| jsr cg_branch_fwd | |||||
| sta fix_and1 | |||||
| EMIT_LDA_IMM $01 | |||||
| EMIT_STA V_EVAL | |||||
| EMIT_LDA V_BYTE_B | |||||
| lda #$F0 ; BEQ false | |||||
| jsr cg_branch_fwd | |||||
| sta fix_and2 | |||||
| EMIT_LDA_IMM $01 | |||||
| EMIT_STA V_FLAG | |||||
| jsr cg_rts | |||||
| lda fix_and1 | |||||
| jsr cg_patch_here | |||||
| lda fix_and2 | |||||
| jsr cg_patch_here | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_FLAG | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta s7 | |||||
| ; ---------------------------------------------------- shape 7: short-circuit OR | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_EVAL | |||||
| EMIT_LDA V_BYTE_A | |||||
| lda #$D0 ; BNE true | |||||
| jsr cg_branch_fwd | |||||
| sta fix_or1 | |||||
| EMIT_LDA_IMM $01 | |||||
| EMIT_STA V_EVAL | |||||
| EMIT_LDA V_BYTE_B | |||||
| lda #$D0 ; BNE true | |||||
| jsr cg_branch_fwd | |||||
| sta fix_or2 | |||||
| EMIT_LDA_IMM $00 | |||||
| EMIT_STA V_FLAG | |||||
| jsr cg_rts | |||||
| lda fix_or1 | |||||
| jsr cg_patch_here | |||||
| lda fix_or2 | |||||
| jsr cg_patch_here | |||||
| EMIT_LDA_IMM $01 | |||||
| EMIT_STA V_FLAG | |||||
| jsr cg_rts | |||||
| lda cg_len | |||||
| sta golden_len | |||||
| jsr copy_golden | |||||
| lda cg_err | |||||
| beq @golden_ok | |||||
| lda #$01 | |||||
| jsr fail | |||||
| jmp @finish | |||||
| @golden_ok: | |||||
| ; ---------------------------------------------------- run golden shapes | |||||
| lda #$00 | |||||
| jsr run_code | |||||
| lda V_DST | |||||
| cmp #$5A | |||||
| beq @s2 | |||||
| lda #$01 | |||||
| jsr fail | |||||
| @s2: lda s2 | |||||
| jsr run_code | |||||
| lda V_WIDE | |||||
| cmp #$5A | |||||
| bne @s2_bad | |||||
| lda V_WIDE+1 | |||||
| bne @s2_bad | |||||
| jmp @s3 | |||||
| @s2_bad: | |||||
| lda #$01 | |||||
| jsr fail | |||||
| @s3: lda s3 | |||||
| jsr run_code | |||||
| lda V_ADD_R | |||||
| cmp #$21 | |||||
| bne @s3_bad | |||||
| lda V_ADD_R+1 | |||||
| cmp #$22 | |||||
| bne @s3_bad | |||||
| jmp @s4 | |||||
| @s3_bad: | |||||
| lda #$02 | |||||
| jsr fail | |||||
| @s4: lda s4 | |||||
| jsr run_code | |||||
| lda V_SUB_R | |||||
| cmp #$BB | |||||
| bne @s4_bad | |||||
| lda V_SUB_R+1 | |||||
| cmp #$EC | |||||
| bne @s4_bad | |||||
| jmp @s5 | |||||
| @s4_bad: | |||||
| lda #$02 | |||||
| jsr fail | |||||
| @s5: lda s5 | |||||
| jsr run_code | |||||
| lda V_CMP | |||||
| cmp #$0E | |||||
| beq @s6 | |||||
| lda #$04 | |||||
| jsr fail | |||||
| @s6: lda #$00 | |||||
| sta V_BYTE_A | |||||
| lda #$01 | |||||
| sta V_BYTE_B | |||||
| lda s6 | |||||
| jsr run_code | |||||
| lda V_FLAG | |||||
| bne @s6_bad | |||||
| lda V_EVAL | |||||
| bne @s6_bad | |||||
| jmp @s7 | |||||
| @s6_bad: | |||||
| lda #$08 | |||||
| jsr fail | |||||
| @s7: lda #$01 | |||||
| sta V_BYTE_A | |||||
| lda #$00 | |||||
| sta V_BYTE_B | |||||
| lda s7 | |||||
| jsr run_code | |||||
| lda V_FLAG | |||||
| cmp #$01 | |||||
| bne @s7_bad | |||||
| lda V_EVAL | |||||
| bne @s7_bad | |||||
| jmp @arith | |||||
| @s7_bad: | |||||
| lda #$08 | |||||
| jsr fail | |||||
| ; ---------------------------------------------------- arithmetic helpers | |||||
| @arith: | |||||
| jsr cg_init | |||||
| EMIT_LDA V_MUL_A | |||||
| EMIT_STA ar_a | |||||
| EMIT_LDA V_MUL_A+1 | |||||
| EMIT_STA ar_a+1 | |||||
| EMIT_LDA V_MUL_B | |||||
| EMIT_STA ar_b | |||||
| EMIT_LDA V_MUL_B+1 | |||||
| EMIT_STA ar_b+1 | |||||
| EMIT_JSR ar_mul_u16 | |||||
| EMIT_STA V_MUL_R | |||||
| EMIT_STX V_MUL_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_err | |||||
| beq @mul_run | |||||
| lda #$10 | |||||
| jsr fail | |||||
| jmp @div | |||||
| @mul_run: | |||||
| lda #$00 | |||||
| jsr run_code | |||||
| lda V_MUL_R | |||||
| cmp #$E0 | |||||
| bne @mul_bad | |||||
| lda V_MUL_R+1 | |||||
| cmp #$2E | |||||
| bne @mul_bad | |||||
| jmp @div | |||||
| @mul_bad: | |||||
| lda #$10 | |||||
| jsr fail | |||||
| @div: jsr cg_init | |||||
| EMIT_LDA V_DIV_A | |||||
| EMIT_STA ar_a | |||||
| EMIT_LDA V_DIV_A+1 | |||||
| EMIT_STA ar_a+1 | |||||
| EMIT_LDA V_DIV_B | |||||
| EMIT_STA ar_b | |||||
| EMIT_LDA V_DIV_B+1 | |||||
| EMIT_STA ar_b+1 | |||||
| EMIT_JSR ar_div_u16 | |||||
| EMIT_STA V_DIV_R | |||||
| EMIT_STX V_DIV_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_err | |||||
| beq @div_run | |||||
| lda #$20 | |||||
| jsr fail | |||||
| jmp @sdiv | |||||
| @div_run: | |||||
| lda #$00 | |||||
| jsr run_code | |||||
| bcs @div_bad | |||||
| lda V_DIV_R | |||||
| cmp #$8E | |||||
| bne @div_bad | |||||
| lda V_DIV_R+1 | |||||
| bne @div_bad | |||||
| lda ar_rem | |||||
| cmp #$06 | |||||
| bne @div_bad | |||||
| lda ar_rem+1 | |||||
| bne @div_bad | |||||
| jmp @sdiv | |||||
| @div_bad: | |||||
| lda #$20 | |||||
| jsr fail | |||||
| @sdiv: jsr cg_init | |||||
| EMIT_LDA V_SDIV_A | |||||
| EMIT_STA ar_a | |||||
| EMIT_LDA V_SDIV_A+1 | |||||
| EMIT_STA ar_a+1 | |||||
| EMIT_LDA V_SDIV_B | |||||
| EMIT_STA ar_b | |||||
| EMIT_LDA V_SDIV_B+1 | |||||
| EMIT_STA ar_b+1 | |||||
| EMIT_JSR ar_div_i16 | |||||
| EMIT_STA V_SDIV_R | |||||
| EMIT_STX V_SDIV_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_err | |||||
| beq @sdiv_run | |||||
| lda #$40 | |||||
| jsr fail | |||||
| jmp @divzero | |||||
| @sdiv_run: | |||||
| lda #$00 | |||||
| jsr run_code | |||||
| bcs @sdiv_bad | |||||
| lda V_SDIV_R | |||||
| cmp #$F2 | |||||
| bne @sdiv_bad | |||||
| lda V_SDIV_R+1 | |||||
| cmp #$FF | |||||
| bne @sdiv_bad | |||||
| jmp @divzero | |||||
| @sdiv_bad: | |||||
| lda #$40 | |||||
| jsr fail | |||||
| @divzero: | |||||
| lda #$00 | |||||
| sta V_DIV_B | |||||
| sta V_DIV_B+1 | |||||
| jsr cg_init | |||||
| EMIT_LDA V_DIV_A | |||||
| EMIT_STA ar_a | |||||
| EMIT_LDA V_DIV_A+1 | |||||
| EMIT_STA ar_a+1 | |||||
| EMIT_LDA V_DIV_B | |||||
| EMIT_STA ar_b | |||||
| EMIT_LDA V_DIV_B+1 | |||||
| EMIT_STA ar_b+1 | |||||
| EMIT_JSR ar_div_u16 | |||||
| EMIT_STA V_DIV_R | |||||
| EMIT_STX V_DIV_R+1 | |||||
| jsr cg_rts | |||||
| lda cg_err | |||||
| beq @dz_run | |||||
| lda #$80 | |||||
| jsr fail | |||||
| jmp @finish | |||||
| @dz_run: | |||||
| lda #$00 | |||||
| jsr run_code | |||||
| bcc @dz_bad | |||||
| lda ar_fault | |||||
| cmp #AR_FAULT_DIV0 | |||||
| bne @dz_bad | |||||
| lda V_DIV_R | |||||
| ora V_DIV_R+1 | |||||
| bne @dz_bad | |||||
| jmp @finish | |||||
| @dz_bad: | |||||
| lda #$80 | |||||
| jsr fail | |||||
| ; ---------------------------------------------------- report | |||||
| @finish: | |||||
| lda V_DST | |||||
| sta MB_DATA+0 | |||||
| lda V_CMP | |||||
| sta MB_DATA+1 | |||||
| lda V_ADD_R | |||||
| sta MB_DATA+2 | |||||
| lda V_ADD_R+1 | |||||
| sta MB_DATA+3 | |||||
| lda V_SUB_R | |||||
| sta MB_DATA+4 | |||||
| lda V_SUB_R+1 | |||||
| sta MB_DATA+5 | |||||
| lda V_MUL_R | |||||
| sta MB_DATA+6 | |||||
| lda ar_fault | |||||
| sta MB_DATA+7 | |||||
| lda fail_mask | |||||
| sta MB_CODE | |||||
| lda #$00 | |||||
| sta MB_CODE+1 | |||||
| lda #MB_ST_FAIL | |||||
| ldx fail_mask | |||||
| bne @status | |||||
| lda #MB_ST_PASS | |||||
| @status: | |||||
| sta MB_STATUS | |||||
| ldy #$00 | |||||
| @print: lda message,y | |||||
| beq @end | |||||
| sty print_y | |||||
| jsr CHROUT | |||||
| ldy print_y | |||||
| iny | |||||
| bne @print | |||||
| @end: plp | |||||
| rts | |||||
| ; A = code-buffer offset. Patch a JSR operand and run the snippet. | |||||
| run_code: | |||||
| clc | |||||
| adc #<cg_code | |||||
| sta @target+1 | |||||
| lda #>cg_code | |||||
| adc #0 | |||||
| sta @target+2 | |||||
| @target: | |||||
| jsr $FFFF | |||||
| rts | |||||
| copy_golden: | |||||
| ldx #$00 | |||||
| @copy: cpx golden_len | |||||
| beq @done | |||||
| lda cg_code,x | |||||
| sta golden_copy,x | |||||
| inx | |||||
| bne @copy | |||||
| @done: rts | |||||
| init_vars: | |||||
| lda #$5A | |||||
| sta V_SRC | |||||
| sta V_DST | |||||
| lda #$00 | |||||
| sta V_WIDE | |||||
| sta V_WIDE+1 | |||||
| sta V_CMP | |||||
| sta V_FLAG | |||||
| sta V_EVAL | |||||
| sta ar_fault | |||||
| lda #$34 | |||||
| sta V_ADD_A | |||||
| lda #$12 | |||||
| sta V_ADD_A+1 | |||||
| lda #$ED | |||||
| sta V_ADD_B | |||||
| lda #$0F | |||||
| sta V_ADD_B+1 | |||||
| lda #$00 | |||||
| sta V_ADD_R | |||||
| sta V_ADD_R+1 | |||||
| lda #$00 | |||||
| sta V_SUB_A | |||||
| lda #$10 | |||||
| sta V_SUB_A+1 | |||||
| lda #$45 | |||||
| sta V_SUB_B | |||||
| lda #$23 | |||||
| sta V_SUB_B+1 | |||||
| lda #$00 | |||||
| sta V_SUB_R | |||||
| sta V_SUB_R+1 | |||||
| lda #$05 | |||||
| sta V_BYTE_A | |||||
| lda #$07 | |||||
| sta V_BYTE_B | |||||
| lda #$2C | |||||
| sta V_MUL_A | |||||
| lda #$01 | |||||
| sta V_MUL_A+1 | |||||
| lda #$28 | |||||
| sta V_MUL_B | |||||
| lda #$00 | |||||
| sta V_MUL_B+1 | |||||
| lda #$00 | |||||
| sta V_MUL_R | |||||
| sta V_MUL_R+1 | |||||
| lda #$E8 | |||||
| sta V_DIV_A | |||||
| lda #$03 | |||||
| sta V_DIV_A+1 | |||||
| lda #$07 | |||||
| sta V_DIV_B | |||||
| lda #$00 | |||||
| sta V_DIV_B+1 | |||||
| lda #$00 | |||||
| sta V_DIV_R | |||||
| sta V_DIV_R+1 | |||||
| lda #$9C | |||||
| sta V_SDIV_A | |||||
| lda #$FF | |||||
| sta V_SDIV_A+1 | |||||
| lda #$07 | |||||
| sta V_SDIV_B | |||||
| lda #$00 | |||||
| sta V_SDIV_B+1 | |||||
| lda #$00 | |||||
| sta V_SDIV_R | |||||
| sta V_SDIV_R+1 | |||||
| rts | |||||
| .segment "CODE" | |||||
| fail: | |||||
| ora fail_mask | |||||
| sta fail_mask | |||||
| rts | |||||
| .segment "RODATA" | |||||
| message: .byte "NOVA128 SCALAR TEST DONE", $0D, $00 | |||||
| .segment "BSS" | |||||
| fail_mask: .res 1 | |||||
| print_y: .res 1 | |||||
| s2: .res 1 | |||||
| s3: .res 1 | |||||
| s4: .res 1 | |||||
| s5: .res 1 | |||||
| s6: .res 1 | |||||
| s7: .res 1 | |||||
| fix_eq: .res 1 | |||||
| fix_ne: .res 1 | |||||
| fix_lt: .res 1 | |||||
| fix_le1: .res 1 | |||||
| fix_le2: .res 1 | |||||
| fix_le3: .res 1 | |||||
| fix_gt1: .res 1 | |||||
| fix_gt2: .res 1 | |||||
| fix_ge: .res 1 | |||||
| fix_and1: .res 1 | |||||
| fix_and2: .res 1 | |||||
| fix_or1: .res 1 | |||||
| fix_or2: .res 1 | |||||
| .export golden_len | |||||
| .export golden_copy | |||||
| golden_len: .res 1 | |||||
| golden_copy: .res 256 | |||||
| @@ -0,0 +1,95 @@ | |||||
| .setcpu "6502" | |||||
| lda $1B00 | |||||
| sta $1B01 | |||||
| rts | |||||
| lda $1B00 | |||||
| sta $1B02 | |||||
| lda #$00 | |||||
| sta $1B03 | |||||
| rts | |||||
| lda $1B04 | |||||
| clc | |||||
| adc $1B06 | |||||
| sta $1B08 | |||||
| lda $1B05 | |||||
| adc $1B07 | |||||
| sta $1B09 | |||||
| rts | |||||
| lda $1B0A | |||||
| sec | |||||
| sbc $1B0C | |||||
| sta $1B0E | |||||
| lda $1B0B | |||||
| sbc $1B0D | |||||
| sta $1B0F | |||||
| rts | |||||
| lda #$00 | |||||
| sta $1B12 | |||||
| lda $1B10 | |||||
| cmp $1B11 | |||||
| bne L0050 | |||||
| lda $1B12 | |||||
| ora #$01 | |||||
| sta $1B12 | |||||
| L0050: lda $1B10 | |||||
| cmp $1B11 | |||||
| beq L0060 | |||||
| lda $1B12 | |||||
| ora #$02 | |||||
| sta $1B12 | |||||
| L0060: lda $1B10 | |||||
| cmp $1B11 | |||||
| bcs L0070 | |||||
| lda $1B12 | |||||
| ora #$04 | |||||
| sta $1B12 | |||||
| L0070: lda $1B10 | |||||
| cmp $1B11 | |||||
| beq L007C | |||||
| bcc L007C | |||||
| bcs L0084 | |||||
| L007C: lda $1B12 | |||||
| ora #$08 | |||||
| sta $1B12 | |||||
| L0084: lda $1B10 | |||||
| cmp $1B11 | |||||
| beq L0096 | |||||
| bcc L0096 | |||||
| lda $1B12 | |||||
| ora #$10 | |||||
| sta $1B12 | |||||
| L0096: lda $1B10 | |||||
| cmp $1B11 | |||||
| bcc L00A6 | |||||
| lda $1B12 | |||||
| ora #$20 | |||||
| sta $1B12 | |||||
| L00A6: rts | |||||
| lda #$00 | |||||
| sta $1B14 | |||||
| lda $1B10 | |||||
| beq L00C1 | |||||
| lda #$01 | |||||
| sta $1B14 | |||||
| lda $1B11 | |||||
| beq L00C1 | |||||
| lda #$01 | |||||
| sta $1B13 | |||||
| rts | |||||
| L00C1: lda #$00 | |||||
| sta $1B13 | |||||
| rts | |||||
| lda #$00 | |||||
| sta $1B14 | |||||
| lda $1B10 | |||||
| bne L00E1 | |||||
| lda #$01 | |||||
| sta $1B14 | |||||
| lda $1B11 | |||||
| bne L00E1 | |||||
| lda #$00 | |||||
| sta $1B13 | |||||
| rts | |||||
| L00E1: lda #$01 | |||||
| sta $1B13 | |||||
| rts | |||||
| @@ -0,0 +1 @@ | |||||
| AD 00 1B 8D 01 1B 60 AD 00 1B 8D 02 1B A9 00 8D 03 1B 60 AD 04 1B 18 6D 06 1B 8D 08 1B AD 05 1B 6D 07 1B 8D 09 1B 60 AD 0A 1B 38 ED 0C 1B 8D 0E 1B AD 0B 1B ED 0D 1B 8D 0F 1B 60 A9 00 8D 12 1B AD 10 1B CD 11 1B D0 08 AD 12 1B 09 01 8D 12 1B AD 10 1B CD 11 1B F0 08 AD 12 1B 09 02 8D 12 1B AD 10 1B CD 11 1B B0 08 AD 12 1B 09 04 8D 12 1B AD 10 1B CD 11 1B F0 04 90 02 B0 08 AD 12 1B 09 08 8D 12 1B AD 10 1B CD 11 1B F0 0A 90 08 AD 12 1B 09 10 8D 12 1B AD 10 1B CD 11 1B 90 08 AD 12 1B 09 20 8D 12 1B 60 A9 00 8D 14 1B AD 10 1B F0 10 A9 01 8D 14 1B AD 11 1B F0 06 A9 01 8D 13 1B 60 A9 00 8D 13 1B 60 A9 00 8D 14 1B AD 10 1B D0 10 A9 01 8D 14 1B AD 11 1B D0 06 A9 00 8D 13 1B 60 A9 01 8D 13 1B 60 | |||||
Powered by TurnKey Linux.