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