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 ENDMACAn 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,#01234567produces the same machine code as:
LIL R5,#012345 ORIS R5,#67By 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 C567If 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 ENDThe 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 ENDHere, 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 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:
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.
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) ENDMACHere, 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.
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 ENDMACSimilarly, 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 ENDMACThe pretense supporting the existance of a compare immediate instruction is even more elaborate:
MACRO CMPI =x,=disp LEACC 0,x,-disp ENDMACThis 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.