The Hawk machine allows text output through a memory mapped display. This is the same display technology used by most PC and workstation class machines, although the Hawk version we are using is somewhat limited in its capabilities. Specifically, because our Hawk emulator is accessable using minimal dialup facilities, its display hardware is text-only.
Our display hardware is attached to the Hawk machine at address #FF000000. The hardware contains two read-only registers, one giving the number of rows and one giving the number of columns on the machine's display, and it contains the "video RAM" that is used to hold the text being displayed. The size of the video RAM depends on the window size you setup before starting the Hawk emulator. If you are using a standard 24 line by 80 character display, the Hawk emulator will directly control a 9 line by 80 character region on the bottom half of the display. This occupies 720 bytes or 180 words of the memory address space.
The hawk.macs file contains standard definitions for access to the the video RAM region of memory. These are:
DISPBASE = #FF000000 ; base address of the display interface DISPROWS = 0 ; offset of the rows register DISPCOLS = 4 ; offset of the columns register DISPTEXT = #100 ; offset of the start of video ramThus, memory location DISPBASE+DISPROWS contains the rows register, and the video ram bebgins at DISPBASE+DISPTEXT.
To access the video RAM, we need to get the address of the vide interface into a register. We can access the contents of an arbitary memory address by first loading that address in a register and then using that address. For example, to read the number of columns on our display, we could do this:
LIW R1,DISPBASE+DISPCOLS LOADS R2,R1This loads R2 using the address loaded in R1. If many different related memory locations are to be accessed, we can load the address once and then use it many times, using what is called indexed addressing. Here is an example:
LIW R1,DISPBASE LOAD R2,R1,DISPROWS LOAD R3,R1,DISPCOLSRecall that the long version of the LOAD instruction adds a 16 bit constant, DISPROWS or DISPCOLS in this example, to the contents of a register, and then uses the sum as a memory address. Thus, this example loads R2 with the contents of the rows register, and R3 with the contents of the columns register.
Sometimes, we need to compute an address without immediately referencing that address. For example, if we are about to make a sequence of modifications to the video RAM, we are likely to want to load the address of the video RAM first. We do this using the LEA or load effective address instruction, as follows:
LIW R1,DISPBASE LEA R4,R1,DISPTEXTFor comparison, purposes, here is a block of C code that does exactly the same thing as the above bits of assembly language text, using the variable names r1, r2, r3 and r4:
#define dispbase 0xFF000000 #define disprows 0 #define dispcols 4 #define disptext 0x100 { char* r1 = (char*)dispbase; int r2 = *((int*)(r1 + disprows)); int r3 = *((int*)(r1 + dispcols)); char* r4 = r1 + disptext ;Recall that the C type char is a variable holding one byte, while the type int is a variable holding one word. The type char* is a pointer to a byte, and the cast prefixes (char*) and (int*) the value of the following expression to be interpreted as a pointer to a character or a pointer to an integer. The prefix unary operator * causes a memory reference to the object pointed to by an expression (assuming that expression has a type that is a pointer type).
So, what should we do with this? Consider the following C code, in the context of the above:
char r5 = '-'; int r6; for (; r2 > 0; r2--) { /* for each row */ for (r6 = r3; r6 > 0; r6--) { /* for each col */ *r4 = r5; r4 ++; } }This bit of code puts rows times cols characters in memory, filling the video RAM with dashes.
Translating this to assembly code requires solving one little problem, that of assigning to a single byte in memory. The Hawk architecture allows byte addressing, but the basic load and store instructions only address whole words, ignoring the least significant two bits of each address.
The Hawk solution to byte addressing is provided by the stuff and extract instructions. The stuff instructions can be used to "stuff" a byte into position within a word, while the extract instructions can be used to extract one byte from a word. To fetch an arbitrary byte from memory, first fetch the word holding that byte, then extract it. To store a byte at an arbitrary location in memory, first fetch the word surrounding that location, stuff the byte into position and then put the word back.
This may appear unnecessarily expensive, particularly in the light of the fact that many computers have single machine instructions for loading and storing bytes. In fact, these instructions are fairly expensive! To store a byte in an arbitrary memory location, most machines do exactly what the Hawk machine does, loading the byte into a register somewhere in the CPU, stuffing the byte into position, and then storing the result. Thus, the Hawk simply makes the programmer explicitly state the work that the CPU would be doing anyway.
To store a byte in memory, for example, translating *r4=r5 (in C) to the SMAL Hawk assembly language, the following sequence of Hawk instructions will suffice.
LOADS R7,R4 STUFFB R7,R5,R4 STORES R7,R4
The C code given above can now be translated to SMAL Hawk code:
TITLE Program to fill the screen USE "/group/22c018/hawk.macs" MACRO LIW =dst, =const LIL dst, const >> 8 ORIS dst, const & #FF ENDMAC . = #1000 S . LIW R1,DISPBASE LOAD R2,R1,DISPROWS LOAD R3,R1,DISPCOLS LEA R4,R1,DISPTEXT LIS R5,'-' OUTERLOOP: ; for each row (decrementing R2) MOVE R6,R3 ; setup for inner loop INNERLOOP: ; for each column (decrementing R6) LOADS R7,R4 STUFFB R7,R5,R4 STORES R7,R4 ; store '-' m[R4] ADDSI R4,1 ; bump pointer into text ADDSI R6,-1 ; decrement inner loop counter BGT INNERLOOP ADDSI R2,-1 ; decrement outer loop counter BGT OUTERLOOP JUMP 0This example program runs correctly under the Hawk interpreter as currently installed on the IBM and Silicon Graphics machines we have, and it runs eccentrically but not completely incorrectly under the HP machines we have. The error in the HP machines should be correctled shortly (it's HP's fault, they moved to a new and faulty version of the curses package in their new operating system release).
The final JUMP 0 transfers control to locaton zero; as long as your program is running in the low half of ROM, this is legal, and, if you haven't experimented with breakpoints and other advanced debugging methods in the interpreter, it will halting the interpreter because, by default, there is a breakpoint set at location zero.