IDIOT Reference Material

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 reasonably well-crafted one that he built specifically for Spring 2016 EE480. However, this design is not complete -- each team of students must devise your own encoding of the instructions and your own implementations. This document only covers the design principles, assembly language, and functionality.

An IDIOT Overview

The Instruction Definition In Our Target (IDIOT) instruction set is very simple. Everything is 16 bit: memory addresses, memory locations, registers, integers, floating-point values, and even instructions (with one exception). Main memory is only addressed by 16-bit word addresses; adding 1 to an address gets you the next 16-bit word. In fact, what the C language calls int, short, char, and float are all the same size for IDIOT (which is actually compliant with the C standard, except for the fact that modern versions want an int to have at least 32 bits). I'm sure that having only 16-bit memory addresses does not sound exciting to most of you, but 65,536 16-bit memory locations is 8X the main memory I had in my first computer. More importantly, having only 16 bits in a word makes it feasible to implement each ALU operation in as little as a single clock cycle -- and that includes the floating-point operations!

This instruction set also is complete enough that I'll be giving you a compiler (including full C source code) that translates programs written in a significant subset of C into IDIOT code. Come to think of it, it actually generates pretty dumb code even for an IDIOT compiler. ;-)

The C subset accepted is called IDIOC and the compiler is obviously enough idiocc: the IDIOC Compiler. At this writing, the compiler is generating probably-working integer code, but doesn't yet handle floats correctly... I'll post the compiler when all that is in a little better shape.

The IDIOT Integer Instruction Set

Enough with the platitudes about this great IDIOT. Here's the basic integer instruction set:

Instruction Description Functionality
add $d, $s ADD integers $d += $s
and $d, $s bitwise AND $d &= $s
any $d, $s bitwise ANY reduction $d = ($s ? 1 : 0)
dup $d, $s DUPlicate $d = $s
jz $c, $a Jump if Zero (where a must be >1) if ($c == 0) PC = $a
ld $d, $a LoaD $d = memory[$a]
li $d, v Load 16-bit Immediate $d = v
or $d, $s bitwise OR $d |= $s
sz $c Skip (Squash) if Zero if ($c == 0) { next instruction is skipped }
shr $d, $s signed SHift Right $d = $s >> 1
st $d, $a STore memory[$a] = $d
sys SYStem call this does a bunch of things...
xor $d, $s bitwise XOR $d ^= $s

There is nothing particularly obscure nor clever about the IDIOT instructions; in fact, in many ways they're about halfway between an Intel 8080 and MIPS. However, the cool thing is that nothing here is very difficult to implement.

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

The IDIOT Registers

While we're talking about registers, we might as well give the details out about them. They are not just 64 general-purpose registers; 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 {zero	one	sign	all	sp	fp	ra	rv
	u0	u1	u2	u3	u4	u5	u6	u7
        u8  	u9	u10	u11	u12	u13	u14	u15
        u16  	u17	u18	u19	u20	u21	u22	u23
        u24 	u25	u26	u27	u28	u29	u30	u31
        u32 	u33	u34	u35	u36	u37	u38	u39
        u40 	u41	u42	u43	u44	u45	u46	u47
        u48 	u49	u50	u51	u52	u53	u54	u55 }

Registers $u0 through $u55 (aka, registers $8 through $63) are "user" registers to be used in any way the programmer sees fit. The first eight registers have special meanings:

Register Number Register Name Read/Write? Use
$0 $zero Read ZERO; constant 0x0000
$1 $one Read ONE; constant 0x0001
$2 $sign Read the SIGN bit; constant 0x8000
$3 $all Read ALL bits; constant 0xffff or -1
$4 $sp Read/Write the Stack Pointer
$5 $fp Read/Write the Frame Pointer
$6 $ra Read/Write the Return Address
$7 $rv Read/Write the Return Value

The IDIOT Floating Point

The idiocc compiler understands four different base data types: char, short, int, and float. All of these data types are signed. Of course, the compiler also needs to manage unsigned arithmetic for addressing operations, but none of that is user-visible. The IDIOC dialect does not allow direct manipulation of pointers. However, it does support indexing one-dimensional arrays of any of the above types.

It should come as no surprise that char, short, and int are really fully equivalent data types in IDIOC. All are 16-bit values encoded in 2's complement. All arithmetic on these types is modular; there is no detection of arithmetic issues such as overflow or underflow, let alone the kind of clever handling you implemented in the second project.

The IDIOT data type that probably does surprise you is float. Yes, there is floating-point hardware. IEEE 754-2008 floating point typically uses at least 32 bits to represent a value, whereas here we only get 16 bits. Actually, 16-bit floats are not a new invention; they are sometimes called half precision. An IEEE single float normally has a sign bit, an 8-bit exponent, and a 24-bit mantissa magnitude stored in just 23 bits. So, how many bits is each of those things in a 16-bit float? Well, IEEE suggests 1+5+11 bits. However, that would mean to implement mulf, you'd need an 11-bit multiplier... which is a tad large as a combinatorial circuit. That's also a bit annoying in that you get a much more limited dynamic range than with a 32-bit float. For this project, we're willing to sacrifice IEEE compliance... hey, we were gonna ignore denorms, infinities, NaNs, and rounding modes anyway....

To keep things simple, our 16-bit IDIOT float format basically looks like the first 16 bits of an IEEE 32-bit float. That means 1 sign bit, 8 exponent bits, and 8 mantissa magnitude bits. It's not a huge change, but sacrificing some precision buys us a much larger dynamic range and means mulf only needs to do an 8x8 bit multiply -- which certainly could be implemented within a single clock cycle without a rediculous amount of circuitry....

Instruction Description Functionality
addf $d, $s ADD Floats $d += $s
f2i $d, $s Float-to-Integer $d = ((int) $s)
i2f $d, $s Integer-to-Float $d = ((float) $s)
invf $d, $s approximate INVerse Float $d = 1.0/$s
mulf $d, $s MULtiply Floats $d *= $s

It is not yet determined how much we'll be having you deal with floating point in this project... but we're at least going to discuss how it works in detail.

Add and multiply of floats works just like you'd expect from our discussions in EE380. However, reciprocal is probably a bit of a surprise... it was for me. Here's the basic algorithm:

float x;
int guess = 0x7ef3 - *(((int) *)&x);
int two = 0x4080;
guess = f16mul(guess, f15add(two, f16neg(f16mul(guess, x))));
guess = f16mul(guess, f15add(two, f16neg(f16mul(guess, x))));

How the heck does that work? Well, here's an explanation of a similar fast inverse square root. I'll explain more in class.... ;-)


EE480 Advanced Computer Architecture.