wickensonline.co.uk Retrochallenge 2009 Summer Challenge Entry Mbrt1 |
Retrochallenge 2009
Summer Challenge
Mark Wickens
15-Jul-2009
Macro Coding, Floating Point Numbers
Here we go again! Nothing in the world of VAX Macro coding goes smoothly it
seems! Actually, it's not been too bad, but boy do you have to concentrate!
Typically I'm starting coding around 9pm which gives me a window of about an
hour and a half (if I'm lucky) before the brain goes to complete mush.
I think the majority of the algorithm I quoted in my Research page has been
implemented, however I've hit a, how do they put it, 'natural conclusion' to
this evening's proceedings. How to output a floating point number to the
terminal. I thought I'd cracked it in the OTS$CNVOUT library routine, but alas
not. The documentation for said routine (pulled up on VMS using the command
HELP RTL_routines OTS$ OTS$CNVOUT) says that it'll convert a floating point
value passed to it into a character string representation using the 'Fortran E
Format'. This, as it turns out, is appaulingly bad at displaying small numbers.
For example, 0.9999999 comes out as 0.E+1. However, having re-read the
documentation as I'm writing this entry it would appear that I've been missing a
crutial argument - argument 3 is the number of digits in the fractional part.
Aha, EUREKA! Of course in any assembly language you can do pretty much anything
you like. For example, the code I was using is:
.ENTRY PRTFLOAT,^M<>
MOVL @4(AP),FEF_VAL
; Convert float to string
MOVW #FEF_MAX_SIZE, FEF_DESC
PUSHAQ FEF_DESC
PUSHAQ FEF_VAL
CALLS #2, G^OTS$CNVOUT
; Output string to terminal
PUSHAQ FEF_DESC
CALLS #1, G^LIB$PUT_OUTPUT
RET
This code calls the routine OTS$CNVOUT with two arguments pushed on the stack
(in reverse order), FEF_DESC (a character string descriptor into which the
converted number will be stored) and FEF_VAL the address of the value to
convert. The #2 in the CALLS even suggests that this routine takes two
arguments, even though that's come straight out of my head (by not reading the
documentation carefully).
The correct code should be:
.ENTRY PRTFLOAT,^M<>
MOVL @4(AP),FEF_VAL
; Convert float to string
MOVW #FEF_MAX_SIZE, FEF_DESC
PUSHL #6
PUSHAQ FEF_DESC
PUSHAQ FEF_VAL
CALLS #3, G^TS$CNVOUT
; Output string to terminal
PUSHAQ FEF_DESC
CALLS #1, G^LIB$PUT_OUTPUT
RET
This includes the magic third argument (pushed first of course) that specifies
how many digits should appear after the fullstop.
To be fair to me, some of these issues originate from the choosing of G_FLOAT as
the floating point number format to use originally in this code. G_FLOAT is a 64
bit floating point representation. I soon realised, however, that 64-bit
floating point numbers were going to be a royal PITA to work with. Basically you
can't do something like: PUSHG G_FLOAT_ADDRESS - there is no such instruction to
push a 64-bit value, so I'd have had to resort to treating the numbers as two 32
bit longs in a lot of the code - not good for readability or sanity - and
certainly not good for a first program. So I resorted to F_FLOAT which are
32-bit and a lot easier to pass around.
In other news, I finally managed to get copies of the disk images from the VAX
I'm using VMS on through NetBSD (on another VAX) onto my laptop. Pulling a 4.3GB
disk image off a SBB via NetBSD using dd written to an NFS partition on the Dell
7500 laptop (the one that runs the retrobbs) took forever, probably about four
hours in the end. The plan was to use the copied images with SIMH but having
just looked at the configuration file I may be out of luck (maybe I should have
done that beforehand!)
Anyway, without further ado, the program so far (this was pulled into ALLIN1
using the key sequence GOLD-G (Get Document) followed by VMS (Get a VMS file)
then the VMS filename: DISK$USERS:[MSW.RC.MBG.MBRT1]MBRT1.MAR. The next stage is
to verify the code is working correctly then clean up the output so it puts a
single line out containing the real and imaginary axis values and a colour at
that value. I'll need to look into getting gnuplot installed on the VAX shortly
and hopefully I will have a picture (although it may not be pretty!)
.TITLE Chaotic Dusty Curls Generation Using VAX Macro
;++
; This program will produce a set of coordinate and value tuples
; for a window on a Mandelbrot set to the standard output.
;
; References:
; VAX Macro and Instruction Set Reference Manual
; VMS Run-Time Library (LIB$) Reference Manual
; VMS System Services Reference Manual
; VMS Run-Time Math Library (MTH$) Reference Manual
; Sara Baase 'VAX Assembler Language Programming'
;
; Author : Mark Wickens
; Date : 11-Jul-2009
;--
; ALGORITHM: Calculation of chaotic dusty curls
; Variables: rz, iz = real, imaginary component of complex number
; i = iteration counter
; u, z = complex numbers
; Note: Choose one of the three different tests for divergence.
;
; u = -.74 + .11 i;
; DO rrz = -1 to 1 by 0.001;
; DO iiz = -1 to 1 by 0.001;
; z = cplx(rrz, iiz);
; InnerLoop: DO i = 1 to 100;
; z = z**2 + u;
; rz = real(z); iz = imag(z);
; if sqrt(rz**2 + iz**2) > 2 then leave InnerLoop;
; END;
; color = i;
; if convergence_test = 1 then
; if rz**2 + iz**2 > 4 then PRINTDOT(rrz,iiz,color);
; if convergence_test = 2 then
; if ((abs(rz)<2) & (abs(iz)<2)) then PRINTDOT(rrz,iiz,color);
; if convergence_test = 3 then
; if rz**2+iz**2>4 & mod(i,2) = 0 then PRINTDOT(rrz,iiz,color);
; END;
; END;
;--
; Define symbols, constants, etc. used in this module
; Debugging aids
; Debug the inner loop by outputting each value calculated for sqrt(rz**2 + iz**2)
DEBUG_INNER = 1;
; Offset in bytes in real/imaginary pair of the real and imaginary value
COMP_REAL = 0;
COMP_IMAG = 4;
.PSECT MBRT1_RWDATA WRT,NOSHR,NOEXE,PAGE
.ALIGN LONG
; Real and imaginary values for constant 'u'
U: .F_FLOATING 0.74, 0.11
; Convergence test 1, comparison value
CONV1_MAX: .F_FLOATING 4.0
; Real axis dimensions and increment
RRZ_MIN: .F_FLOATING -1.0
RRZ_MAX: .F_FLOATING 1.0
RRZ_INC: .F_FLOATING 0.001
POW2: .LONG 2
; Imaginary axis dimensions and increment
IIZ_MIN: .F_FLOATING -1.0
IIZ_MAX: .F_FLOATING 1.0
IIZ_INC: .F_FLOATING 0.001
TWOF: .F_FLOATING 2.0
; Number of iterations to perform in convergence loop
LOOP_MAX = 100;
; Convergence test to use
CONV_TEST_VALUE = 1;
; Output buffer maximum size
MAX_SIZE = 132
; Fortran E Format buffer max size
FEF_MAX_SIZE = 16
; Setup a text buffer for use as an output line
OUT_BUFFER: .BLKB MAX_SIZE
OUT_DESC: .WORD MAX_SIZE
.BYTE DSC$K_DTYPE_T
.BYTE DSC$K_CLASS_S
.ADDRESS OUT_BUFFER
FEF_BUFFER: .BLKB FEF_MAX_SIZE
FEF_DESC: .WORD FEF_MAX_SIZE
.BYTE DSC$K_DTYPE_T
.BYTE DSC$K_CLASS_S
.ADDRESS FEF_BUFFER
FEF_VAL: .F_FLOATING 0.0
; Allocate a longword to store any failure status value we might see
ERROR_CODE: .BLKL 1
; Real axis loop counter
RRZ: .BLKF 1
; Imaginary axis loop counter
IIZ: .BLKF 1
; Z Complex value (real/imaginary pair)
Z: .BLKF 2
; Complex temporary
COMP_TEMP: .BLKF 2
RZ: .BLKF 1
IZ: .BLKF 1
RZPOW2: .BLKF 1
IZPOW2: .BLKF 1
SQRTZ: .BLKF 1
ZPOW2: .BLKF 1
COLOUR: .BLKF 1
; Convergence test
CONV_TEST: .BYTE CONV_TEST_VALUE
.PSECT MBRT1_CODE NOWRT,SHR,EXE,PAGE
.ALIGN PAGE
;*******************************************************************************
; Program Entry Point
;*******************************************************************************
.ENTRY MBRT1,^M<R2,R3,R4,R5,R7> ; entry point, save R7 automatically
; Initialize
START:
MOVB #SS$_NORMAL, ERROR_CODE ; error code to normal
MOVB #CONV_TEST_VALUE, CONV_TEST ; convergence test to use
MOVF RRZ_MIN, RRZ ; init real axis value
RRZ_LOOP:
MOVF IIZ_MIN, IIZ ; init imaginary axis value
; Set value of complex variable Z, stored in R2
IIZ_LOOP:
MOVAF Z, R2
MOVF RRZ, COMP_REAL(R2)
MOVF IIZ, COMP_IMAG(R2)
; Initialize inner loop counter, stored in R3
MOVL #1, R3
; Within loop
LOOP:
; z**2
PUSHL POW2
PUSHL COMP_IMAG(R2)
PUSHL COMP_REAL(R2)
CALLS #3, G^OTS$POWCJ
MOVAF COMP_TEMP, R4
MOVF R0, COMP_REAL(R4)
MOVF R1, COMP_IMAG(R4)
;R4 now contains address of z**2, COMP_TEMP
;; z = z**2 + u
MOVAL U, R5
ADDF3 COMP_IMAG(R4), COMP_IMAG(R5), COMP_IMAG(R2)
ADDF3 COMP_REAL(R4), COMP_REAL(R5), COMP_REAL(R2)
; rz = real(z)
MOVF COMP_REAL(R2), RZ
; iz = imag(z)
MOVF COMP_IMAG(R2), IZ
MULF3 RZ, RZ, RZPOW2
MULF3 IZ, IZ, IZPOW2
ADDF3 RZPOW2, IZPOW2, R5
; R5 = rz**2 + iz**2
MOVF R5, ZPOW2
PUSHAF ZPOW2
CALLS #1, G^MTH$SQRT
MOVF R0, SQRTZ
; R0 = SQRT(rz**2 + iz**2)
.IF DEFINED DEBUG_INNER
PUSHAL SQRTZ
CALLS #1, G^PRTFLOAT
.ENDC
CMPF SQRTZ, TWOF
BGTR EXIT_LOOP
CMPL #LOOP_MAX, R3
BEQLU EXIT_LOOP
ADDL #1, R3
JMP LOOP
EXIT_LOOP:
CVTLF R3, COLOUR
JSB CONVERGENCE_TEST
; IIZ = IIZ + IIZ_INC
ADDF3 IIZ, IIZ_INC, IIZ
; IF IIZ < IIZ_MAX THEN GOTO iiz_loop
CMPF IIZ, IIZ_MAX
BGTR EXIT_IIZ_LOOP
JMP IIZ_LOOP
EXIT_IIZ_LOOP:
ADDF3 RRZ, RRZ_INC, RRZ
; if rrz < rrz_max then goto rrz_loop
CMPF RRZ, RRZ_MAX
BGTR EXIT_RRZ_LOOP
JMP RRZ_LOOP
CONVERGENCE_TEST:
CMPF R5, CONV1_MAX
BLEQ CONVERGENCE_TEST_END
JSB PRINTDOT
CONVERGENCE_TEST_END:
RSB
PRINTDOT:
PUSHAL RRZ
CALLS #1, PRTFLOAT
PUSHAL IIZ
CALLS #1, PRTFLOAT
PUSHAL COLOUR
CALLS #1, PRTFLOAT
RSB
EXIT_RRZ_LOOP:
; Normal Exit
MOVL #SS$_NORMAL, ERROR_CODE
BRB COMMON_EXIT
FAILURE_EXIT:
MOVL R0, ERROR_CODE
MOVW #MAX_SIZE, OUT_DESC
PUSHAQ OUT_DESC
PUSHAW OUT_DESC
PUSHAL ERROR_CODE
CALLS #3, G^LIB$SYS_GETMSG
PUSHAQ OUT_DESC
CALLS #1, G^LIB$PUT_OUTPUT
COMMON_EXIT:
$EXIT_S ERROR_CODE
.ENTRY PRTFLOAT,^M<>
MOVL @4(AP),FEF_VAL
; Convert float to string
MOVW #FEF_MAX_SIZE, FEF_DESC
PUSHL #6
PUSHAQ FEF_DESC
PUSHAQ FEF_VAL
CALLS #3, G^OTS$CNVOUT
; Output string to terminal
PUSHAQ FEF_DESC
CALLS #1, G^LIB$PUT_OUTPUT
RET
.END MBRT1