Memory Reference Instructions

Special Memory Considerations

All program execution takes place in lower memory, 0-77777.
A few addresses in page zero have a special relationship with the nova hardware.

  • Addresses 0 and 1 == are for handling device interrupts. Taken care of by IRIS. Do not use.
  • Addresses 20-27 == Auto Increment cells. In IRIS, only 26 and 27 are available for this.
  • Addresses 30-37 == Auto Decrement cells. In IRIS, only 30 and 31 are available for this.
  • Auto Increment / Decrement is discussed below.

Memory Addressing

An executing program has 3 ways it can reference a memory location. All 3 methods are available at all times to the program. Examples will be given later. The methods are:

  • Page zero addressing. Locations 0-377 are directly addressable at all times.
  • Addressing relative to the current executing instruction. Addresses plus or minus 177 from the current executing command are directly addressable. It is usually stated as “present plus x” or “present minus x”.
  • Addressing relative to AC2 or AC3. Often called ‘indexed memory reference’ or ‘base register addressing’. Both AC2 and AC3 have the ability to be used as indexes into memory. Given a value in either AC2 or AC3, all locations plus or minus 177 from that address value are addressable. So if AC2 = 12000, then a memory reference instruction could address any word from 11601 to 12177 by using AC2 as an index.

Mem. Ref. type 1 — Load or Store Accumulator

To load a value from memory into an accumulator:

LDA 1,54    ; load AC1 with contents of loc. 54 in page zero
LDA 3,.+12  ; load AC3 with contents of present loc + 12
;   ( if curr. loc. is 11204, this will load contents of 11216 )
LDA 2,.-1  ; load AC2 with contents of previous word (relative to curr loc)
LDA 0,4,2  ; load AC0 with contents of (AC2) + 4
;    ( if AC2 contains 66100, this will load contents of 66104 )

To store the value of an accumulator at a location in memory:

STA 1,203  ; store AC1 in page zero address 203
STA 3,.-1  ; store AC3 at present minus 1 (the word prev. to this one)
STA 2,-4,3  ; store AC2 at (AC3) minus 4.
;   ( if AC3 contains 66140, this would store AC2 at 66134 )

Mem. Ref. type 2 — Increment and Decrement Values in Memory

While most computations occur in the AC’s, there are 2 instructions that modify memory directly. These are called “Increment and Skip if Zero” and “Decrement and Skip if Zero”.

ISZ 204    ; incr loc 204 in page zero
ISZ .+52   ; incr loc 52 words away
ISZ 7,2    ; incr (AC2) + 7
DSZ 205    ; decr loc 205 in page zero
DSZ .-7    ; decr loc 7 words prior
DSZ -11,3  ; decr loc (AC3) - 11

Note that if in any of the above instances the value of the item in memory went to zero as a result of the command, the CPU would SKIP then following instruction. One common use of this is in constructing a loop with a specific count. An example will be given at the end of this page.

Mem. Ref. type 3 — Jump to Another Memory Address

This is how programs branch to other routines. There are two ways to get to another location. (a) a simple jump to another location, via ‘JMP‘ command; or (b) jump and save return, via ‘JSR‘ command, which means jump to a location after saving the address of the next instruction in AC3. This is a special function of AC3 only. This is how subroutines are called, because the called routine can return to the caller by referencing AC3.

JMP 322  ; jump to a routine at 322 in page zero
JMP .+22 ; jump 22 words from current location
JMP 0,3  ; jump to the address in AC3 (plus zero)
JSR 322  ; jump to the routine at 322 and pass return address
JSR .-41 ; jump 41 words back and pass return address in AC3
JSR 6,2  ; jump to (AC2) + 6 and pass return address

When a subroutine requires the caller to use a JSR command, the called routine can return by doing JMP 0,3 (jump to the address in A3, with no offset). Sometimes the subroutine will do a skip return (similar to RETURN +1 in Basic) for normal completion and a non-skip return for an error, like this:

JSR <routine address> ; jump to routine, AC3 pointing to next command
JMP <error routine>   ; if subroutine does JMP 0,3 it will return here
<next command>        ; if subroutine does JMP 1,3 it will return here
<subroutine commands>
JMP 0,3 ; error return
JMP 1,3 ; normal return

In IRIS, all device drivers, discsubs, and system calls are written as subroutines that are accessed via a JSR to the routine.

Indirect Addressing.

This concept is often unfamiliar to programmers new to machine-level coding, but it is not really that complicated. To say it in general terms, it is often easier to access a value via a pointer to that value, rather than attempt to access that value directly. Any of the memory reference instructions can make use of indirect addressing ( in the syntax this is done by including an ‘@’ sign). A couple of examples will help.

(1) IRIS contains a lot of short subroutines that collectively provide a library of commonly needed functions. It would not be possible for a program to have direct access to all of them. So those that are used most often have a pointer to them that is located in Page zero. For example, location 122 points to a routine that simulates binary multiplication of two accumulators. When a program needs to multiply 2 numbers, it can do so by jumping to the routine which 122 points to. One approach would be to LDA 3,122 followed by JSR 0,3. That would jump to the routine pointed to by AC3 and also pass the return address of the calling routine. But an easier way is to refer to location 122 and specify “indirect addressing.” It looks like this: JSR @122 ( read it as “JSR indirect 122” ) which then jumps to the multiply routine and passes its return address in AC3. Many system calls are done via indirect addressing through a page zero pointer this way.

