CS 341L Computer Architecture - Lab 2

CS 341L Computer Architecture, Spring 2019

Lab 2 - due Sunday March 10 @ midnight

Please get started on the lab as soon as possible. In general, in this class you will have about 3 weeks from the lab release date until the due date.

Accessing Your Repository for this Lab

You will submit your work by pushing it to your repository on LoboGit. Details about how to access your repository are found on the course webpage.

Setting Up the Tools

First, install some tools: Xilinx ISE WebPack and ModelSim SE. The total space required will be about 30 GB (total download size is about 8 GB). You have two options here.

Option 1 - Install on Your Personal Windows Machine

I do not recommend this option, and I cannot provide you with much help if you try this option and it doesn't work.

Here's where to get the tools for Windows:

Option 2 - Install on Your Personal Linux Machine (or Linux VM)

You cannot install this software on the lab machines, since you don't have sudo permissions there.

NOTE: these install instructions were tested on Ubuntu 16.04 and 18.04.

The following outlines how to install ModelSim on Linux.

wget http://download.altera.com/akdlm/software/acdsinst/13.1/162/ib_installers/ModelSimSetup-13.1.0.162.run
chmod +x ModelSimSetup-13.1.0.162.run
sudo apt-get install build-essential gcc-multilib g++-multilib lib32z1 lib32stdc++6 lib32gcc1 expat:i386 fontconfig:i386 libfreetype6:i386 libexpat1:i386 libc6:i386 libgtk-3-0:i386 libcanberra0:i386 libice6:i386 libsm6:i386 libncurses5:i386 zlib1g:i386 libx11-6:i386 libxau6:i386 libxdmcp6:i386 libxext6:i386 libxft2:i386 libxrender1:i386 libxt6:i386 libxtst6:i386
# sudo apt-get install libpng12-0:i386
# sudo apt-get install libpng16-16:i386
sudo ./ModelSimSetup-13.1.0.162.run

After you have installed ModelSim in /opt/altera/13.1, you then need to do the following:

wget http://download.savannah.gnu.org/releases/freetype/freetype-2.4.12.tar.bz2
tar -xvf freetype-2.4.12.tar.bz2
cd freetype-2.4.12
./configure --build=i686-pc-linux-gnu "CFLAGS=-m32" "CXXFLAGS=-m32" "LDFLAGS=-m32"
make -j8
sudo mkdir /opt/altera/13.1/modelsim_ase/lib32
sudo cp objs/.libs/libfreetype.so* /opt/altera/13.1/modelsim_ase/lib32
cd /opt/altera/13.1/modelsim_ase/
sudo pico bin/vsim

This should open the ModelSim startup script. You need to go to the line that looks like

dir=`dirname $arg0`

and insert the following on the next line:

export LD_LIBRARY_PATH=${dir}/lib32

Additionally, search for the word linux_rh60 in the file, and change it to linuxaloem. Now, if you exit the editor, and type bin/vsim, you should see ModelSim start up.

Next, add the following at the end of your ~/.bashrc file:

export PATH=$PATH:/opt/altera/13.1/modelsim_ase/bin
alias xilinx="source /opt/Xilinx/14.7/ISE_DS/settings64.sh"
export MODELSIM="/opt/altera/13.1/modelsim_ase/modelsim.ini"
export WD_MGC="/opt/altera/13.1/modelsim_ase/"
export MODEL_TECH="/opt/altera/13.1/modelsim_ase/linuxaloem"

and type source ~/.bashrc to apply your changes.

Finally, install Xilinx ISE Webpack.

  • Download the Full Installer for Linux.
  • You will be asked to first create a Xilinx account. You'll need to use your UNM email address.
  • The download is about 7 GB, so will take quite a while to download (it took about 45 minutes for me).
  • Once the download is finished, do the following:
tar -xvf Xilinx_ISE_DS_Lin_14.7_1015_1.tar
cd Xilinx_ISE_DS_Lin_14.7_1015_1
sudo ./xsetup
  • The graphical installer should let you install Xilinx ISE Webpack in /opt/Xilinx.
  • After the install is complete, you should go to the Xilinx license site and download a Xilinx ISE Webpack license.
  • You should be able to start Xilinx by typing
xilinx
ise

Xilinx will ask for a license, and you should click "manage licenses", and then "load license" and select your downloaded license file (Xilinx.lic).

Overview of the Lab

In the first lab, we learned about how the MIPS instruction set works by implementing a basic MIPS CPU in software. In this lab, we will take this a step further, and learn how to implement a basic MIPS CPU in hardware.

We will be supporting the following 12 MIPS instructions: add, addi, sub, and, or, nor, lw, sw, slt, beq, j, nop. Note that we will ignore bne for now. Note also that mult, mfhi, mflo, sll, srl will require some additional thought, since they are multi-cycle operations, so we will leave these for future work.

The course textbook provides a very useful MIPS Cheat Sheet. You can also check out the other MIPS Assembly Reference which shows the syntax of the MIPS assembly language instructions (the order of the arguments can sometimes be a bit confusing). We will again utilize these references throughout this lab.

Recall the following basic MIPS assembly code:

# this program computes $t1 * $t2 and places the result in $t0

.globl c_entry
c_entry: nop
# initialize registers
addi $t1, $zero, 5
addi $t2, $zero, 4
addi $t0, $zero, 0

loop: beq $t2, $zero, done
add $t0, $t0, $t1
addi $t2, $t2, -1
j loop

.globl done
done: nop

We saw that this code can be assembled into the following machine code:

address block offset machine instruction assembly code explanation
0x00000000 c_entry+0 00 00 00 00 nop no-operation (left-shift by zero places)
0x00000004 c_entry+4 20 09 00 05 addi $t1, $zero, 5 add immediate
0x00000008 c_entry+8 20 0a 00 04 addi $t2, $zero, 4 add immediate
0x0000000c c_entry+12 20 08 00 00 addi $t0, $zero, 0 add immediate
0x00000010 loop+0 11 40 00 03 beq $t2, $zero, 0x00000020 <done> branch to done (3 words from next PC)
0x00000014 loop+4 01 09 40 20 add $t0, $t0, $t1 add
0x00000018 loop+8 21 4a ff ff addi $t2, $t2, -1 add immediate
0x0000001c loop+12 08 00 00 04 j 0x00000010 <loop> jump to loop (byte address 0x10 is word address 0x04)
0x00000020 done+0 00 00 00 00 nop no-operation (left-shift by zero places)

In particular, the "machine instruction" column contains a list of 32-bit words that represents the MIPS program.

In this lab, we will build hardware which can execute such a list of instructions.

Creating a New Project in Xilinx

  • Click on "New Project", name your project lab2, select a location where your project should be stored, and select HDL as the top-level source type, and click "Next".
  • Change "Preferred Language" to VHDL, and change "Simulator" to Modelsim-SE VHDL, and click "Next", and then "Finish".
  • Click on the "New Source" icon on the left, select "VHDL Module", and type full_adder as the filename.
  • Create the following ports: a (in), b (in), carry_in (in), sum (out), carry_out (out), and click "Next"/"Finish".
  • This should add a file full_adder.vhd (VHDL module) to your project, that looks like this:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity full_adder is
    Port ( a : in  STD_LOGIC;
           b : in  STD_LOGIC;
           carry_in : in  STD_LOGIC;
           sum : out  STD_LOGIC;
           carry_out : out  STD_LOGIC );
end full_adder;

architecture Behavioral of full_adder is
begin
-- PUT YOUR VHDL CODE HERE
end Behavioral;

