use EDA playground (Verilog) to make a Memory Module

profilexxasdas
Lab.pdf

CSCI 2121- Computer Organization and Assembly Language Laboratory No. 5

Week of March 12th, 2018

Submission Instructions:

1. Save the files as shift.sv and vending.sv 2. Put all files into one folder. 3. Compress the folder into a .zip file. 4. Submit the zip file on Brightspace. 5. Submission Deadline: Sunday, April 15th, 2018, 11.55PM

SRC CHIP PROJECT

In order to proceed on the project for this course, there are several key Verilog concepts which are

necessary in the implementation of this code.

Part 0: More Verilog concepts.

Shared busses and tri-state buffers

Before we begin implementing the CPU, let's go over a few concepts which will be needed for the lab

assignment. The first is the concept of a shared bus. We've already seen busses in Verilog in terms of an

array of wires or registers, however in the context of our CPU, the word "bus" has another meaning. Our

CPU uses a shared "bus" which is a wire connected between the inputs and outputs of many different

registers, in order to share data:

This can be easily modelled in Verilog, using the inout keyword for a module. This defines a wire which

can act as both an input and an output to our module. However, there is one critically important design

concept which goes along with the usage of shared busses:

Only one signal can be written to a shared bus at any given time.

If two signals are written to the bus, the result is a bus collision, and the value is undefined. In the

simulation, this is represented as "X", but in real life this would cause garbage data to exist on the bus,

potentially corrupting any process which is running on the CPU. As Verilog programmers, it's our job to

ensure that a bus collision never happens. To facilitate this, we'll use a digital logic component called a

tri-state buffer:

The purpose of a tri-state buffer is to act as a switch. If B is 1, then the data can pass through the buffer,

but if not, it simply disconnects the wire, outputting a high-impedance state (in Verilog, denoted by Z).

hz943141

A B C

0 0 Z

0 1 0

1 0 Z

1 1 1

In Verilog, we can't directly write a tri-state buffer, but it can be easily synthesized using a ternary

operator as shown on line 11:

Line 11 shows the construction of a ternary operator to use as a tri-state buffer. If the input called

activate_out is set to 1, then the tri-state buffer is activated, and the circuit will put my_result on the

bus. Otherwise, it puts the value 32'bz on the bus, which will disconnect my_result from the bus, and

allow another circuit to write to the bus.

Note: It is important that during your lab assignment, any circuits which write to the bus have a tri-

state buffer implemented to prevent bus collisions.

Verilog Tasks:

Verilog tasks are like object functions in Java. Read more about them here. You may find that they are

useful in separating the code for instructions within a module, particularly for the CPU module.

More ways to use the "always" block

We've seen the always block in the context of sequential circuits, but it can also be used to define

combinational circuits. While we could already defined these blocks with assign, using an always block

provides more flexibility, and the ability to write higher level language functions such as if. There are

two approaches to writing a combinational circuit with an always block:

In this case, the block is always triggered when the net a_in is 1. Similarly, you can replace a_in with

~a_in for capturing events on 0. In this case, whenever a_in is 1, the register a takes on the value in

cpu_bus. This concept is essential for completing the lab, because in the SRC chip, all registers are

connected to the bus in this way. You may also opt to use the following syntax:

This will trigger when any net in the module is set to 1. This statement can be slow to simulate and

synthesize if the module has a large number of nets, such as the memory module.

2-Dimensional register blocks.

Verilog has useful syntax for defining memory using registers. The following code demonstrates how to

make a 2d array of registers to create a block of memory:

One more Verilog concept which may be useful are genvars which can be used, for instance, to

perform an assignment on a large set of wires using a for loop. This is useful for defining the tri-state

buffers for general purpose registers in the CPU.

Information about the project

The work for this lab can be divided into two streams, according to the following dependency diagram

for the modules:

To implement the memory controller and the ALU, it is important to have a good grasp on how tri-state

buffers and shared busses work, as detailed above. When it comes to the CPU, it is a good idea to have a

look at the testbench code for both the ALU and the memory controller, to get an idea for how to

implement the control path logic. In both cases, the testbench code is designed to act as a stand-in for

the CPU, and behaves in much the same way.

Part 1: The ALU

The ALU is a relatively straightforward construction in Verilog, if you make use of operators available to

Verilog. The ALU has three main components: A register called A, which stores the first operand. A

connection to the bus, which will contain the second operand, and a register C, to output the result.

Between all of these components is a combinational circuit which will perform the desired operations.

Note that in the following diagram, the star (*) represents a logical subsection of the module, and not

another actual module.

The important part here is how the control logic is performed so that the CPU can make use of it. There

are three distinct steps to performing an operation on the ALU (these may vary for certain operations):

1. A value is loaded into register A using the bus, and the a_in input. Once a_in is set to 1, A should