(2) Imagine an important value ‘X’ being stored at location 11201 in a relatively large program that always runs in the same place in memory. An instruction at location 14744 wants to load the contents of 11201 into AC1, but it’s too far away for relative addressing. One solution is to put a pointer to that value nearby. If for example loc. 14754 contains the value 11201, then the instruction at 14744 can access the value at 11201 by referring to the address in 14750 and specify a level of “indirect” addressing. Thus:

14744:  LDA 1,@.+10  ; load AC1 indirect, present plus 10
;  do not load contents of 14754, but use 14754 as the address of what to load

(3) Consider having a list of items to process, each item one word in size. If we store a pointer to that list in an accessible location, we can use it to process the list. For example:

LDA 1,@201  ; get item from list which 201 points to
ADD 1,1     ; double it (add AC1 to AC1)
STA 1,@201  ; put it back
ISZ 201     ; increment the pointer
JMP .-4      ; loop

So if 201 contains 11000 to begin with, then the contents of 11000 and all following locations will get doubled. Obviously, more needs to be added to the above routine or else it will modify all of memory! But the idea is to show how using a pointer and indirect addressing can be useful.

The syntax for indirect addressing using AC2 or AC3 indexes looks a little different. For example:

Given a list of pointers like this ( address: contents )
11000: 12320
11001: 66000
11002: 42400
11003: 14321
and a pointer to the list in AC3 ( i.e. AC3 = 11000 ), then ...
LDA 1,2,3  ; load AC1 with the contents of 11002 ( AC1 will == 42400 )
; displacement 2 from AC3 gives an effective address of 11002 for the load
;    vs.
LDA 1,@2,3 ; load AC1 with the contents of 42400
;  "@" causes an add'l level of addressing, making 42400 the effective address

Introducing Labels in Assembly Syntax

In actual coding, it would be impossible to maintain a program if all addresses were hard-coded with literal page zero references and displacements relative to the current location. So the assembler allows the programmer to define labels explicitly in the code, and also implicitly by putting a label on an instruction. (Internally, the assembler truncates all labels to max 5 characters, although they can be written longer to improve readability of the code).

IRIS has already defined a great many labels which make the code much more readable. For example, page zero location 54 (in IRIS 7.5) contains the constant 215 which is an octal value of a carriage return. If every time a program needed that constant the programmer first had to know where it was and then code it as LDA 1,54 the process would be very tedious. And then if IRIS 8.0 moved things around, the new program would be loading the wrong constant, which means program maintenance would be extremely difficult. Instead, IRIS defines a label ‘C215‘ with the value 54 (in v7.5), so all the programmer needs to do is LDA 1,C215, and the assembler creates the machine instruction for LDA 1,54. The programmer does not care exactly where it is located in page zero. And if v8.0 defines C215 as being somewhere else, it does not matter. All that is needed is for the program to be assembled using the correct definitions for that version of IRIS.

Extending this further, IRIS defines terms for most of its available subroutines as well. For example the syntax BINMULTIPLY is defined as JSR @122, which gets to the proper routine in IRIS for binary multiplication. Given the proper calling sequence, the code might look like this:

LDA 0,WORD1   ; multiplicand
LDA 1,WORD2   ; multiplier
BINMULTIPLY   ; get 2-word product (more readable than JSR @122)
STA 0,RESLT   ; save lower 16 bits of result
STA 3,OVFL    ; save upper 16 bits, or zero if product was <= 177777

Much more readable than trying to hard code a direct interpretation of the machine code.
For a good overview of the definitions provided by IRIS, see the documents R75DEFS and R75PZ. These two files reside on LU0 in proper source form, so they can be included in any assembly. Partially annotated versions of these two files are also available here under Downloads >>> IRIS core listings.

Example Program

This short subroutine moves 40 words from one place to another. Notice the labels BEGIN, DST (destination), SRC (source), and CNTR (counter). These labels provide support for relative addressing without having to count how far away things are. The assembler gives these labels the value of the memory address for which the instruction is assembled. For example if the first instruction happens to be assembled at 11222 in memory, then ‘BEGIN’ is given the value 11222. When it then gets to the JMP BEGIN instruction which would be stored in location 11227, the assembler can calculate the relative distance to BEGIN and create the correct instruction ( 11222 – 11227 ==>  minus 5).

BEGIN: LDA 1,@SRC  ; assembles as LDA 1,@.+10 (load from list)
       STA 1,@DST  ; assembles as STA 1,@.+6 (store new area)
       ISZ SRC     ; assembles as ISZ .+6  (incr pointers)
       ISZ DST     ; assembles as ISZ .+4
       DSZ CNTR    ; decr count and skip when it hits zero (DSZ .+5)
       JMP BEGIN   ; assembles as JMP .-5
       JMP 0,3     ; return to calling routine
DST:   2000
SRC:   1000        ; pointer to 40 words from 1000-1037
CNTR:  40

A Caveat Regarding Relative Addressing

The above discussion says that relative addressing is plus or minus 177. If you read tech manuals, they will say relative addressing allows for any address minus 200 to plus 177. While that is technically correct, there are two good reasons to simply think in terms of plus or minus 177.

  • It is hard to remember if it’s plus 200 and minus 177, or plus 177 and minus 200 (smile).
  • If you are coding something that close to the limits, it might be time to rearrange things to make them a bit closer. Otherwise, any minor additions to a routine could put items out of reach, which will cause the assembler to generate an address error message. So it doesn’t matter if it’s 177 or 200, it’s already too far away.

Auto Increment / Decrement

This section is included for the sake of being thorough. But nothing in IRIS currently makes use of this feature.

( future )

Leave a Reply