AXA: Ahead X About-face (PRELIMINARY, 20190918 version)

The researchy nature of this project makes changes more likely throughout the semester. The 20190918 version is to be used for Assignment 1.

Instruction set design is hard. Prof. Dietz has designed dozens of instruction sets in the three decades he's been a professor, and it still isn't easy for him to get things right. Thus, rather than giving you complete freedom to design your own instruction set, we're going to walk through the design logic for a hopefully reasonably well-crafted one that he built specifically for Fall 2019 EE480. However, this design is not complete -- each student must devise their own encoding of the instructions implement their own assembler.

AXA Overview

"AXA" is a palindrome: it reads the same forward and backward. That's basically what reversible computing is about: being able to execute forward or backward. However, this symmetry is broken by the fact that programs normally only execute forward. The reverse execution supported here is essentially an error-handling mechanism, allowing backing-up to a given earlier state in the execution of the program. As you'll see, the mechanism used also provides only limited reverse execution; you can't undo an arbitrary number of instructions.

The machine is a Harvard architecture, with separate instruction (.text segment) and data (.data segment) memories. It has sixteen 16-bit registers, 16-bit datapaths, and 16-bit addresses, and each address in either memory holds one 16-bit word. Most of the instructions look fairly normal, but there are a few oddities to support reversibility and use of reverse execution to unwind operations to deal with errors and perhaps even implement transactional memory.

This instruction set is complete enough that I hope to be giving you a compiler (including full C source code) that translates programs written in a significant subset of C into AXA code. It will not be a particularly smart compiler (ok, it's really dumb), but it will show you how AXA can be used for complete programs.

The AXA Instruction Set

AXA's instruction set uses a simple general-register model encoding each instruction as a single 16-bit word. Although implementing reversibility of some operations isn't very easy, the bulk of the operations are really quite ordinary and very little is strange in forward execution. In any case, the assembly langauge is also straightforward. Most instructions allow specifying a destination register, $d. Many also allow specifying a src, which can be any one of the following. Some instructions treat the i4 form differently; so we also use s16 to refer to any of the forms that directly produces a 16-bit value: $s, @$s, and i4$.

$s
A source register
i4
A 4-bit immediate value; this could also be used for the mask in the jerr and fail instructions
@$s
A source register to be used as an indirect data memory address
i4$
A 4-bit unsigned immediate value to be used to index the undo buffer; 0$ would be the most recently pushed value, 1$ the one pushed before that, etc. In this way, the undo buffer doubles as an extension of the register file, allowing reuse of remembered values

Only the last of these four operand structures is really unusual. In fact, most of the instruction set is inherently reversible and thus does nothing very surprising. The troublesome ones are the ones that do push() operations: lhi, llo, or, and, dup, shr, and land.

Instruction Description Functionality Reverse Functionality (errors!=0)
xhi $d, i8 Exclusive OR high immediate $d^=(i8<<8); ++pc $d^=(i8<<8); --pc
xlo $d, i8 Exclusive OR low immediate $d^=i8; ++pc $d^=i8; --pc
lhi $d, i8 Load high immediate push($d); $d=(i8<<8); ++pc $d=pop(); --pc
llo $d, i8 Load low immediate push($d); $d=signext(i8); ++pc $d=pop(); --pc
add $d, src Add integers $d+=src; ++pc $d-=src; --pc
sub $d, src Subtract integers $d-=src; ++pc $d+=src; --pc
xor $d, src Bitwise eXclusive-OR $d^=src; ++pc $d^=src; --pc
ex $d, @$s Exchange (d!=s) swap($d, @$s); ++pc swap($d, @$s); --pc
rol $d, src Rotate left $d=rotateleft($d,src); ++pc $d=rotateright($d,src); --pc
shr $d, src Shift right (arithmetic) push($d); $d=($d>>src); ++pc $d=pop(); --pc
or $d, src Bitwise OR push($d); $d|=src; ++pc $d=pop(); --pc
and $d, src Bitwise AND push($d); $d&=src; ++pc $d=pop(); --pc
dup $d, src Duplicate push($d); $d=src; ++pc $d=pop(); --pc
bz $d, i4 Branch if zero if (!$d) pc+=i4 --pc
jz $d, s16 Jump if zero if (!$d) pc=s16 --pc
bnz $d, i4 Branch if non-zero if ($d) pc+=i4 --pc
jnz $d, s16 Jump if non-zero if ($d) pc=s16 --pc
bn $d, i4 Branch if negative if ($d<0) pc+=i4 --pc
jn $d, s16 Jump if negative if ($d<0) pc=s16 --pc
bnn $d, i4 Branch if non-negative if ($d>=0) pc+=i4 --pc
jnn $d, s16 Jump if non-negative if ($d>=0) pc=s16 --pc
jerr $d, mask Enable checking for mask, fail to $d check|=mask; ++pc check&=~mask; errors&=~mask; if (!errors) pc=$d else --pc
land Landing for branch push(lastpc); ++pc pc=pop()
com Commit (disable all active jerr) check=0; errors=0; ++pc check=0; errors=0; ++pc
fail mask Reverse until jerr that covers mask errors|=(mask&check); --pc --pc (really not useful!)
sys SYStem (system call; end execution) halt --pc

Determining how to encode the above instructions as bit patterns is a key part of your project. However, there are a few rules:

The AXA Registers

The AXA processor has two different types of registers: general-purpose registers and the undo stack. Do not confuse the undo stack with a conventional stack -- there is assumed to be one of those too, stored in ordinary data memory.

The AXA General Registers

There are 16 general-purpose registers, some of which have special purposes -- a lot like MIPS. They all have names as well as numbers. Perhaps the best way to give both is the following specification (formatted as an AIK specification):

.const {r0	r1	r2	r3	r4	r5	r6	r7
	r8	r9	r10	r11	rt	fp	sp	rv }

Registers $r0 through $r11 (aka, registers $0 through $11) are "user" registers to be used in any way the programmer sees fit. However, it is expected that the assembler or compiler would use registers starting at $12 for "internal" things and starting at $0 for normal coding. The special meanings of the last four registers are not enforced by the hardware, but by convention:

Register temporary: rt
Intended to be used by the assembler/compiler for constructing constants and any other random temporary uses
Frame pointer: fp
If desired, this is used as a pointer to where the previous fp value was stored on the stack.
Stack pointer: sp
This is the stack pointer which is assumed to grow downward from high memory. The memory[sp] is the first free element on the stack, not the last allocated one.
Return value: rv
The return value for the current function.


EE480 Advanced Computer Architecture.