1
0
mirror of https://github.com/dschmenk/PLASMA.git synced 2026-03-11 09:42:04 +00:00

Add hardware acceleration for scalb, compXY, and annuityXY functions

Implemented hardware-accelerated versions of three additional functions:

- scalb(x, n): Binary scale operation (x * 2^n) using pow2X and mul
- compXY(rate, periods): Compound interest ((1+rate)^periods) using powXY
- annuityXY(rate, periods): Annuity calculation using powXY

This brings the total hardware-accelerated functions from 28 to 31 out of
40 total SANE operations (78% coverage). These functions leverage the
existing hardware-accelerated powXY implementation, combined with
mathematical identities to avoid SANE fallback.

All functions maintain full API compatibility and automatically fall back
to SANE if MegaFlash hardware is not available.

Updated documentation to reflect new accelerated function count and
reorganized function categories.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Brendan Robert
2026-01-24 18:01:02 -06:00
parent 6839f068ab
commit 3d359f0a0e
3 changed files with 173 additions and 21 deletions

View File

@@ -58,13 +58,12 @@ This ensures programs work on any Apple II system, whether or not MegaFlash is p
### Accelerated Operations
The following operations use MegaFlash hardware acceleration:
The library provides **31 hardware-accelerated functions** out of 40 total SANE operations (78% coverage):
- **Arithmetic**: `mul`, `div`, `sqrt`
- **Trigonometry**: `sin`, `cos`, `tan`, `atan`
- **Logarithmic**: `lnX` (natural log), `powEX` (e^x)
- **Direct Hardware FPU** (9 functions): `mul`, `div`, `sqrt`, `sin`, `cos`, `tan`, `atan`, `lnX`, `powEX`
- **Via Mathematical Identities** (22 functions): `neg`, `abs`, `log2X`, `log21X`, `ln1X`, `pow2X`, `pow21X`, `powE1X`, `powE21X`, `powXInt`, `powXY`, `asin`, `acos`, `sinh`, `cosh`, `tanh`, `sec`, `csc`, `cot`, `scalb`, `compXY`, `annuityXY`
Operations not supported by MegaFlash hardware (add, sub, rem, neg, abs, etc.) automatically use SANE fallback.
The remaining 9 operations (add, sub, rem, type, cmp, trunc, round, logb, randNum) use SANE fallback.
### Performance

View File

@@ -10,9 +10,9 @@
- **API Compatible**: Maintains complete API compatibility with `fpu.pla`
- **Automatic Fallback**: Detects MegaFlash presence and falls back to SANE if not available
- **Format Conversion**: Automatically converts between SANE Extended (80-bit) and MBF (40-bit) formats
- **Hardware-Accelerated Operations** (28 functions total):
- **Hardware-Accelerated Operations** (31 functions total):
- **Direct FPU**: mul, div, sqrt, sin, cos, tan, atan, ln, exp
- **Via Identities**: neg, abs, log2, log21, ln1, pow2, pow21, powE1, powE21, powXInt, powXY, asin, acos, sinh, cosh, tanh, sec, csc, cot
- **Via Identities**: neg, abs, log2, log21, ln1, pow2, pow21, powE1, powE21, powXInt, powXY, asin, acos, sinh, cosh, tanh, sec, csc, cot, scalb, compXY, annuityXY
## Usage
@@ -113,6 +113,9 @@ The library uses the following MegaFlash registers (Slot 4):
- `sec` - Secant (1/cos)
- `csc` - Cosecant (1/sin)
- `cot` - Cotangent (1/tan)
- `scalb` - Binary scale (x * 2^n via pow2X and mul)
- `compXY` - Compound interest ((1 + rate)^periods via powXY)
- `annuityXY` - Annuity ((1 - (1+r)^-periods) / r via powXY)
### Software Fallback (SANE)
- `add` - Addition (conversion overhead makes hardware slower)
@@ -122,9 +125,7 @@ The library uses the following MegaFlash registers (Slot 4):
- `cmp` - Compare
- `trunc` - Truncate
- `round` - Round
- `logb` - Log base
- `scalb` - Scale
- `compXY`, `annuityXY` - Financial functions
- `logb` - Log base (binary exponent extraction)
- `randNum` - Random number generation
## Building

View File