contain the value on the bus.

2. The value B is loaded onto the bus, to act as the second operand.

3. The operator line (add, sub, etc.) is set to 1, and the result is stored by setting the c_in input to

1.

4. Finally, the bus is cleared and the c_out pin is set to 1.

From here, the CPU will decide what to do with the value which is now active on the bus.

The skeleton code for this module is provided here: https://www.edaplayground.com/x/3aGb

Please save this file as alu.sv

Part 2: Memory module

In order to make use of our CPU, we'll construct a rudimentary memory module. In future lectures, you

will learn about more sophisticated techniques for designing memory, but we only need something

simple to hold instructions for our CPU. Our memory module will have three main components:

1. A connection to a shared data bus.

2. A set of control pins: addr, read, enable

3. A 65536x32 set of registers.

The chip has the following functionality: Whenever enable goes to 1, we do one of three things:

1. If read is 1, then the module will output the memory at address addr to the bus. (Read mode)

2. If read is 0, then the module will write the value from bus to the memory at address addr.

(Write mode)

Hint: the syntax for creating a 2d array of registers in Verilog which is 64x16 is as follows:

The skeleton code for this module is provided here: https://www.edaplayground.com/x/36Ze

Please save this file as mem.sv

Part 3: Memory Controller

The memory controller is designed to bridge the gap between the CPU's shared bus, and the memory

interface. It is connected to two shared busses: cpu_bus and mem_bus, and it has control pins for its

two registers: ma and md. Finally, it is connected to the same read, enable, and address, wires that the

CPU will ultimately output to the memory block. The CPU will use the memory controller's address

output as its own address output, so this connects directly to the memory.

This chip has the following behavior:

1. It has in/out control wires for ma and md. These define behavior for output and input to the

cpu_bus

2. When read is 0 and enable is 1, md will output its value to mem_bus.

3. When read is 1 and enable is 1, md will take it's value from mem_bus.

4. The output address always outputs the content of ma

In order to test this module, you must first complete the memory module, and copy and paste it into the

tab called "mem.sv":

Warning: Do not start this module until you have a working memory module! The unit test relies on

testing the controller with a memory module of your construction.

The skeleton code for this module is provided here: https://www.edaplayground.com/x/36Ze

Please save this file as mem_controller.sv

Part 4: SRC CPU

This is the most challenging part of the lab. You will implement a CPU which can perform the load, store,

add, and subtract operations. The CPU will make use of all of the components we have built so far, and

you can add the completed modules to the tabs.

The CPU should consist of a 32-bit pc and ir register, as well as 32 general purpose registers. The goal of

this portion of the project is to connect the CPU to the previous module, and using the concrete RTN you

learned in the lectures, activate these signals in a specific order (activated by the clock pulse) to execute

instructions in memory, starting at address 0x0.

Most of the functionality of the CPU is internal, where its main outside connections are to the memory

module. However, for our lab, there is one extra piece of functionality: the reg_select input and

reg_value output. Whatever the value of reg_select is, simply output the value in that general purpose

register to the reg_value output. This lets the unit test determine what the value in a given register is,

after an instruction has been executed. Make sure you implement this behavior before you start unit

testing your code.

The CPU module consists of two logical sections:

1. Control wires and their input/output behavior.

2. A state machine for executing instructions based on the opcode, the contents of IR, and the

current state.

In order to determine the behavior for an opcode and a current state, you will have to consult your

notes on concrete RTN, and the instruction set specification. For this exercise, we will implement ld, sto,

add and sub.

Here is the suggested order for implementing the CPU:

1. Add all 32 general-purpose registers, as well as PC and IR.

2. Add the reg_select and reg_value functionality. The unit testing depends on this, so don't forget

it.

3. Create tri-state buffers and combinational logic for input/output to the registers.

4. Add opcode parsing by creating wires attached to registers in ir.

5. Add an instance of the SrcALU and SrcMemoryController modules.

6. Create registers to act as control signals for each of the control wires for SrcAlu and

SrcMemoryController (1 for every input to each of these modules)

7. Define a state register to hold the current state of the CPU. The size of this depends on how

efficiently you can implement each instruction. If you are having a lot of issues with timing, try

adding more intermediate states.

8. Create a state-transition diagram for each step of an instruction's concrete RTN.

9. Using switch or if/else if logic, define the behavior for each instruction. (Note that instruction

fetching is the same for all instructions). Each unit test is designed to test a specific instruction,

so just try to get the tests passing one at a time. Modify the unit test while working on this to

only load one instruction in at a time, if you find the debug output overwhelming.

The skeleton code for this module is provided here: https://www.edaplayground.com/x/4Tuk

Please save this file as cpu.sv

The page which follows contains the entire diagram for the SRC CPU.

  • Laboratory No. 5