Home | People | Curriculum
| Projects | Resources
| Media |

**HERA Architecture and Assembly Language (version 0.99)**

The HERA (Haverford educational RISC architecture) processor has seventeen 16-bit registers: a Program Counter (PC) which is only involved in control-flow operations; twelve general-purpose registers (numbered 1 - 12), a Stack Pointer (SP or 15), Frame Pointer (FP or 14), and Old Frame Pointer (OFP or 13) that are used for function calls (and may be used elsewhere); and a Zero register (number 0) that always has the value 0, letting us provide a comparison (CMP), negation (NEG) and other things with a minimal set of instructions.

Arithmetic operations may produce a value, and all but SAVEF set the arithmetic condition flags, which are 0 (or s, for sign), 1 (or z, for zero), 2 (or v, for overflow), and 3 (or c, for carry). These flags may then be used in a branch instruction, and the value of the carry flag may be used in subsequent arithmetic operations. There is an additional 4th flag known as "carry-block". When carry-block is set the carry is not used during arithmetic operations. This flag can be saved, restored, or explicitly modified, but is not affected by arithmetic and cannot be used in a branch.

A logical shift left by 1 bit sets all flags exactly as they would be set if we added a number to itself: c is set iff the number was negative (bit 15 was true), and v is set iff bit 14 xor bit 15 was true. A logical shift right by 1 bit sets c to the old bit 0 and shifts the carry into the leftmost position. For a shift of multiple positions, the resulting flags may be anything, and the carry is only shifted in once for LSL.

This architecture can address 2^{16} 16-bit bytes of
memory, using the LOAD and STORE instructions. Memory transfer and control
flow operations do not affect flags.

Assembly Language Conventions

In the definitions below, the letters a, b, d, and t refer to
four-bit register numbers, and the operation works on the values in the given
registers: SUB(3,1,2) subtracts the contents of register 2 from that of
register 1 and puts the result in register 3. The letter c refers to the
carry bit (0 if unset, 1 if set); c^{*} is 1 iff carry is set and
carry-block is clear. The letter f refers to the (unsigned) 4-bit flags
register [cvzs]. Italicized letters such as *o*, *v*, *m*, and *u*
are constants, and are treated as unsigned (positive) quantities during
arithmetic: SETLO(1, 9) sets register 1 to the value 9, and INC(1,15) adds 15
to register 1 (rather than adding -1, which has the same 4-bit binary representation).

For the branching instructions (BR, BRN, and BR2), x, x1 and x2 refer to two-bit flag numbers; the terms flag_s, flag_z, flag_v, and flag_c may also be used.

True instructions (in normal font) each produce a single 16-bit instruction; pseudo-instructions (italicized) are converted by the assembler into a sequence of machine-language instructions.

Typical Stack Frame Layout (lower addresses lower on page)

SP --> Space above frame for constructing new frames for function calls

Local variables, temporaries, and saved registers

Parameters

Return value

Static link, if used: 1 word

Control link (saved old fp): 1 word

FP --> Return address: 1 word

Idioms

Single-Precision Arithmetic: (e.g. make R1 the single-precision sum of R2...R4)

SETCB() // no need for carry flag in single-precision

ADD(1,0,2) // R1 = R2

ADD(1,1,3) // R1 = R1 (i.e. value of R2) + R3

ADD(1,1,4)

Double-Precision Arithmetic: (e.g. Make [R1 R2] the sum of [R3 R4] and [R5 R6])

CLCCB() // we need add-with-carry, and carry initially false

ADD(2,4,6) // R2 = R4+R6, carry set if necessary

ADD(1,3,5) // R1 = R3+R5 plus carry, if set

Function Call with parameters and return value on stack, "callee-save" of registers:

// Call the function "times2" with R1 as a single-precision param., add 1 to result

STORE(4,SP,1) // Store R1 at SP+4 (conventional location of 1st param.)

CALL(12,5,times2) // Call times2, frame size initially 5, using R12

LOAD(3,SP,2) // Load R2 from SP+3 (conventional return area)

INC(2,1) // Increment R2 -- now R2 == 2*R1+1

Function Body (as above):

// Body of the "times2" function: 1 single-precision param, 1 local var, 1 saved reg.

// Assumes we normally run in carry-blocked (single-precision) mode

LABEL(times2)

STORE(1,FP,OFP) // Store OFP at FP+1 (not needed if no further calls)

INC(SP,2) // Make space for 1 local, 1 saved reg

STORE(6,FP,1) // Store R1 at FP+6

LOAD(4,FP,1) // R1 = 1st parameter (FP+4)

STORE(5,FP,1) // Save R1 in local variable at FP+5 (no real reason)