@@ -826,10 +826,36 @@ def logb
end
def scalb(scale)
// Scale - use SANE
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(sane:op2FP(FFEXT|FOSCALB, stackRegs[0], scale))
// scalb(x, n) = x * 2^n = x * pow2(n)
// Hardware accelerated via pow2X and mul
word err
if !mfAvailable
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(sane:op2FP(FFEXT|FOSCALB, stackRegs[0], scale))
fin
// Push scale as integer, convert to float, compute 2^scale
err = pushInt(scale)
if err < 0
_drop(0) // Drop the pushed value
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(sane:op2FP(FFEXT|FOSCALB, stackRegs[1], scale))
fin
// Compute 2^scale
err = pow2X
if err < 0
_drop(0) // Drop the failed result
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(sane:op2FP(FFEXT|FOSCALB, stackRegs[0], scale))
fin
// Multiply x * 2^scale
return mul
end
//==============================================================================
@@ -1568,21 +1594,147 @@ def cot
end
//==============================================================================
// FINANCIAL AND RANDOM (SANE fallback)
// FINANCIAL FUNCTIONS (Hardware accelerated via powXY)
//==============================================================================
def compXY
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOCOMPND, stackRegs[0], stackRegs[1]))))
// compXY(rate, periods) = (1 + rate)^periods
// Hardware accelerated via powXY
word err
byte[t_extended] one
if !mfAvailable
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOCOMPND, stackRegs[0], stackRegs[1]))))
fin
// Stack: periods (top), rate (second)
// Need to compute: (1 + rate)^periods
// one = 1.0
one.0 = 0
one.1 = $3F
one.2 = $80
one.3 = 0
one.4 = 0
one.5 = 0
one.6 = 0
one.7 = 0
one.8 = 0
one.9 = 0
// Push 1.0, then add to rate
memcpy(stackRegs[2], @one, t_extended) // Push 1.0
memcpy(stackRegs[3], stackRegs[1], t_extended) // Copy rate
stackRegs[0], stackRegs[1], stackRegs[2], stackRegs[3] = stackRegs[2], stackRegs[3], stackRegs[0], stackRegs[1]
// Stack now: rate, 1.0, periods, (old)
err = add // Compute 1 + rate
if err < 0
_drop(0)
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOCOMPND, stackRegs[0], stackRegs[1]))))
fin
// Stack now: (1+rate), periods, ...
// Swap to get periods on top for powXY
swap
// Stack now: periods, (1+rate), ...
// Call powXY to compute (1+rate)^periods
return powXY
end
def annuityXY
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
// annuityXY(rate, periods) = (1 - (1+rate)^-periods) / rate
// Hardware accelerated via powXY
word err
byte[t_extended] one
if !mfAvailable
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
fin
// Stack: periods (top), rate (second)
// Need: (1 - (1+rate)^-periods) / rate
// one = 1.0
one.0 = 0
one.1 = $3F
one.2 = $80
one.3 = 0
one.4 = 0
one.5 = 0
one.6 = 0
one.7 = 0
one.8 = 0
one.9 = 0
// Save rate for later division
memcpy(stackRegs[2], stackRegs[1], t_extended) // Copy rate to stack[2]
// Negate periods: -periods
err = neg
if err < 0
_drop(0)
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
fin
// Stack now: -periods, rate, rate, ...
// Compute (1 + rate)
memcpy(stackRegs[3], @one, t_extended) // Push 1.0
stackRegs[0], stackRegs[1], stackRegs[2], stackRegs[3] = stackRegs[3], stackRegs[1], stackRegs[0], stackRegs[2]
// Stack: 1.0, rate, -periods, rate
err = add // 1 + rate
if err < 0
_drop(0); _drop(0)
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
fin
// Stack: (1+rate), -periods, rate, ...
swap // Stack: -periods, (1+rate), rate, ...
err = powXY // (1+rate)^-periods
if err < 0
_drop(0)
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
fin
// Stack: (1+rate)^-periods, rate, ...
// Compute 1 - (1+rate)^-periods
memcpy(stackRegs[2], @one, t_extended)
stackRegs[0], stackRegs[1], stackRegs[2] = stackRegs[2], stackRegs[0], stackRegs[1]
// Stack: 1.0, (1+rate)^-periods, rate, ...
err = sub // 1 - (1+rate)^-periods
if err < 0
_drop(0)
if !saneInit; initSANE; fin
sane:saveZP()
return sane:restoreZP(_drop(_swap(sane:op2ELEM(FFEXT|FOANNUIT, stackRegs[0], stackRegs[1]))))
fin
// Stack: (1-(1+rate)^-periods), rate, ...
// Divide by rate
swap
return div
end
//==============================================================================
// RANDOM NUMBER GENERATION (SANE fallback)
//==============================================================================
def randNum(pSeed)
if !saneInit; initSANE; fin
sane:saveZP()