Version 1.0 Spring 2022 1
Lab 4: The Complete Microprocessor
Prerequisites: Before beginning this lab, you must: • Understand how to use the tool flow (See the installation guide and Lab 0) • Have completed Lab 1: Half Adder, Full Adder, 4-bit Incrementer and Adder. • Have completed Lab 2: Multiplexers, Decoders, and the Arithmetic Logic Unit. • Have completed Lab 3: Registers, Counters, and the “Brainless CPU”.
Equipment: Personal computer with the required software installed. Files to copy from Lab 3 (do NOT copy from Lab 1 or Lab 2!): alu.dig
and_add.dig
brainless.dig
four_bit_adder.dig
four_bit_mux.dig
four_bit_reg.dig
full_adder.dig
half_adder.dig
incrementer.dig
not_neg.dig
program_ctr.dig
program_ram.dig
two_bit_mux.dig
Files to download: rom_vals.hex
ram_vals.hex
micro_top.v
micro_stim.v
Objectives: When you have completed this lab, you will be able to: • Build a Memory Address Generator for a microprocessor. • Design a ROM-based controller for a microprocessor. • Create an instruction set for a microprocessor. • Use the instruction set to write a program and enter it into a RAM. • Execute programs in Digital and in Verilog.
Introduction In this lab we will complete the microprocessor design by building a memory address generator
and a controller and adding them to your brainless microprocessor. We’ll also define an
instruction set for the controller. Finally, you will use the language inherent in your instruction
set to create a simple program, enter the program in your microprocessor’s memory and
execute the program. You will test the program first in Digital and then in Verilog.
Version 1.0 Spring 2022 2
Warning: Use the signal and circuit names provided! Verilog does not allow names to start with a number or names that have dashes! Create a folder named Lab4. Into that folder, copy the files listed above from Lab 3. Be sure to
only copy the required files. While it is tempting to do Lab 4 in the same directory where you
did a prior lab, it is advisable to start in a fresh directory in case something goes wrong. That
way, you still have the pristine prior lab results to copy again. In addition, this means that the
Lab4 folder will only have the files necessary for Lab 4.
Once you’ve copied the files from Lab 3, download the files provided for Lab 4 and place them
in the Lab4 folder. Now, you’re ready to start!
NOTE: You are required to design the circuits as presented in this document. Even though
Digital supplies many of the functions we will design, you are not to use them.
Task 4-1: Build and Test the Memory-Address-Generation Circuit Now that your brainless microprocessor is working, we will add additional pieces of circuitry to
it and assemble the complete microprocessor. The first step is to modify the 4-bit incrementer
you built in Task 3-2 so that you can use it to generate the memory addresses for your CPU. The
memory address generation circuit is shown in Figure 1. The memory address generation circuit
is comprised of two independent pieces. One piece consists of a register and increment circuit,
which is your 4-bit incrementer, and is known as the Program Counter (PC). The other piece,
which accepts addresses from the data bus, is known as the Memory Address Register (MAR).
This register can be loaded from the data bus and holds the address of a storage location that
we want to access. This address does not have to be in sequence with the other addresses.
Thus, the MAR allows us to access a specific address and that address is usually obtained from
our RAM. In order to load the memory address from the data_bus to the MAR, we have an enable signal that is called load_mar. The output of the address generation circuit is generated by a mux, which controls whether the PC or MAR drives the address bus, addr_bus. The control input to the mux, use_pc (short for Use Program Counter), is high when the PC values are driving the address bus and low when the MAR contents are to be supplied to the address bus.
The memory address generation circuit will allow our processor to access memory locations
both in and out of sequential order. The circuit can:
• Automatically increment memory addresses after each instruction or operand is retrieved by the CPU.
• Change the memory reference address so that we can store data to arbitrary memory locations or read data from arbitrary memory locations.
Open Digital and select File->New and File->Save As and name the circuit addr_gen, making
sure it is saved in the Lab4 folder. Then build the circuit shown in Figure 1 using the circuits you
built in prior labs plus an inverter. Note that the data_bus input and the addr_bus output are 4-bits wide. And remember to use the in front of underscores when entering the labels. For
Version 1.0 Spring 2022 3
example, data_bus. You’ll also want to change the width of the program_ctr to 4. (Open the
program_ctr properties, select Open Circuit, then Edit->Circuit specific settings.) You may want
to label the MAR as “MAR” to remind you what this four-bit register is holding.
Figure 1. The Memory Address Generation circuit.
Notice the small triangle symbols labeled “pc” and “mar”. We’re going to add these to make
finding these wires easier when we simulate in Verilog. In Digital, this component is called a
Tunnel. In other environments, they are also referred to as connectors. The normal use case for
a Tunnel is to allow two distant points to be connected without requiring a wire to run between
them. That is, the Tunnel establishes a virtual connection. The advantage is that you can
connect distant things without your circuit becoming a mess of wires. In our case, we’ll use the
Tunnel components to give the wires names as there is no other way to do so in Digital.
The best way to add the Tunnel component is to add all the Tunnel instances you need and
name them before connecting them to the wires. Select Components->Wires->Tunnel and
place them as shown. Open their properties and set the net names as they appear in the
diagram. Finally, connect them to their respective wires.
When done, simulate the circuit in Digital. Does it work properly? Can you load values into the
Memory Address Register? Make sure that both the PC and the MAR can be used to drive the
Version 1.0 Spring 2022 4
addr_bus. Once you are convinced your circuit is working properly, end the simulation and save your design. Take a screenshot of the circuit and paste it into your template.
Task 4-2: Build and Test the Controller Circuit Remember the brainless microprocessor from Lab 3? How did it know how to execute each of
the instructions in our program? The answer, of course, is that we acted as the controller since
we knew how to set all of the signals to execute each operation. Next, we will design a
controller that will be able to automatically load instructions (in the form of hexadecimal
numbers, called operation codes or “opcodes”) stored in the Program RAM and then automatically perform the operations needed to carry out each instruction. We will use a ROM
to decode the instructions and generate all control signals to operate the CPU. The only
remaining signals that we will control ourselves are clk, reset, and data_in.
The controller we will design and build is a ROM-based finite state machine as shown in Figure
2. Why do we need a finite state machine? It turns out that some of our operations cannot be
completed in one clock cycle. Think of the operation that writes the accumulator content to the
RAM at a specific address. We might need one or more extra clock cycles to accomplish this.
Rather than creating lots of opcodes for each individual step, our finite state machine allows us
to break down the opcodes into “micro-operations” or “micro-ops” (µ-ops). Each opcode, now called macro-op, can have up to four micro-ops in our design. This will be enough for all of the
operations that we want to perform.
NOTE: The controller is shown here for reference. Instructions on building it come later.
Figure 2. The controller.
Version 1.0 Spring 2022 5
The central part of the controller circuit is the “Instruction ROM”. It performs “double duty”,
acting as both a decoder for the control signals and as the programmable logic for the finite-
state machine. The controller circuit consists of three pieces. One piece is a 4-bit register which
holds the current instruction, or opcode, we are currently executing. This is known as the
Instruction Register. The second piece is a 2-bit register which holds the number of the micro-
op we are executing. (If you recall, each instruction can be broken down into, at most, 4 micro-
ops numbered 0 to 3.) This 2-bit register is called the Step Register. The outputs of the two
registers form a 6-bit bus which is used as the address into the third piece, the ROM. A ROM is a
Read Only Memory into which we will preload values. Our microprocessor will be able to read
from the ROM but, as its name implies, the microprocessor will be unable to modify the ROM’s
contents.
Why use a ROM? Because it is a straightforward way to implement the logic that we need.
Otherwise, we’d have to generate 12 6-variable Karnaugh maps and implement the logic with
gates – not an attractive prospect!
As stated above, the output of the Instruction Register and the Step Register are combined to
form the address to the ROM. The 4 bits of the Instruction Register are on the left and the 2 bits
of the Step Register are on the right. This way, the steps of an instruction are contiguous in the
ROM. If we represent the addresses as hexadecimal numbers, that means the first instruction
will be contained in addresses 00, 01, 02, and 03. Then the second instruction will use
addresses 04, 05, 06. and 07. If the instruction doesn’t need all the steps, that’s ok – we can just
load the unused addresses with 0s.
There are two types of values which the ROM produces. Bits 1:0 are used to determine the next
value that will be in the Step Register. Bits 11:2 are all control bits. We used all but one of these
in the brainless CPU. The only new control signal is the one used to load a new instruction into
the Instruction Register.
The IR is loaded with an opcode that is stored in the Program RAM and appears on the Data
Bus. In order to load that opcode into the Instruction Register, the controller has to set its
load_ir output to 1. We call this operation the “Instruction fetch”. The “fetch state” will be the first step executed for each of our instructions. The next three steps will be “execute states” to
achieve whatever functionality that instruction requires.
To build the controller, we’ll have to create a 2-bit register. To build this register, we’ll first have
to create a mux for 2-bit buses as shown in Figure 3. In Digital, select File->Open and select
four_bit_mux.dig. Immediately, select File->Save As and name the circuit two_bit_bus_mux.
IMORTANT: You must call this two_bit_bus_mux! If you leave out the “bus”, you’ll overwrite the two_bit_mux circuit you created previously and your four_bit_mux will no longer work. (If you already forgot the bus, that’s ok. Just copy the two_bit_mux circuit over from Lab 3 and start this step over.)
Version 1.0 Spring 2022 6
Figure 3. The mux for 2-bit buses.
Open the properties of the splitter/merger components and delete the “3-3,2-2,” leaving just
“1-1,0-0” and change the 4 to 2. This way, we’ll just have 2-bit buses rather than 4-bit buses.
Now delete the flip-flops no longer connected to the splitter/merger components. Now delete
the two_bit_mux components that are no longer connected as well as all the wires that are no
longer connected on both ends. Finally, change a, b, and y so that are 2 bits wide. Simulate this circuit in Digital and, when you are satisfied that it works, save it. Take a screen shot and paste
it into your template.
Now we’ll finish the two-bit register as shown in Figure 4. Select File->Open and select
four_bit_reg.dig. Immediately, select File->Save As and name the circuit two_bit_reg. Open the properties of the splitter/merger components and delete the “3-3,2-2,” leaving just “1-1,0-0”
and change the 4 to 2. This way, we’ll just have 2-bit buses rather than 4-bit buses. Now delete
the flip-flops that are no longer connected as well as all the wires that are no longer connected
on both ends. Finally, change d and q so that are 2 bits wide. Simulate this circuit in Digital and, when you are satisfied that it works, save it and paste a screenshot of it into your template.
We now have the pieces we need to create the controller. In Digital, select File->New. Then
select File->Save As and save the new file as controller, again making sure it is saved in the Lab4
folder. Let’s start by placing the ROM. Select Components->Memory->ROM. You’ll notice the
ROM has two inputs. A is the address and sel is the output enable. If the output enable is 0, the ROM outputs are not driven and, therefore, are at the high impedance, or Z state. To avoid
that, we’ll tie sel to the supply voltage.
Version 1.0 Spring 2022 7
Figure 4. The two-bit register.
Open the ROM properties and fill it out as shown in Figure 5. Then click on the Edit button in
the properties window and you’ll see a window as shown in Figure 6. At the top of that window
is the word File. Select File->Load and another window, shown in Figure 7 will appear. Select
rom_vals.hex, which you should have downloaded and placed in the Lab4 folder. Then click
Open. The ROM data window should now appear as shown in Figure 8. Click OK and OK to close
the ROM windows.
We’ll add three Tunnel components to this design. Remember to add the Tunnel components
and name them before connecting them to their respective wires. And don’t forget the in
front of the underscores when you set the Net name for each Tunnel.
Figure 5. The ROM bit definitions.
Version 1.0 Spring 2022 8
Figure 6. ROM data edit window.
Figure 7. Selecting the file with which to load the ROM.
Version 1.0 Spring 2022 9
Figure 8. The loaded ROM data.
Now place the four-bit and two-bit registers. Open their properties and select Open circuit. The
design will be a bit neater if we reorder the inputs. Select Edit->Order Inputs and match the
input order shown in Figure 2. Select Edit->Circuit specific settings and verify the width is 4. Do
this for both registers.
The last new work to do is to set up the splitter/mergers. Add the splitter/merger components.
Figure 9 shows how to combine the outputs from the registers. Figure 10 shows how to split
them back out. Why are we splitting them this way? The upper two bits coming out of the ROM
will be the next two bits to be loaded into the Step Register. So we’ll pull those off the bus and
route them internally. The next two bits are not currently used, so we’ll just leave them out.
The lower 10 bits will be the various control signals needed to operate the microprocessor.
We’ll discuss the contents of those bits shortly.
Figure 9. Combining the instr_reg and step_reg buses.
Version 1.0 Spring 2022 10
Figure 10. Splitting the ROM output.
Go ahead and add the inputs, the output, and all the wires. Remember that the data_bus input is 4 bits wide and the control output is 10 bits wide.
Now, let’s discuss the contents of the ROM. Figure 11 shows the first 12 entries in the ROM.
Remember that the first line, “v2.0 raw” is something that Digital needs to properly read the
file. Note that the # character starts a comment so we can annotate the entries.
v2.0 raw
1205 # LOAD ACC; Load IR
0234 # Load ACC
0000 # unused
0000 # unused
1205 # ADD ACC; Load IR
0294 # ADD
0000 # unused
0000 # unused
1205 # STOP; Load IR
1000 # stay here
0000 # unused
0000 # unused
Figure 11. The first 12 entries of the ROM.
Since this is a hexadecimal file, each digit represents 4 bits. However, the left digit will only
represent the numbers 0-3, so there are only 14 bits per row. Bit 13 is on the left and bit 0 is on
the right. Table 1 defines what each of the bits represent. Because the first line of each
instruction is always 1205, that value has been entered for you. This shows how each
instruction will start on a multiple of 4 entries. Note that the very last row in the file, which isn’t
shown in the figure, is assigned the value 3FFF. This was done to force Digital to write out all
the entries of the ROM when exporting to Digital. You’ll see why this is needed in a later task.
Version 1.0 Spring 2022 11
Table 1. Bit definitions for ROM entries
Bit # Signal Name Function 13:12 next_step[1:0] The next step of this instruction to execute. 11:10 – These are unused. They are included so the next step bits align nicely
9 use_pc Propagate the Program Counter (PC) to the address bus and increment the PC; otherwise select the Memory Address Register (MAR)
8 load_mar Load the MAR 7 arith One of the three controls for the ALU as defined in Lab 3 6 invert One of the three controls for the ALU as defined in Lab 3 5 pass One of the three controls for the ALU as defined in Lab 3 4 load_acc Load the accumulator. 3 acc_to_db Propagate the accumulator to the data bus; otherwise select the data mux
output 2 read The data mux should select the RAM output; otherwise it selects data_in 1 write Write the value on the data bus into the RAM. 0 load_ir Load the Instruction Register
Now that we know what the bits are, we assemble them into a value to place in the ROM. Take
the bits in Table 1 and arrange them horizontally as shown in Table 2. Note that each group of
four bits makes a hexadecimal digit.
Table 2. Bit Definitions Grouped in Hex Digits
name next_step unused use_pc load_mar arith invert pass load_acc acc_to_db read write load_ir bits 13:12 11:10 9 8 7 6 5 4 3 2 1 0
Note that bits 11 and 10 are unused. It is a common practice when designing hardware to leave
unused bits in order to line things up nicely. Consider the first data line in Figure 11: 1205. It is
easy to see that this means the control output from the controller will be 205. On the other hand, if we moved the next_step bits down to eliminate the unused bits, the first data line
would be 605. And if you’re trying to figure out if the output is correct, you’d have to think
about what that number would be without the top two bits. And that can, at times, become
painful since one sometimes forgets how many bits to ignore. Another advantage of leaving
some unused bits is that there’s room for enhancements. (Or bug fixes!)
So how does this work? When an instruction is in the instruction register, it combines with the
value in the step register to select a value from the ROM. This value divides into two functions.
The first function is to specify the next step in the instruction to execute. Since each instruction
has up to 4 steps associated with it, we need two bits dedicated to this task. These are bits 13
and 12. The second function is to control how the rest of the circuit is operating. These are the
10 bits 9 to 0.
How do you decide which values to assign in each step? The idea is to have the control signals
steer the data to where it needs to go and to get the various components to perform the
required logic. Let’s analyze the Load ACC command, step by step:
Version 1.0 Spring 2022 12
Step 0: This step is the same for all the instructions and is used to get an instruction from the RAM to the instruction register. Table 3 is used to explain how each value was chosen. The bold
horizontal lines in the table separate the binary bits that form each hexadecimal digit.
Table 3. Step 0 of the Load ACC instruction.
Signal Name Value Discussion next_step[1:0] 01 This takes us to the next step of the instruction. unused bits 00 – use_pc 1 Use the PC as the address to access the RAM and increment the PC load_mar 0 Not loading the MAR arith 0 Not doing an ALU function so default to 0 invert 0 Not doing an ALU function so default to 0 pass 0 Not doing an ALU function so default to 0 load_acc 0 Not loading the accumulator acc_to_db 0 Need to propagate the data mux output to the data bus read 1 The data mux should select the RAM output write 0 Not writing to the RAM load_ir 1 Load the value on the data bus into the instruction register
If you take these bits from top to bottom, you have 01_0010_0000_0101 in binary. (Recall
Table 2 where the bits are shown horizontally.) Converting that to hexadecimal, we get 1205,
the value we see in the rom_vals.hex file. Once this step is executed, we’re ready to move on to
step 1.
Step 1: This step will be similar for several of the instructions. In this case, all we are doing is loading a value we are reading from the RAM into the accumulator. The values required are
shown in Table 4. Since this is the last step needed for this instruction, we’ll return to step 0 to
fetch the next instruction.
Table 4. Step 1 of the Load ACC instruction.
Signal Name Value Discussion next_step[1:0] 00 This takes us back to step 0 so we can fetch the next instruction. unused bits 00 – use_pc 1 Use the PC as the address to access the RAM and increment the PC load_mar 0 Not loading the MAR arith 0 Not doing an ALU math function invert 0 Not inverting pass 1 Pass the value from the data bus to the ALU output load_acc 1 Load the accumulator with the ALU output acc_to_db 0 Need to propagate the data mux output to the data bus read 1 The data mux should select the RAM output write 0 Not writing to the RAM load_ir 0 Not loading the instruction register
Version 1.0 Spring 2022 13
If you take these bits from top to bottom, you have 00_0010_0011_0100 in binary. Converting
that to hexadecimal, we get 0234, the value we see in the rom_vals.hex file. Once this step is
executed, we’re ready to move back to step 0.
Note that when each instruction completes, it returns to step 0 and fetches the next
instruction. This means that the next instruction actually starts on its step 1. Make sure this is
clear before proceeding.
Additional instructions are created in much the same way. At each step, you decide which
values to assign to the ROM entries to get the behavior you want.
The one strange instruction, STOP, is a bit different. Its step 1 simply stays in step 1 and all the
other bits are 0. That way, your design stays in a final, stable state so you can verify that the
circuit worked correctly.
Simulate the controller in Digital and satisfy yourself that it is working correctly. Be sure to save
it and take a screenshot of the circuit and paste it into your template.
Task 4-3: Build the Complete Microprocessor Circuit
Now it’s time to complete our microprocessor! Open Digital, if it isn’t already open, and select
File->New and then File->Save As and name the design microprocessor. Make sure it saves into
the Lab4 folder! Implement the microprocessor shown in Figure 12.
Version 1.0 Spring 2022 14
Figure 12. The completed microprocessor.
The circuit shown in Figure 12 is hierarchical. This means that the guts of the three large
modules are hidden from view. This makes connecting them much easier and has the added
benefit of a much simpler schematic. However, this does make it harder to see how all the
pieces work together. Therefore, another view of the completed microprocess is shown in
Figure 21 at the end of this lab.
Note that the subcircuit inputs have been reordered to make the wiring much simpler.
Remember to include the backslash when you label data_in and that both data_in and accum are 4 bits wide.
Open the properties of the subcircuits and set the width of the controller to 7 and the width of
both the addr_gen and brainless subcircuits to 10.
Notice how the control bus is distributed. The programming for the splitters is shown in Figures
13-15. Note that you can resize the Splitter/Merger window to accommodate the number of
splits required in front of the brainless CPU.
Version 1.0 Spring 2022 15
Finally, we need to specify the RAM file to be loaded at startup. Select Edit->Circuit specific
settings and click on the Advanced tab. Select “Preload program memory at startup.” Then, on
the next line, click the 3 dots and navigate to your Lab4 folder and select the ram_vals.hex file
that you downloaded. Then click OK and save the design.
Figure 13. The splitter in front of addr_gen.
Figure 14. The splitter in front of the brainless CPU.
Figure 15. The splitter to extract load_ir.
Version 1.0 Spring 2022 16
Now we’ll simulate your design in Digital to see if the program we’ve provided works. First, let’s
have a look at the contents of ram_vals.hex shown in Figure 16.
v2.0 raw
0 # Load ACC
3 # value to load
1 # ADD ACC
5 # value to add
2 # STOP
Figure 16. The first 5 values in the ram_vals.hex file.
The first line in the file, v2.0 raw, makes it so Digital will properly load the file. Then we start an
actual program! The 0 is the instruction to load the accumulator with a value and the value to
load, 3, is on the next line. Following that is a 1, which is the ADD instruction, which adds the
next value, 5, to the value currently in the accumulator. Finally, 2 is the STOP instruction. If you
simulate digital using this file, you should see the following behavior:
When you start the simulation, the circuit will behave as if it’s been reset. To be certain, set the
reset to 1 and then back to 0 by clicking on it twice. The addr_bus and data_bus should both be 0, and the use_pc, read, and load_ir signals should all be 1. The circuit is ready to load an instruction.
On the first positive edge of clk, which occurs when you click clk, the first instruction will be loaded into the Instruction Register. You should see the addr_bus change to 1 and the data_bus change to 3. In addition, the pass, load_acc, and read lines should all be 1. You’re now ready to get the value of 3 into the accumulator. Click clk again so it returns to 0.
Now click clk so you get a second positive edge. The accum output should now be 3. In addition, the addr_bus should be 2, the data_bus should be 1, and the use_pc, read, and load_ir signals should be 1. We’re ready to load the next instruction. Click clk again so it returns to 0.
Click clk to get a third positive edge. The accum output should still be 3. The addr_bus should have changed to 3 resulting in the data_bus now being 5. The use_pc, read, arith, and load_acc signals are all 1. Great – we’re ready to add the 5 to the 3 already in the accumulator. Click clk again so it returns to 0.
Click clk to get a fourth positive edge. Success! The accum output is now 8 and the controller has set things up to read the next instruction: use_pc, read, and load_ir are all 1. The addr_bus is 4 and the data_bus is 2. Click clk again so it returns to 0.
Finally, click clk one more time to get a fifth positive edge. The accum output stays 8 and all of the control signals are 0 because the STOP instruction has been loaded. Since use_pc is 0, the addr_bus is now equal to the Memory Address Register. Since we’ve not loaded the MAR, it is
Version 1.0 Spring 2022 17
still 0 and so, too, is addr_bus. No matter how many more times you click on clk, nothing will change. Figure 17 shows the final state of the circuit following the simulation.
Figure 17. The final state of the microprocessor following the first simulation.
Make sure you understand how the above simulation worked before proceeding as you’ll be
writing your own tests and implementing your own instructions in the next task.
The ram_vals.hex file contains another 11 lines of 0 which you can use as necessary in the next
task to build your own programs.
Once your simulation is working, take a screenshot showing the final values as shown in Figure
17 and paste it into your template. Then stop the simulation, save the design, and then select
File->Export->Export to Verilog. Make sure it saves to the Lab4 folder.
Version 1.0 Spring 2022 18
Task 4-4: Simulate the Design in Verilog
In order to simulate in Verilog, you’ll have to modify the RAM module as we did in Lab 3. Edit
microprocessor.v and find the code shown in Figure 18.
module DIG_RAMDualPort
#(
parameter Bits = 8,
parameter AddrBits = 4
)
(
input [(AddrBits-1):0] A,
input [(Bits-1):0] Din,
input str,
input C,
input ld,
output [(Bits-1):0] D
);
reg [(Bits-1):0] memory[0:((1 << AddrBits) – 1)];
assign D = ld? memory[A] : 'hz;
always @ (posedge C) begin
if (str)
memory[A] <= Din;
end
endmodule
Figure 18. The original RAM code in microprocessor.v.
Version 1.0 Spring 2022 19
Now modify it by adding the lines shown in bold in Figure 19.
module DIG_RAMDualPort
#(
parameter Bits = 8,
parameter AddrBits = 4
)
(
input [(AddrBits-1):0] A,
input [(Bits-1):0] Din,
input str,
input C,
input ld,
output [(Bits-1):0] D
);
reg [(Bits-1):0] memory[0:((1 << AddrBits) – 1)];
assign D = ld? memory[A] : 'hz;
always @ (posedge C) begin
if (str)
memory[A] <= Din;
end
initial begin $readmemh("ram_vals.txt",memory); end endmodule
Figure 19. Add the lines in bold to the RAM module in microprocessor.v.
Now, copy the file ram_vals.hex to ram_vals.txt and delete the first line, v2.0 raw. In addition,
the comment delimiter in Verilog is //, so you’ll need to substitute // for each instance of # in
ram_vals.txt. If this isn’t clear, check out the comments in micro_top.v.
Let’s now look at the file micro_stim.v that you downloaded and placed in your Lab4 folder.
This file will allow you to simulate for up to 32 clocks. Table 5 shows the bit assignments for the
test_vals array.
Table 5: Bit definitions for micro_stim.v.
Bit # 11:8 7 6:5 4 3:0 Meaning exp_accum done unused reset data_in
Version 1.0 Spring 2022 20
The exp_accum value is what you expect the accumulator contents to be after each clock cycle. The reset and data_in signals are the inputs you’ll control since the clock will be provided for you. The done signal is new. Set the done signal to 1 when you want the simulation to end. If you forget to set it, then the simulation will automatically end after 32 clock cycles and a
message will print out telling you that’s why it ended. The version of micro_stim.v you’ve been
given will run the simulation you ran in Digital once you create the ram_vals.txt file and modify
microprocessor.v as described above.
You’re now ready to simulate! Execute:
iverilog -o microprocessor.exe microprocessor.v micro_top.v micro_stim.v
On Mac: ./microprocessor.exe
On Windows: vvp microprocessor.exe
Now, open GTKWave and set up your view similar to that shown in Figure 20. Grouping the
signals this way makes it really easy to see what is going on in the circuit. We’ll also see why we
used the Tunnel components. Open the waves using File->Open New Tab and select
micro_waves.vcd.
To create the waves as shown in the figure, click on the symbol next to micro_top and it will
expand to show the microprocessor circuit name. Now click on the symbol next to the
microprocessor and that section of your window should match that shown in the figure. Select
microprocessor and the signals should appear in the Signals section on the lower left. Double
clock on clk and reset. These are signals that go to all the modules so it’s handy to have them up top rather than repeating them with each group of signals. Now select Edit->Insert Blank to
insert a blank row. This separation makes things easier to see.
Let’s add the signals associated with the controller circuit. First, let’s name this group so we
remember where these signals are used or generated. Select Edit->Insert Comment and give
the group the name Controller. Now select the controller in the hierarchy panel (that’s the
small pane in the upper left) and the controller signals will appear. Add the signals as shown in
the figure. Note that some of the signals in the controller have names of the form s#. If we
hadn’t used the Tunnel components, the instr_reg, step_reg, and rom_out signals would have been of that form as well and we would have had figure out which signals they were. While not
too difficult in this case, it would have been hard if they all had the same number of bits!
Now add another blank and another comment as shown and add the signals from the addr_gen
module. Once done, add a final blank and comment and add the signals from the brainless
module.
Version 1.0 Spring 2022 21
Figure 20. Simulation waveform.
Remember that you can select File->Write Save File to save the waveform setup so you can
come back later and select File->Read Save File to get it back. That way, you don’t have to
recreate it every time. Also recall that you can select File->Reload Waveform if you’ve run a
new simulation and want to see how things changed rather than having to open things from
scratch.
If your waves look like those in the figure, take a screen shot and paste it into your template.
Otherwise, you’ll need to debug the circuit. Remember – if you go back to Digital and make changes, you’ll have to edit microprocessor.v to add the readmem statement for the RAM!
Task 4-5: Add the AND, ZERO, SUB, and STORE ACC Instructions In this task you get to demonstrate your understanding of your microprocessor by adding
instructions to your instruction set. Add the AND, ZERO, SUB, and STORE ACC instructions to
your microprocessor circuit.
• The AND instruction should perform a bit-wise AND operation of the value in the accumulator with an operand from the RAM and store the result in the accumulator.
• The ZERO instruction should store the hex digit 0 in the accumulator by subtracting the accumulator from itself and storing the result back in the accumulator.
Version 1.0 Spring 2022 22
• The SUB instruction gets the next value in the RAM and subtracts it from the accumulator by forming its two's complement, adding the result to the value already in
the accumulator, and then storing this result back into the accumulator.
• The STORE ACC instruction should write the contents of the ACC into a location in RAM specified by the instruction’s operand.
For each instruction above, define the control signals for all the microinstructions by modifying
the contents of the ROM. Update the contents of the Instruction ROM in the controller with the
added instruction definitions. Then write and execute programs that test each of the
instructions. As long as you clearly document what you’ve done, you may test multiple instructions with each test. You’ll need to paste the waves for each of your simulations into
your template along with an explanation of which instructions each simulation tests. Note that
different tests may require that the values in the RAM be modified. If running in Verilog,
remember that changes to the ram_vals.txt file do NOT require you to recompile the design.
To be absolutely clear, the AND instruction will be the third instruction and will therefore start
in the ROM at address C, or the 12th entry. ZERO will then start at hex address 10, or the 16th
entry.
You have three choices as to how to update the ROM contents. The first choice is to make the
changes in Digital manually. Open the properties for the controller and choose Open Circuit and
then open the ROM properties and click Edit. Then, enter the values you want. Since these are
hex values, the format is to put 0x in front of the entry you need in each spot. For example, if
you want the hex value 1205, then enter 0x1205. You can then run simulations in Digital to see
if your coding is correct. Once you’ve made the changes, click OK. If you’d like, you can then
save the changes and they’ll be written to the rom_vals.hex file. You’ll still have to simulate in
Verilog, so save the file and then export it again. Remember to edit microprocessor.v to add the
readmemh statement for the RAM and to update the ram_vals.txt file as necessary. Warning: when you save the updated ROM file, the comments in the file will be lost!
The second choice is to update the rom_vals.hex file directly. Simply open it an editor and
replace the 0 values with the values you need. When you start a simulation in Digital, the
changes you’ve made will be automatically read. When you are satisfied, export to Verilog and
proceed as described above for the first choice. Since you are editing the ROM file, the
comments will not be lost if you use this method.
If you are using choice 1 or choice 2, you’ll need to include a screenshot of the final version of
rom_vals.hex in your template.
The third choice is to edit the Verilog file directly. Digital takes the ROM contents and
hardcodes it in the Verilog fie. Open microprocessor.v and look for the module shown in Figure
21.
Version 1.0 Spring 2022 23
Figure 21. The ROM in microprocessor.v.
Now, you can directly edit the ROM entries. For example, the AND instruction will start at
my_rom[12]. Simply replace the 0s with the appropriate values, recompile and rerun the
simulation. If you use this method, be sure to take a screenshot of the above from your design
and paste it into your template once you are done with your design. (If you are doing the extra
credit, wait to take this screen shot until after Task 4-6.)
Version 1.0 Spring 2022 24
Once you are convinced that your circuit is working properly, take a screenshot of your timing
diagrams and copy them to your lab template. Include the programs from ram_vals.txt in your
lab template. Also, comment on any issues that you encountered.
If you are not doing the extra credit, also include a screenshot of your ROM contents either
from microprocessor.v or rom_vals.hex.
Task 4-6: Invent Your Own Instruction (Extra Credit) The microprocessor you’ve built can actually do many more operations than what we’ve done
so far. To earn extra credit, come up with a function we haven’t done yet and implement an
instruction to perform that function. Remember that you need to limit the function to no more
than 4 steps. Simulate your instruction in Verilog and include the program your ran along with a
screenshot of your waves in your template. And, now that you’ve added this last instruction,
take a screenshot of your ROM contents, either from microprocessor.v or from rom_vals.hex
and paste it into your template.
Task 4-7: Create a video and submit your report.
Take a screenshot of your final microprocessor design in Digital and paste it into your template.
Create a video showing your schematic in Digital and one of your waveforms and explain how
your design works. (This can be the waveform for any simulation other than the one you were
provided with!) Be sure to show yourself in the video and show your screen. Place a link to the
video in your template. Be sure to include any required password or to set permissions so that
everybody can see your video.
Make sure all of your files are in the Lab4 directory. Create a zip file of the Lab4 directory. Turn
in the zip file and your completed template. (Double check that you turned in the completed
template and NOT the blank one you downloaded. Unfortunately, turning in the wrong
template file is a common mistake.)
Congratulations! You’ve completed Lab 4!
Version 1.0 Spring 2022 25
Figure 21. The microprocessor with a layer of hierarchy removed.