CprE 305 Lab 9
Introduction
In this lab you will be implementing a memory device and a program counter.
The datapath created in the last lab had to be driven by the user through the waveform editor. A computer, however, functions independently by retrieving instructions from a memory to perform its operation. The memory can be considered to be two separate components, the instruction memory where the instructions describing the operations are kept, and the data memory that contains the data to be operated on.
Access to the data memory is controlled by the load-store instructions in the MIPS architecture. These instructions calculate an address and route the data returned to a destination register.
Access to the instruction memory is handled by a special register known as the program counter (PC). This register automatically increments itself to the next instruction address unless told to retrieve an alternate instruction based on a branch or jump instruction.
In verilog there is a construct for specifying a memory. This construct is of the following format:
reg [wordsize:0] memdata [wordcount:0];
This construct would create a memory with wordcount+1 words that are wordsize+1 bits wide. Once this memory is created, the individual bits are no longer accessible within the module. Only the words are accessible. A standard RAM module might be declared as follows:
module RAM(dataout, address, datain, write, clock);
input write, clock;
input [wordsize:0] datain;
input [wordcount:0] address;
output [wordsize:0] dataout;
reg [wordsize:0] dataout;
reg [wordsize:0] memdata [wordcount:0];
always @(posedge clock)
if(write)
memdata[address] = datain;
else
dataout =
memdata[address];
endmodule
This would allow you to create a memory of any size with relative ease. However, MaxPlus-II does not support the verilog memory construct. There is a workaround in which a memory can be implemented in the method used to create a register file. Create a series of word registers and employ a decoder to select the register written to, and a multiplexor to select the register used as an output. However, this would be tedious for a memory of any substantial size. Even when employing behavioral level constructs for the design, the module can become very large very quickly. Here is a link to a sample file demonstrating the creation of a 64 entry, 4bit word, RAM module using the behavioral constructs, always, if/else and case.
Notice the use of the case statement. This statement can be used in place of either the decoder or the multiplexor. The decoder implementation is in the first half of the if-clause, which selects which register to write to. The multiplexor is in the second half, which selects which register to read from. This memory module clocks both the write operation as well as the read (note the use of the registered output).
As can be seen this module is rather redundant and would become larger for increasing memory address space. The size of the words will have no impact on the code size.
This workaround is rather cumbersome, and one might be tempted to think there is a core functionality missing in Altera’s implementation of verilog. However, this is not the case. Altera chose not to support the verilog method of memory specification in favor of its own memory megafunctions, which it has standardized across various implementation schemes, including verilog, VHDL, AHDL and the graphic editor.
These megafunctions can be embedded into a verilog module manually, but MaxPlus-II also has a wizard that will generate a module for you. This wizard is the MegaWizard Plug-in Manager located in the file menu.
First select to create a new custom megafunction variation. Then select the megafunction you wish to employ from the list on the left. For this lab you will use the LPM_ROM, located under the storage option. Make sure to specify Verilog HDL. Next, specify the name for the module you wish to create including the location you wish to store it in. On the next screen, specify the size of the memory module. The next screen you will specify the name of the memory initialization file you will use to set the initial values of the memory. The format for this file can be found in the help menu for MaxPlus-II.
A program counter is a single register whose width is the size required to address all possible instruction words. In an architecture such as MIPS, addresses to instructions must be a multiple of 4 (memory is byte addressable, each instruction word is 4 bytes), and thus the PC is incremented by 4. However, if the system was word addressable only (could not access any piece of memory smaller than one word based on address) the PC would only need to be incremented by 1. When a branch or jump occurs, the PC must be set to a specific value that denotes the new target instruction. The target can be either an absolute address (the exact address itself) or an offset address (difference from the current PC, positive or negative).
Create a 64-entry 11-bit word instruction memory compatible with your datapath from the previous lab using Altera’s megafunction wizard. Assume that the instruction memory cannot be modified during runtime and thus you can use the ROM megafunction. Make sure to include the clock signal (the default). Create a memory initialization file (MIF) for your instruction memory that contains the following instructions at the start of the memory address space. Allow the remaining memory to be initialized to zeros.
ldi $r0, 1
ldi $r1, 3
ldi $r2, 2
add $r3 $r1 $r2
sub $r2 $r3 $r0
or $r0 $r1 $r2
and $r3 $r2 $r1
slt $r2 $r1 $r0
j 0
This is the same sequence of instructions from the previous lab with 2 minor changes to the instruction format. The new instruction format will be 11-bits wide and is as follows.
Instruction 10
9 8 7 6 5
4 3 2 1 0
Jump 1
0 0 0 0 A5 A4 A3 A2 A1 A0
LoadI 0
1 0 0 0 D3 D2 D1 D0 c1 c0
ALU 0 0 Op2 Op1 Op0 a1 a0 b1 b0 c1 c0
This means that the data loaded in an ldi instruction is now embedded directly within the instruction, denoted by D3-D0. The a1-0, b1-0, and c1-0 are still the addresses passed directly to the register file. A new jump instruction type has been added which has the new absolute target address embedded in the instruction as A5-0. The jump instruction above tells the datapath to reset the PC to 0, which is the location of the instruction:
ldi $r0, 1
Create a PC module.
Module PC(iaddr, clock, reset, branch, baddr);
Where iaddr is the address passed to the instruction memory, clock is the signal that tells the PC to update, reset initializes the PC to zero, branch is a signal that tells the PC to reset itself to the value supplied by baddr.
Combine the PC and the instruction memory with the components from your previous datapath to create a new module:
Module CPU(aluout, drega, dregb, pcaddr, instruction,
clock);
Where aluout, drega, dregb are the same outputs from your previous datapath, pcaddr is the address supplied to your instruction memory, instuction is the output from your memory, and clock is your input synchronizing signal. There is only one input in this design.
Take careful note: In the previous lab, the write enable for the register file was set high unconditionally because all instructions wrote values to the register file. In this lab however, there is a new jump instruction that should not modify the register file contents. This must be accounted for.
Also: A clock cycle includes a low time and a high time. The instruction should be stable across an entire cycle to insure that the results are stable at the rising edge of the clock. Now there are three devices that require a clock, the RF, the PC and the Memory. Think about the signals each should receive to have an operation similar to that simulated in the previous lab.