Platform-Specific Techniques

Ophis is intended to produce cross-assembled binaries that will run in a variety of contexts. The expectation is that most users will be writing for emulated versions of hardware from when the 6502 chip was current, and producing files either for those emulators or for devices that will transfer the results to real hardware. This chapter describes the support routines and examples to make those tasks easier.

The Commodore 64 and VIC-20

In a real sense, the Commodore 64 is the "native" target platform for Ophis. It was the first platform targeted and it's the one that has received the most additional support. It's also one where the developer needs to take the most care about exactly what kind of program they are writing.

Using LIBBASIC64

The 6502's arithmetic capabilities are rather limited. To counteract this, BASICs of the era did floating point in software and gave BASIC programmers the full suite of arithmetic operations. These operations are largely unavailable to machine language programmers.

The libbasic64.oph library is an attempt to address this. It is currently considered highly experimental, but initial results are very promising.

BASIC stores floating point numbers in a five-byte format, but translates them into a seven-byte format to do actual work in two Floating Point Accumulators (FAC1 and FAC2). Ophis will let you specify 5-byte constants with the .cbmfloat directive, which takes a string and produces the requisite five-byte value.

The floating point functions in BASIC all operate on FAC1 and are relatively reliable. The functions abs_fac1, atn_fac1, cos_fac1, exp_fac1, int_fac1, log_fac1, rnd_fac1, sgn_fac1, sin_fac1, and tan_fac1 are all provided. Routines that touch the FACs tend to be extremely finicky. This system defines a set of macros and routines to manage that for you:

  • `f_move dest, source: Copy a five-byte floating point value from source to dest.

  • `fp_load src: Loads FAC1 with the floating point constant specified by src.

  • `fp_store dest: Saves the value of FAC1 to the named memory location.

  • `fp_print src: Prints out the value of FAC1 to the screen. You may want to call int_fac1 first to round it. Unlike BASIC's PRINT statement, this routine will not bracket the number with blanks.

  • `fp_read ptr: Attempts to convert a string to a floating point value in FAC1, in a manner similar to BASIC's VAL function.

  • `fp_add operand: Adds the operand to FAC1.

  • `fp_subtract operand: Subtracts the operand from FAC1.

  • `fp_multiply operand: Multiplies the operand by FAC1.

  • `fp_divide operand: Divides FAC1 by the operand.

  • `fp_pow operand: Raises FAC1 to the operand's power.

  • `fp_and operand: Juggles floating point-to-integer conversions to do a bitwise AND.

  • `fp_or operand: Likewise, but for OR.

  • jsr randomize: Calls RND(-TI) and leaves the (useless) result in FAC1. This seeds BASIC's random number generator with the number of clock ticks since poweron.

  • jsr rnd: Calls RND(1) and leaves the result in FAC1, providing a random number between 0 and 1.

  • jsr fac1_sign: Loads the SGN(FAC1) into the accumulator. This will be $01 if the accumulator is positive, $00 if it is zero, and $FF if it is negative. This routine is useful for branching based on the result of a floating point computation.

Other functions are available, but their preconditions are hazier. The source file is commented with the current state of knowledge.

To see some of these functions in action, the examples directory includes a program kinematics.oph, which reads numbers in from input and computes trajectories based on them.