Assignment 2: The Making Of An IDIOT

IDIOT is the Instruction Definition In Our Target. In other words, it's the instruction set you'll be designing hardware to implement in this project. However, this project isn't just about the hardware design -- it's about everything you see! What, the curtains? No, not the curtains....

In this project, you'll be determining how to encode the IDIOT instruction set, building an AIK assembler that embodies that coding, creating a multi-cycle implementation of the IDIOT processor and memory, and testing it with some attention paid to test coverage. That's a lot, so you're not doing it alone, but in teams of 2-3 students. Let's take it one step at a time... which is also how you should do it.

Top Down

I said it in class, but let me repeat it here: you're going to be building a fairly complex collection of stuff. You'll never get it all working unless you're pretty methodical about the development process... which I'm strongly recommending should be top down.

Before doing anything, look at the instruction set. Think about what kind of hardware structures you're going to need to implement each type of instruction. Remember those high-level processor architecture diagrams in EE380? Well, you want to think a bit about what one of those would look like for your IDIOT processor. In fact, your multi-cycle design will probably look a lot like the Simple Processor Architecture from EE380, although there will be various simplifications (e.g., you don't need a MFC line because you can assume your memory completes an access in one cycle). Remember how we built-up that design in EE380 by going through the instruction set and incrementally adding whatever was needed to implement each instruction? Think about this project the same way.

Am I saying you need to draw one of those diagrams right at the start of the project? Not at all. What I'm saying is that you should always have in the back of your head roughly what the big picture is expected to look like. As you think about each instruction, think about what hardware will be involved in executing it and what types of control signals and datapaths will be needed. What things seem hard to do (the fancy title for this is "identify technological risk factors")? Make little notes to yourself. Discuss these things in your team. Make the big or confusing decisions as a team -- and document the non-obvious things in your Implementor's Notes.

Encoding Instructions

The IDIOT ISA contains 18 instructions, described here. Although you will not be implementing the five floating-point instructions, you need to have valid encodings for all 18 types of instructions. Each instruction is to be one 16-bit word long except li, which is two 16-bit words long (and the second word of that does not need to have an opcode).

IDIOT has 64 user-visible registers, and most instructions need two registers specified as operands, so that obviously takes 2*6=12 bits. In a 16-bit word, that leaves only four bits for the opcode... which isn't going to get 18 different instructions distinguished all by itself. No problem; all the control flow instructions, jz, sz, and sys, should share a single opcode. This is possible because sz $c should be coded like jz $c,$1 and sys should be coded like jz $0,$0.

The interesting thing for you to decide isn't really the field layout. You need to specify the actual bit patterns that will encode each opcode and which fields will be used to name which registers. Think about the MIPS instruction set encoding discussed in EE380; remember how the lw and sw instructions used the same instruction field for specifying the register that holds the base memory address? That's the kind of logic you want to apply here. For example, ld $d,$a and st $d,$a should probably use the same instruction bit field to encode $a. It might take a little more thought for you to decide which field should hold $a in jz $c,$a -- the correct answer is whatever field assignment is most convenient for you to implement, which might or might not be the same field you used for the thing called $a in ld or st instructions. It's all up to you.

Now you're probably getting nervous about the encoding choices. Don't be. Unlike the real world, in this class you can always change your mind if you later discover your instruction encoding was awkward. It should also be understood that many different encodings are comparably good, so don't be nervous if you hear that somebody else did things differently... you really can both be equally right. Still nervous? Explaining any nervousness-inducing decisions you made in your Implementor's Notes should help you feel better. ;-)

The Assembler

Once you've designed your instruction set encoding, you need a rigorous way of expressing the encoding. What better way to document it than to build an assembler using AIK? The AIK specification of the assembler is essentially executable documentation of the instruction encoding.

When you are specifying your assembler, everything is pretty straightforward and you can literally list one pattern per instruction (i.e., you don't need to use .alias if that confuses you). You also do not need to redefine any of the assembler directives like .origin -- you can just use them as they are. However, you do need to say something about the output from the assembler.

IDIOT not only uses a 16-bit word size, but literally considers a 16-bit word as occupying one memory address. That's a little different from MIPS, in which each 32-bit word spanned 4 byte addresses in memory. That means your assembler specification will need two lines that look a lot like these:

.segment .text 16 0x10000 0 .VMEM
.segment .data 16 0x10000 0 .VMEM

There is a very subtle issue here. Do both these segments go into the same physical memory? In fact, I have not specified that. It's a deliberate free choice for you. Basically, you can build your processor either with separate code and data memories (Harvard architecture) or you can merge both into one space (classic Von Neumann architecture). Either way is ok; the IDIOCC compiler (which I'm giving you) ensures that code and data don't get put in the same addresses by starting code at 0x0000 and data at 0x8000.

Of course, you'll also want the register names defined for AIK, and I already gave you the AIK specification of that:

.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 }

Not too bad, right?

The Verilog Hardware Design

I bet a lot of you are scared of this. You should be; it could be a huge mess. The trick is to never let it become a huge mess by sticking to that top down structured design discipline.

This design problem is not entirely new for you, but the design work you did in EE380 skipped a lot of implementation details that you cannot skip here. Still, think about things as you were told to in EE380. Step through what each instruction needs to do and logically build-up that big picture of the implementation architecture. Think about what function units, data paths, and control signals you will need. Do this before writing Verilog definitions of any piece.

