In this lab, your team will implement and test a complete E100 CPU. You will start from the Lab 5 datapath and finite-state machine, which can execute some of the E100 instructions. You will then add states to the finite-state machine to enable the datapath to execute the four missing instructions (cpfa, cpta, call, ret). You may not change the other Verilog files; this will make it easier to integrate the I/O devices into your E100 for Lab 7.
You will also write simple E100 assembly-language programs to test your implementation of these instructions.
It is tedious to specify an E100 program directly as a memory image file (as we did in Lab 5). Instead, you will write E100 programs in an easier format: E100 assembly language.
E100 assembly language uses a simple format. Lines in an E100 assembly-language file that represent an instruction use the format:
[label] opcode arg1 arg2 arg3
The E100 assembler also supports a pseudo opcode cpdata. cpdata works just like the cp instruction, but instead of copying the value found at the address arg1, it copies the value arg1. For example, cpdata x 5 copies the value 5 into memory address x.
In addition to specifying an instruction, a program may also directly specify the initial value for a memory word. For example:
[label] arg1tells the assembler to initialize the value of a memory word with arg1. As with instructions, arg1 could be a decimal number, a hexadecimal number, a non-whitespace character, or a label.
Comments in an E100 assembly-language file are indicated by //. The rest of the line after // is ignored. Blank lines are also ignored by the assembler.
An E100 assembly-language program may span multiple files. To stitch multiple files together into a single program, a file may contain lines of the form:
#include subfile.ewhere subfile.e is the name of another assembly-language file. This causes the assembler to process subfile.e as though it were inserted into the body of the file that included it. For example, consider the following two files, file1.e and file2.e (both in the same directory):
start add x y z halt #include file2.e
x 0 y 1 z 2
Assembling file1.e is equivalent to assembling a file with the following contents:
start add x y z halt x 0 y 1 z 2
The name of an included file is specified relative to the file that included it. In the above example, file2.e is in the same directory as file1.e, so file1.e can simply have
#include file2.eAs another example, ~/dir1/file1.e could include ~/dir2/file2.e with
#include ../dir2/file2.eIncluded files can themselves include other files, though this can get confusing. For larger programs, we recommend you create a single top-level file that includes all the other files.
You will use the program ase100 to assemble and simulate E100 programs. The assembler part of ase100 translates an E100 assembly-language program into a Quartus memory image file (called a .mif file). These memory image files can then be downloaded onto the FPGA through the memory content editor and executed by a hardware E100 processor. The simulator part of ase100 simulates how a hardware E100 processor would execute a memory image.
Run ase100 on Linux by opening a terminal window and typing ase100. ase100 can also assemble a program without the graphical interface (e.g., type ase100 -a file.e).
You can run ase100 on any CAEN Linux PC or on your own computer. There are versions of ase100 for Linux and Windows.
The main ase100 window looks like this. To assemble an E100 program, click Choose assembly file, then click Assemble. ase100 produces two files when it assembles an assembly-language file:
After you've assembled the .e file into a .mif file, you can then load the contents of the .mif file into memory (click Load) and simulate the execution of an E100 processor on that memory image (click Run). ase100 simulates the behavior (i.e., the effects) of E100 instructions; it does not simulate a particular datapath/FSM implementation of an E100.
Click Run to start simulating the loaded memory image file. The Run button changes to Stop when the simulation is running; click Stop to stop the simulation.
You can execute a specific number of instructions by entering a number in the Steps field. You can tell the simulator to stop before it executes a specific instruction by entering the address of that instruction in the Break field. You can tell the simulator to stop after it modifies a specific address by entering that address in the Watch field. Steps, Break, and Watch values are activated the next time you click Run.
ase100 displays the contents of memory (screenshot), which is updated each time the simulator stops. Viewing the contents of E100 memory is often helpful when debugging E100 programs.
For now, ignore the Save speaker, Save SDRAM and Save VGA buttons and the VGA window. You'll use these when simulating the audio, SDRAM, and VGA devices.
For the pre-lab assignment, you will practice a technique called unit testing, in which individual components are tested in isolation, then combined only after they have been verified to be correct. Unit testing helps narrow down the source of a bug more quickly than testing the combined components.
For each of the four new instructions, your pre-lab assignment is to write a version of control.v that adds states only for that instruction, and to write a short (e.g., 2-instruction) assembly-language program that tests only that instruction. You should also write a single assembly-language program test.e that tests all four new instructions; however you will not use test.e until after unit testing is complete.
Your assembly-language programs should use symbolic addresses for all variables. At each memory location that the program modifies, write a comment at that location that specifies the expected final value. Test your assembly-language programs before lab by running ase100.
In the lab, different members of your team can verify (in parallel) the versions of control.v that implement each new instruction. After a version of control.v for an instruction is verified, it can be added to the final version of control.v and tested there. When merging the different versions of control.v, remember to assign different parameter values for the state names.
To download a .mif file into FPGA memory, first load the .mif file into the memory editor by right-clicking on the displayed memory values and selecting Import Data from File... (screenshot). On the bottom pulldown menu, select type Memory Initialization File (*.mif), then navigate to the directory with the .mif file and click on it (screenshot). This has the same effect as entering the data manually. After this, you can write the displayed contents to FPGA memory in the usual way (right-click and select Write Data to In-System Memory).
Remember that the reset signal should be on while writing data to FPGA memory.
Demonstrate to a lab instructor how your completed E100 CPU executes your test.e. After you've demonstrated your CPU to a lab instructor, one member of your team should submit your final version of control.v and test.e here.
If your group finishes early, we recommend getting a head start on Lab 7 by adding I/O controllers to your E100 processor.