To build a test bench for your VHDL module using ModelSim, click on the "Simulator" checkbox at the top, and then do the following:

  • Select full_adder in the list.

  • In the "Processes" list, open the "ModelSim Simulator" item, right-click on "Simulate Behavioral Model", select "Ignore Pre-Compiled Library Warning Check", and press "Ok".

  • Double-click on "Simulate Behavioral Model".

  • In the "Wave" section, select signals a, b, carry_in, right-click, and select "Edit" and then "Delete".

  • In the "Objects" section, select signal carry_in, right-click, select "Modify" and then "Apply Wave", and change the period to 200 ps.

  • This should create a new signal carry_in in the "Wave" section, with the corresponding waveform.

  • Repeat the above two steps for signal a, except set the period to 100 ps.

  • Do the same for b, except set the period to 50 ps.

  • Select your input signals in the "Wave" section, and click on "File", then "Export", then "Waveform".

  • Select "VHDL Testbench", click "Browse", and name your file full_adder_tb.vhd.

  • In Xilinx ISE, click the "Add Source" button, select your testbench full_adder_tb.vhd, and press "Ok".

  • Double-click on full_adder_tb.vhd in the "Design" section.

  • Make the following substitutions in the VHDL file (and then save):

    • \full_adder_tb.vhd\ --> full_adder_tb
    • \full_adder_tb.vhd_arch\ --> full_adder_tb_arch

Now that you have a VHDL test bench, you can run it using ModelSim:

  • In the "Design" section, change to the "Simulation" view, select full_adder_tb, and double-click "Simulate Behavioral Model".
  • In ModelSim, press the "Restart" button, and then the "Run All" button.
  • Click in the "Wave" section, and press the "Zoom Full" button.
  • You should see your waveforms for the adder inputs, and a red line (undefined value) for the outputs. This is because you haven't yet connected the outputs to anyting. Try modifying your full_adder.vhd in the following simple way:
-- PUT YOUR VHDL CODE HERE
sum <= a and b;
carry_out <= a or b or carry_in;
-- NOTE - this is not the correct definition of the outputs for a full adder - we're just using it for a test.
  • Save and repeat the process of running the testbench in ModelSim.

Basic Digital Logic / VHDL

We will use a fairly basic subset of VHDL in this class. We will use only structural (not behavioral) VHDL to model digital logic.

The CD that accompanies the textbook contains a VHDL Tutorial that you may find useful. There is a VHDL Primer that also may be useful. To refresh your memory on digital logic, please see Appendix C from the textbook CD.

It is relatively easy to produce a logical function from a truth table, but it can be tricky to produce a minimal function. Fortunately, the Espresso tool makes this easy. You can download the Espresso logic minimization tool, and it should work on Linux or Windows. There is a nice Espresso tutorial which shows an example. The following is a simplified version, using the truth table for a half adder.

Once you have downloaded the Espresso program, and added its location to your system path, type the following command:

espresso -o eqntott

Espresso will wait for input. Paste the following and press Enter:

.i 2      # number of inputs 
.o 2      # number of outputs 
.ilb A B  # names of inputs 
.ob C SUM # names of outputs
00 00     # input bits, followed by space, followed by output bits 
01 01     # (use a dash "-" character for "don't care")
10 01     # THIS IS THE TRUTH TABLE FOR A HALF ADDER
11 10 
.e        # end of file

Espresso will produce the following output:

C = (A&B);
SUM = (A&!B) | (!A&B);

Note that this shows that the carry out for a full adder is simply the logical and of the input bits, and the sum is the logical xor of the input bits.

