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.
http://www.asic-world.com/verilog/task_func1.html
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.
https://www.hdlworks.com/hdl_corner/verilog_ref/items/Genvar
.htm
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
https://www.utdallas.edu/~akshay.sridharan/index_files/Page52
12.htm
https://www.utdallas.edu/~akshay.sridharan/index_files/Page52
12.htm
https://www.edaplayground.com/x/3aGb
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
https://www.edaplayground.com/x/36Ze
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
https://www.edaplayground.com/x/36Ze
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.
https://www.edaplayground.com/x/4Tuk
Laboratory No. 5

CSCI 2121- Computer Organization and Assembly Language Labor.docx

  • 1.
    CSCI 2121- ComputerOrganization 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
  • 2.
    array of wiresor 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:
  • 3.
    The purpose ofa 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
  • 4.
    bus. Otherwise, itputs 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. http://www.asic-world.com/verilog/task_func1.html 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
  • 5.
    two approaches towriting 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
  • 6.
    perform an assignmenton 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. https://www.hdlworks.com/hdl_corner/verilog_ref/items/Genvar .htm 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.
  • 7.
    Part 1: TheALU 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.
  • 8.
    2. The valueB 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 https://www.utdallas.edu/~akshay.sridharan/index_files/Page52 12.htm https://www.utdallas.edu/~akshay.sridharan/index_files/Page52 12.htm https://www.edaplayground.com/x/3aGb 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
  • 9.
    memory, but weonly 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
  • 10.
    https://www.edaplayground.com/x/36Ze Part 3: MemoryController 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
  • 11.
    In order totest 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 https://www.edaplayground.com/x/36Ze 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
  • 12.
    32 general purposeregisters. 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.
  • 13.
    In order todetermine 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
  • 14.
    efficiently you canimplement 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.
  • 15.