When you start writing Verilog code, I strongly suggest that you think in terms of writing definitions of control signals and dummy top-level modules (with their output and input specifications). I very much like the idea of having an abstracted list of control signal definitions using `define; you might recall that in lecture February 19, I showed you things like this little fragment of a MIPS implementation in Verilog:

`define	WORD	[31:0]

`define	ALUadd	3'b000
`define	ALUsub	3'b001
`define	ALUxor	3'b010
`define	ALUslt	3'b011
`define	ALUor	3'b100
`define	ALUand	3'b101
`define	ALUsll	3'b110
`define	ALUsrl	3'b111

By consistently using things like `WORD instead of [31:0], the Verilog hardware description becomes just a little more abstract; you no longer have to ask yourself if something that says [31:0] is a 32-bit word or if it is a collection of other things that just happens to also be 32 bits. The same benefit happens by using `ALUadd instead of 3'b000, but you also get two more benefits:

  1. You know that the module implementing the ALU will understand the same control signal the same way as any module that instantiates an ALU.
  2. Knowing the complete set of ALU operations and their encoding becomes a fairly detailed specification of what your ALU must implement. This little "header file" of `defines is really a both a design specification and a part of the design implementation.

In summary, in lectures February 15-19, you got a fairly detailed overview of how to go about designing hardware for a complete computer system. The bottom line is that you should start by defining the set of function units, data paths, and control signals you will need. Define the interfaces and signals. Then build the modules themselves. Note also that for this project, you are allowed to use things like the Verilog + operator to build an adder: you need synthesizable Verilog, but you don't have to specify things at any particular level.

Which modules should you build first? Well, you'll get more done earlier if you do the easy ones first. Of course, a project that correctly implements some instructions is worth more than one that incorrectly implements all. However, what I really recommend is build the critical, but technologically risky, modules first. For example, you're gonna need memories, but we haven't really done much with that. Make sure you can build one before you get too involved in the design, because having to change your memory interface late in the design process could ripple awkward changes throughout.

Test Plan

As we discussed in class, testing a complex piece of hardware is a lot more difficult than simply enumerating all input values and comparing circuit outputs to those of an oracle (correct reference) computation. Your project needs to include a test plan (best described in your Implementor's Notes) as well as a testbench implementing the planned test procedure.

In class, we distinguished testing correctness of the design from testing correct operation of an implementation of the design. For this project, you do not need to worry about implementation test issues: i.e., your test plan does not need to target identification of faults caused by faulty manufacture, timing issues, etc. Neither do you need to "design for testability" in this project -- for example, you don't need to insert scan access paths for internal state that would otherwise be unobservable in the circuit implementation. What you need to do is develop a test plan that will give good certainty that your design itself is logically, functionally, correct.

In class, we discussed the covered test coverage tool, the metrics it collects, and what should be considered acceptable coverage values. Fundamentally, the most important type of coverage for this project is that every circuit path (every Verilog statement) should be used in some test case. You need not use the covered tool, nor its version embedded in this course's Verilog WWW form interface, to perform the coverage analysis, but you should provide some explanation in your Implementor's Notes of how your suite of test cases covers approximately 100% of all statements (lines of Verilog). You may (should) assume that built-in Verilog structures and operators, such as +, are operating correctly without exhaustively testing them.

The testbench you create to implement your test plan should look a lot like the testbench you wrote for Assignment 1, except:

Note that, as of March 2, the online Verilog WWW interface now allows use of $readmem directives, so it is much simpler to use that mechanism to initialize memory for your test cases. Include any such files in your submission as files with names ending in .vmem (to indicate that they are Verilog memory initialization files).

Due Dates

The recommended due date is Monday, March 7. By that time, you should definitely have at least submitted something that includes the assembler specification (a2.aik), and Implementor's Notes including an overview of the structure of your intended design. That overview could be in the form of a diagram, or it could be a list of top-level modules, but it is important in that it ensures you are on the right track. Final submissions will be accepted up to 11:59PM on Friday, March 11 -- just before the Spring break.

Submission Procedure

Each team will submit a project tarball (i.e., a file with the name ending in .tar or .tgz) that contains all things relevant to your work on the project. Minimally, each project tarball includes the source code for the project and a semi-formal "implementors notes" document as a PDF named notes.pdf. (Fairly obviously, the Implementor's Notes should also say who the implementors are -- list all team members as authors.) It also may include test cases, sample output, a make file, etc., but should not include any files that are built by your Makefile (e.g., no binary executables). For this particular project, name the Verilog source file satadd8.v.

Submit your tarball below. The file can be either an ordinary .tar file created using tar cvf file.tar yourprojectfiles or a compressed .tgz file file created using tar zcvf file.tgz yourprojectfiles. Be careful about using * as a shorthand in listing yourprojectfiles on the command line, because if the output tar file is listed in the expansion, the result can be an infinite file (which is not ok).

Use the submission form below to submit your project as a single submission for your team -- you do not submit as individuals. The last submission before the final deadline is the one that will be graded.

Your team name is .
Your team password is


EE480 Advanced Computer Architecture.