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.
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.
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:
- ModelSim Starter Edition (SE): product page, install file.
- Xilinx ISE WebPack: product page, install file, instructions for installation on 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
).
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.
- Click on "New Project", name your project
lab2
, select a location where your project should be stored, and selectHDL
as the top-level source type, and click "Next". - Change "Preferred Language" to
VHDL
, and change "Simulator" toModelsim-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.
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.
- 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 inputcarry_in
, a 32-bit output vector (sum
), and a 1-bit outputcarry_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 vectorz
. -
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 vectorz
.
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.
control input (binary) | function |
---|---|
00000 |
logical and |
00001 |
logical or |
00010 |
add |
00110 |
subtract |
00111 |
set on less than |
01100 |
logical nor |
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;
Put all the components from Parts 1-3 together to form a single-cycle MIPS CPU, as shown in the following diagram.
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.
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!