22C:18, Lecture 8, Fall 1996

Douglas W. Jones
University of Iowa Department of Computer Science

A Practical Example of Macros

You should have noticed that the Hawk machine lacks a "Load Immediate Word" instruction that loads a full 32 bit word into a register. This effect can, of course, be achieved by the combination of LIL and ORIS, and the composite of these two instructions is only 6 bytes long, the same size that we would hope for in a LIW command. The problem is not one of efficiency, but one of programmer convenience.

The macro facilities introduced at the end of the last lecture solve this problem:

	MACRO	LIW =dst, =const
	  LIL	dst, const >> 8
	  ORIS	dst, const & #FF
	ENDMAC
An explanation is in order! The assembler directives MACRO and ENDMAC, taken together, inform the assembler that this sequence of lines, as a unit, defines a new assembler directive called LIW. The final part of the MACRO line defines the two formal parameters expected by the LIW directive. For purposes of macro definition, these are called dst and const.

The equals signs before each formal parameter tell the assembler that these parameters are to be passed by value -- that is, if the actual parameter is an expression, it should be evaluated and its value should be passed.

The body of the macro is an LIL instruction followed by an ORIS instruction, both referencing the same destination register. The assembler evaluates operators like >> (left shift) and & (bitwise logical and) at assembly time.

Having defined this macro, writing the following:

	LIW	R5,#01234567
produces the same machine code as:
	LIL	R5,#012345
	ORIS	R5,#67
By default, when the above LIW macro call is assembled, the assembly listing only shows the resulting data that goes into the Hawk memory, listed on the next line, as follows:
                             8          LIW     R5,#01234567
+000000: E501  2345          9
         C567
If you want to investigate how a macro is expanded by the assembler, you can use the SMAL assembler's LIST directive:
                             8          LIST    +1
                             9          LIW     R5,#01234567
                             9            LIL   5, 19088743 >> 8
+000000: E501  2345          9            ORIS  5, 19088743 & #FF
+000004: C567                9  END
The argument on the LIST directive tells the assembler how many levels of macro expansion should be listed. The lines of the macro body are listed immediately after the macro call, with the actual parameter substituted for the formal parameter; in this case, the actual parameters were passed by value, so the decimal values are shown. The END directive at the end of the macro body shows where the body ends.

Only one level is listed above. Asking for more levels can lead to confusing listings, but it may be helpful in understanding what is happening when macros are expanded:

                             8          LIST    +2
                             9          LIW     R5,#01234567
                             9            LIL   5, 19088743 >> 8
+000000: E501                9    H #E000 ! (5 << 8) ! ((74565 >> 16) & #FF)
+000002: 2345                9    H 74565 & #FFFF
                             9  END
                             9            ORIS  5, 19088743 & #FF
+000004: C567                9    H #C000 ! (5 << 8) ! (103 & #FF)
                             9  END
                             9  END
Here, each of the two macro calls in the body of the LIW macro are shown with their expansions, one as a sequence of two halfwords, and one as a single halfword. The definitions of these macros came from the /group/22c018/hawk.macs file.

The Condition Codes

