22C:18, Lecture 10, Fall 1996

Douglas W. Jones
University of Iowa Department of Computer Science

Hawk Output

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 ram
Thus, memory location DISPBASE+DISPROWS contains the rows register, and the video ram bebgins at DISPBASE+DISPTEXT.

Hawk Memory Addressing

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,R1
This 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,DISPCOLS
Recall 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,DISPTEXT
For 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.

Hawk Byte Addressing

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

Completing the Example

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	0
This 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.