ADD(1,1,1) // Double R1

STORE(3,FP,1) // Save R1 in return area (FP+3)

LOAD(6,FP,1) // Get back the value of R1 that we saved 5 lines ago

LOAD(1,FP,OFP) // Get back our old frame pointer before doing the return

RETURN() // Do the actual return

Arithmetic (& Logic) Instructions

__ Name Args Meaning (RTL) Notes__

SETLO (d,*v*) d = *v* & 0xff Sets
high 8 bits to 0, low 8 to *v*

SETHI (d,*v*) d = d | (*v *<<
8) Leaves low 8 bits alone

* SET (*d*,v) set *d* to value v
using SETLO(*d*, v & 0xff); SETHI(*d*, v >> 8)*

ADD (d,a,b) d = a + b + c^{*} Add (with
carry (if not blocked))

SUB (d,a,b) d = a - b - c^{*} Subtract
with Carry (borrow)

*CMP* *(*a*,*b*)* *set
flags for *a*-*b-c^{*}*, discard result:
SUB(0,a,b)*

*NEG* *(*d*)* *negate *d*:
SUB(d,0,d)*

UMULLO (d,a,b) d = (a * b) & 0xffff Low 16 bits of a*b (unsigned)

UMULHI (d,a,b) d = (a * b) << 16 High 16 bits of a*b (unsigned)

AND (d,a) d = d & a

OR (d,a) d = d | a

NOT (d) d = not d

XOR (d,a) d = d xor a

NAND (d,a) d = d nand a

* ZERO (*d*) set *d* to 0: AND(*d*,0,0)*

SETF (*m*,*v*) f = (f & not *m*)
| (*v*&*m*) Set/Clear flags with mask m

* CLCCB () clear carry & carry-block: SETF(16+8,0)*

* SETCB () set carry-block flag: SETF(16,16)*

SAVEF (d) d = f Save flag values in register d

RSTRF (a) f = a & 7 Restore flags saved in a

*FLAGS* (a) *set
flags for a+0, discard result: SETF(8,0); ADD(0,0,a)*

INC (d,*u*) d = d + *u* + c^{*}
(note: *u* is 4-bit unsigned (0-15)

DEC (d,*u*) d = d - *u* - c^{*}

LSL (d,*u*) c d = [d c^{*}] << *u* Logical
shift left *u* bits w/ carry

LSR (d,*u*) d c = [c^{*} d] >> *u* Logical
shift right *u* bits w/ carry

Memory & Register Transfer Instructions

__ Name Args Meaning (RTL) Notes__

LOAD (*o*,a,d) d = M[a+*o*] Load a word
from memory

(note: *o* is 5-bit unsigned (0-31))

STORE (*o*,a,b) M[a+*o*] = b Store a
word into memory

Control Flow Instructions

__ Name Args Meaning (RTL) Notes__

BR (x*,*a) * *x: PC =
a Branch to a iff flag x is true

else: PC = PC+1

BRN (x*,*a) * *!x: PC =
PC Branch to a iff flag x is false

else: PC = PC+1

BR2 (x1,c2*,*a) x1 or !x2: PC = a Branch
to a iff flag x1 or ! flag x2

else: PC = PC+1 (note: x1==x2 always branches)

CAL (*o*,a) M[sp] = PC+1, Call function at
address a

PC = a

ofp = fp (note: ofp is overwritten)

fp = sp

sp = sp + *o* (note: *o* is 8-bit
unsigned (0-255))

RETURN () PC=M[fp] Return from a function

fp = ofp (note: may need to fix ofp first)

sp = fp

SWI () Software interrupt (sys. stack/reg)

RTI () Return from interrupt

NOP () Do nothing

HALT () PC = PC Halt the processor

*LABEL* *(l) Define
label l for branch*

* JUMP (*t,*l) Jump to l unconditionally: SET(*t*,l);
BR2(0,0,*t*) *

* BREQ (*t,*l) Branch if a=b in last CMP: SET(*t*,l);
BR(flag_z,*t*)*

* BRNE (*t,*l) Branch if a!=b in last CMP: SET(*t*,l);
BRN(flag_z,*t*)*

* BRLT (*t,*l) Branch if a<b in last
CMP: SET(*t*,l); BR(flag_n,*t*)*

* BRGE (*t,*l) Branch if a>=b in last
CMP: SET(*t*,l); BR2(flag_z,flag_n,*t*)*

*CALL* *(*t*,o,l) Call func. at
label l: SET(*t*,l); CAL(o,*t*)*

Page maintained by John Dougherty,
David Wonnacott, and Rachel
Heaton. |