The Hawk central processor contains a special register called the processor status word, or PSW, that reports on the results of certain operations. This register may be set with the PSWSET instruction (which copies from the named register to the PSW, and it may be inspected with the PSWGET instruction, which loads the PSW into the named register.

The most important fields of the PSW are the least significant bits, named N, Z, V and C. These are set by arithmetic instructions, and by some of the load from memory instructions, with the following meanings:

These are central to the construction of control structures in Hawk programs! The most common way that these bits are used is in the Hawk branch or Bcc group of instructions. The most obvious of these instructions are those that test, individually, the state of each of the condition codes. Thus, BCR does a Branch if the C bit is Reset, and BVS does a Branch if the V bit is Set.

To understand the branch instructions, note that they are useful in two distinct contexts. The LOADCC instruction (and other similar instructions) set the condition codes to indicate whether the value loaded was zero or negative. The following conditional branch instructions are obviously useful after a LOADCC instruction:

Recall that the same add instructions can be used to add 32 bit unsigned binary numbers with values between 0 and 4,294,967,295 or to add 32 bit two's complement numbers between -2,147,483,648 and 2147483647. The only difference between these two addition problems lies in how overflow is detected. After an ADD instruction, the following conditional branch instructions can be used for overflow testing: Recall that, under the two's complement number system, a-b = to a+not(b)+1. Because of this, the carry bit, after a subtraction operation, means that there was no borrow out of the sign bit. As a result, after a subtract operation, the following overflow tests apply: Subtraction can be used for comparison, and the SMAL Hawk assembly language supports CMP and CMPI instructions for comparison. These subtract their second operand from their first operand, setting the condition codes to reflect the result. After a CMP a,b instruction, the following condition code tests are available: There is a problem in the above list of useful conditional branch instructions! The Hawk Bcc group of instructions only uses a 4-bit field to indicate which conditional branch instruction is to be used, allowing for only 16 distinct instructions. The above list of instructions includes 18 instructions! Furthermore, our assembly language supports BR, an unconditional branch instruction, making 19.

The solution lies in the fact that some of the condition tests described above are redundant! BZR (branch on zero reset) is the same as BNE (branch on not equal), for example, and BCS (branch on carry set) is the same as (BLTU) branch on less than unsigned.

Branch Destination Addresses

The branch instructions have an 8-bit destination address field. Thus, a branch instruction can only branch to one of 256 possible locations. Inside the Hawk CPU, the 8 bit constant giving the destination of the branch instruciton is used as follows:

	pc = pc + sxt(const << 1)
The constant is first shifted left one place, then sign extended to 32 bits, then added to the program counter. The left shift is done so that the branch will only branch to halfword addresses, since programs are always written in terms of halfword instructions. The sign extension prior to adding to the program counter means that the branch destination can be any instruction within 128 instructions of the branch, forward or backward.

Programming in terms of this construction would be awkward, so the assembler reverses this, allowing SMAL Hawk assembly language programs to be written in terms of the actual memory address of the branch target. The following macro, for example, is used to define the unconditional branch instruction:

	MACRO BR =const
	  W #B800 ! (const - (.+2) >> 1 & #FF)
	ENDMAC
Here, it takes the destination address (const), subtracts .+2, the address of the next successive instruction, shifts one place and truncates to 8 bits to construct the actual branch displacement field. The . itself stands for the assembly location counter, which is not the same as the program counter. Specifically, . holds the address where the branch instruction will be stored in memory, while within the hardware to execute the branch instruction, the program counter value that matters is the value after fetching the instruction, and the program counter is always incremented as a side effect of the fetch operation.

As a programmer, you can usually ignore this complexity! The assembler hides it at assembly time, and the Hawk emulator's disassembler hides it at run-time, should you need to look at the code in memory. Only when you actually look at the machine instructions dumped in hex (or at the assembly listing) will you see the evidence of this confusion.

Lies, all Lies (and Macros)

The SMAL Hawk assembly language provides extra symbolic names for many instructions; these are defined using macro definitions at the end of the /group/22c018/hawk.macs file. Thus, while we encourage the Hawk assembly language programmer to believe that the machine has both a BZS and a BEQ instruciton, in fact, the hardware only supports one instruction, and the other is defined as:

	MACRO BEQ =x
	  BZS x
	ENDMAC
Similarly, while we the Hawk assembly language encourages programmers to program as if the hardware supported a CMP instruction, in fact, this is defined in terms of the SUB instruction, taking advantage of the fact that results stored in register 0 are discarded. Our pretense of a CMP instruction actually rests on the following macro:
	MACRO CMP =s1,=s2
	  SUB 0,s1,s2
	ENDMAC
The pretense supporting the existance of a compare immediate instruction is even more elaborate:
	MACRO CMPI =x,=disp
	  LEACC 0,x,-disp
	ENDMAC
This uses the hardware's "load effective address and set condition codes" instruction. The hardware uses the same logic it uses to compute addresses in the LOAD instruction, adding the contents of a register to a constant, but instead of going to memory, the LEA instruction simply puts the resulting memory address in the destination register. LEACC has the side effect of setting the condition codes, and our macro negated the constant and discarded the result!

The only time Hawk programmers will really need to know that these instrucitons are really not present in the architecture is when inspecting the result of the disassembly of a Hawk program. The Hawk emulator includes a disassembler to support its memory-viewing feature, and when that disassembler converts the contents of memory back to assembly language, it only converts as far as the base machine instruction, with no knowledge of the macros discussed above.