Part 1 - Basic Adder, Inverter, and Sign-Extender (20 points)

  • Task A: write the truth table for a 1-bit full adder.
  • Task B: draw a schematic with logic gates showing how to implement the 1-bit full adder.
  • Task C: use VHDL to implement a 1-bit full adder according to your schematic (you can use operators like and, or, xor, etc., but do NOT use the higher-level operators like +). There should be 3 input bits (a, b, carry_in), and 2 output bits (sum, carry_out).
  • Task D: build a 32-bit adder in VHDL, by combining 32 instances of your 1-bit full adder. It should have two 32-bit input vectors (a, b), and 1-bit input carry_in, a 32-bit output vector (sum), and a 1-bit output carry_out.
  • Task E: build a 32-bit inverter in VHDL (i.e., compute the two's complement of the input). It should have a 32-bit input vector a, and a 32-bit output vector z.
  • Task F: build a 16-bit to 32-bit sign-extender in VHDL. It should have a 16-bit input vector a, and a 32-bit output vector z.

Part 2 - Arithmetic Logic Unit (ALU) (25 points)

Use the functionality you built in Part 1 to build a 32-bit ALU. The interface should look like the following. Inputs a and b are 32-bit vectors, c is a 5-bit control input vector, r is the 32-bit output vector, e is a 1-bit output which is set if and only if the result is zero.

ALU

control input (binary) function
00000 logical and
00001 logical or
00010 add
00110 subtract
00111 set on less than
01100 logical nor

Part 3 - Register File, Memory, and Instruction Store (20 points)

Build an instruction store ("instruction memory" in the below diagram), register file ("registers" in the below diagram), and memory ("data memory" in the below diagram), with interfaces as shown in the below diagram. The instruction store will be "hardcoded" to contain the MIPS program you want to run. The memory should be byte-addressable, and should contain at least 512 bytes.

Here is some basic code for the memory. It should be straightforward to use the ideas here to create the register file and instruction store.

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity mem is
  Port(clk : in STD_LOGIC;
       reset : in STD_LOGIC;
       addr : in  STD_LOGIC_VECTOR (31 downto 0);
       data_in : in STD_LOGIC_VECTOR (31 downto 0);
       write_enable : in  STD_LOGIC;
       read_enable : in  STD_LOGIC;
       data_out : out  STD_LOGIC_VECTOR (31 downto 0));
end mem;

architecture Behavioral of mem is
  type t_mem is array (0 to 511) of std_logic_vector(7 downto 0);
  signal array_mem : t_mem;
begin
process(clk,reset)
  begin
    if(reset='1') then
      -- NOTE: the following functionality allows you to initialize
      -- the contents of the memory
      array_mem <= (
        "00000000","00000000","00000000","00000000", -- 32-bit word at addr 0
        "00000000","00000000","00000000","00000000", -- 32-bit word at addr 4
        "00000000","00000000","00000000","00000000", -- 32-bit word at addr 8
        -- you can add more 32-bit words here if needed...
        -- the following line sets all other bytes in the memory to 0
        others => "00000000" );
    elsif(clk'event and clk='1' and write_enable='1') then
      array_mem(to_integer(signed(addr))+0) <= data_in(31 downto 24);
      array_mem(to_integer(signed(addr))+1) <= data_in(23 downto 16);
      array_mem(to_integer(signed(addr))+2) <= data_in(15 downto 8);
      array_mem(to_integer(signed(addr))+3) <= data_in(7 downto 0);
    end if;
     
    if(read_enable='1' and reset='0') then
      data_out <= array_mem(to_integer(signed(addr))+0) &
                  array_mem(to_integer(signed(addr))+1) &
                  array_mem(to_integer(signed(addr))+2) &
                  array_mem(to_integer(signed(addr))+3);
    else
      data_out <= "00000000000000000000000000000000";
    end if;
  end process;
end Behavioral;

Part 4 - Single-Cycle MIPS CPU (25 points)

Put all the components from Parts 1-3 together to form a single-cycle MIPS CPU, as shown in the following diagram.

Single-Cycle MIPS CPU

Part 5 - Documentation (10 points)

Clearly document (using source-code comments) all of your work. Also fill in the README.md file (preferably using Markdown syntax) with a brief writeup about the design choices you made in your code.

Make sure that you have named your source code files exactly as requested in the lab assignment! (If for some reason you need to use a different naming convention, make sure to carefully explain the mapping between your source code files and the requirements in the lab assignment).

Providing adequate documentation helps us see that you understand the code you've written.

Submission Instructions

We will automatically grab a snapshot of the master branch of your lab repository at the deadline.

Make sure you push all of your work before the deadline!