Friday, January 17, 2014

CPU Design and Verification

Project goals: 
  • writing an RTL description of a processor that is capable of executing the instruction set below (43 assembly instructions)
  • writing an assembler (written in Perl for R-type instructions only)
  • verifying the design by placing instructions in the instruction memory, initializing CPU registers, and running for a number of cycles
  • synthesizing the RTL for the Cyclone II FPGA (just to make sure that the design, if further improved, could be used for simple assembly programs)
  • (not finished) building a UVM verification environment and developing testcases
Tools:
  • Perl
  • Verilog
  • SystemVerilog (UVM library)
  • Synopsys VCS
  • Quartus II


  • The CPU is using the classic RISC pipeline (IF, ID, EXE, MEM, WB)
  • Data hazards are avoided by using bypassing/forwarding
  • Separate instruction and data memories are used to avoid some of the structural hazards
  • Branch target adder is located in the ID stage (one less stall/bubble cycle for conditional branches as opposed to having it in the EXE stage)
  • Branch predictor (not implemented)
  • Instruction prefetch (not impemented)

The controller is implemented as a Moore state machine and has 9 states. The instructions are classified as follows:
    • R1: MULT MULTU DIV DIVU ADD ADDU SUB SUBU AND OR XOR SEQ SNE SLT SGT SLE SGE
    • R2: SLL SRL SRA
    • I1: SLLI SRAI SEQI SNEI SLTI SGTI SLEI SGEI SRLI ADDI ADDUI SUBI SUBUI ANDI ORI XORI
    • I2: BEQZ BNEQ
    • I3: SW
    • I4: LW
    • I5: JR
    • J1: J
    • J2: JAL





The following Perl script (assembler) was used to generate instructions to test the CPU:

#################################################################################################
#       Sergey Ostrikov 
#       1 June 2013
#       Description: assembly to hex
#################################################################################################

use strict;
#use warnings;

my $return;
my @R = qw(SLL SRL SRA MULT MULTU DIV DIVU ADD ADDU SUB SUBU AND OR XOR SEQ SNE SLT SGT SLE SGE);
my @I = qw(BEQZ BNEQ SRLI ADDI ADDUI SUBI SUBUI ANDI ORI XORI JR SLLI SRAI SEQI SNEI SLTI SGTI SLEI SGEI LW SW);
my @J = qw(J JAL);

my $R = @R; 
my $J = @J; 
my $I = @I;


#########################################################################################
###################     GENERATE R-type instructions    #################################

r_type("ADD,0,0,0");
r_type("ADD,1,2,3");

#########################################################################################
#########################################################################################


sub r_type {

open(OUT, ">>instructions.txt"); # to print out to a verilog/SV file

my $str = @_[0];
chomp($str);
my @instr = ();
my ($opc1, $rs1, $rs2, $rd, $sa, $opc2) = 0;
my $z32 = 0;
my @aa = ();

#################################################################################################
#       R-type instructions     
#################################################################################################
for (my $i=0; $i<$R; $i++) {
if ($str =~ m/$R[$i]/) {
        #print "\n\t\tR-type\n";
        $opc1 = "000000";
        @instr = split(/,/, $str);  
        if      ($instr[0] ~~ [qw(SLL SRL SRA)]) {      # R-type instructions that use "shamt" (shift amount) $sa
                $rs2 = "00000";
                if ($instr[3] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $sa = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $sa = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $sa = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $sa = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); $rs2 = $a;}
                }
                if ($instr[2] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $rs1 = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $rs1 = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $rs1 = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $rs1 = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); $rs2 = $a;}
                }
                if ($instr[1] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $rd = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $rd = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $rd = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $rd = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); $rs2 = $a;}
                }
                if      ($instr[0] eq "SLL") {$opc2 = "000100";}                        # 0x04 6-bit
                elsif   ($instr[0] eq "SRL") {$opc2 = "000110";}                # 0x06
                elsif   ($instr[0] eq "SRA") {$opc2 = "000111";}                # 0x07
                $z32 = join("", $opc1, $rs1, $rs2, $rd, $sa, $opc2);    # 32-bit binary instruction
                @aa = split(//, $z32);                                                                  # separate into chunks of 4 to get hex
                my $a1 = join("", $aa[0],$aa[1],$aa[2],$aa[3]);         my $aa1 = oct("0b$a1");         my $aaa1 = sprintf "%x", $aa1;
                my $a2 = join("", $aa[4],$aa[5],$aa[6],$aa[7]);         my $aa2 = oct("0b$a2");         my $aaa2 = sprintf "%x", $aa2;
                my $a3 = join("", $aa[8],$aa[9],$aa[10],$aa[11]);       my $aa3 = oct("0b$a3");         my $aaa3 = sprintf "%x", $aa3;
                my $a4 = join("", $aa[12],$aa[13],$aa[14],$aa[15]);     my $aa4 = oct("0b$a4");         my $aaa4 = sprintf "%x", $aa4;
                my $a5 = join("", $aa[16],$aa[17],$aa[18],$aa[19]);     my $aa5 = oct("0b$a5");         my $aaa5 = sprintf "%x", $aa5;
                my $a6 = join("", $aa[20],$aa[21],$aa[22],$aa[23]);     my $aa6 = oct("0b$a6");         my $aaa6 = sprintf "%x", $aa6;
                my $a7 = join("", $aa[24],$aa[25],$aa[26],$aa[27]);     my $aa7 = oct("0b$a7");         my $aaa7 = sprintf "%x", $aa7;
                my $a8 = join("", $aa[28],$aa[29],$aa[30],$aa[31]);     my $aa8 = oct("0b$a8");         my $aaa8 = sprintf "%x", $aa8;
                $return = join("", $aaa1, $aaa2, $aaa3, $aaa4, "_", $aaa5, $aaa6, $aaa7, $aaa8);
                print OUT "$return\n";                                                                  # 32-bit HEX instruction epic win!
                }
        elsif   ($instr[0] ~~ [qw(MULT MULTU DIV DIVU ADD ADDU SUB SUBU AND OR XOR SEQ SNE SLT SGT SLE SGE)]) { # r-type instructions that use "rs2"
                $sa = "00000";
                if ($instr[3] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $rs2 = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $rs2 = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $rs2 = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $rs2 = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); my $x = ""; $rs2 = $a;}
                }
                if ($instr[2] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $rs1 = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $rs1 = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $rs1 = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $rs1 = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); my $x = ""; $rs2 = $a;}
                }
                if ($instr[1] =~ m/([0-9]{1,2})/) { 
                        my $aa = $1;
                        if      ($aa <= 1)              {my $a = sprintf("%b", $1); my $x = "0000"; $rd = join("", $x, $a);}
                        elsif   (($aa > 1)&&($aa <= 3)) {my $a = sprintf("%b", $1); my $x = "000"; $rd = join("", $x, $a);}
                        elsif   (($aa > 3)&&($aa <= 7)) {my $a = sprintf("%b", $1); my $x = "00"; $rd = join("", $x, $a);} 
                        elsif   (($aa > 7)&&($aa <= 15)){my $a = sprintf("%b", $1); my $x = "0"; $rd = join("", $x, $a);}
                        elsif   ($aa > 15)              {my $a = sprintf("%b", $1); my $x = ""; $rs2 = $a;}
                }
                if      ($instr[0] eq "MULT")   {$opc2 = "010000";}                     # 0x10 6-bit
                elsif   ($instr[0] eq "MULTU")  {$opc2 = "010001";}             # 0x11
                elsif   ($instr[0] eq "DIV")    {$opc2 = "010010";}             # 0x12
                elsif   ($instr[0] eq "DIVU")   {$opc2 = "010011";}             # 0x13
                elsif   ($instr[0] eq "ADD")    {$opc2 = "100000";}             # 0x20
                elsif   ($instr[0] eq "ADDU")   {$opc2 = "100001";}             # 0x21
                elsif   ($instr[0] eq "SUB")    {$opc2 = "100010";}             # 0x22
                elsif   ($instr[0] eq "SUBU")   {$opc2 = "100011";}             # 0x23
                elsif   ($instr[0] eq "AND")    {$opc2 = "100100";}             # 0x24
                elsif   ($instr[0] eq "OR")     {$opc2 = "100101";}             # 0x25
                elsif   ($instr[0] eq "XOR")    {$opc2 = "100110";}             # 0x26
                elsif   ($instr[0] eq "SEQ")    {$opc2 = "101000";}             # 0x28
                elsif   ($instr[0] eq "SNE")    {$opc2 = "101001";}             # 0x29
                elsif   ($instr[0] eq "SLT")    {$opc2 = "101010";}             # 0x2a
                elsif   ($instr[0] eq "SGT")    {$opc2 = "101011";}             # 0x2b
                elsif   ($instr[0] eq "SLE")    {$opc2 = "101100";}             # 0x2c
                elsif   ($instr[0] eq "SGE")    {$opc2 = "101101";}             # 0x2d
                $z32 = join("", $opc1, $rs1, $rs2, $rd, $sa, $opc2);    # 32-bit binary instruction
                @aa = split(//, $z32);                                                                  # below - separate into chunks of 4 to get hex values
                my $a1 = join("", $aa[0],$aa[1],$aa[2],$aa[3]);         my $aa1 = oct("0b$a1");         my $aaa1 = sprintf "%x", $aa1;
                my $a2 = join("", $aa[4],$aa[5],$aa[6],$aa[7]);         my $aa2 = oct("0b$a2");         my $aaa2 = sprintf "%x", $aa2;
                my $a3 = join("", $aa[8],$aa[9],$aa[10],$aa[11]);       my $aa3 = oct("0b$a3");         my $aaa3 = sprintf "%x", $aa3;
                my $a4 = join("", $aa[12],$aa[13],$aa[14],$aa[15]);     my $aa4 = oct("0b$a4");         my $aaa4 = sprintf "%x", $aa4;
                my $a5 = join("", $aa[16],$aa[17],$aa[18],$aa[19]);     my $aa5 = oct("0b$a5");         my $aaa5 = sprintf "%x", $aa5;
                my $a6 = join("", $aa[20],$aa[21],$aa[22],$aa[23]);     my $aa6 = oct("0b$a6");         my $aaa6 = sprintf "%x", $aa6;
                my $a7 = join("", $aa[24],$aa[25],$aa[26],$aa[27]);     my $aa7 = oct("0b$a7");         my $aaa7 = sprintf "%x", $aa7;
                my $a8 = join("", $aa[28],$aa[29],$aa[30],$aa[31]);     my $aa8 = oct("0b$a8");         my $aaa8 = sprintf "%x", $aa8;
                $return = join("", $aaa1, $aaa2, $aaa3, $aaa4, "_", $aaa5, $aaa6, $aaa7, $aaa8);
                print "$return\n";      # 32-bit HEX instruction. print OUT to put it in file                    
                }
} else {;}
}

close(OUT);

} # end of sub r_type

Below is a sample test that consists of initializing the CPU registers with values, placing instructions in the instruction memory, running Synopsys VCS simulator, and displaying the way data propagates through the CPU pipeline.

A testbench was written to monitor the following nodes:

IF stage:     Program Counter (bus 1)
ID stage:     32-bit instruction fetched from the memory (bus 6)
EXE stage:   Opcode that ALU will use (bus 15)
                   ALU input A (bus 12)
                   ALU input B (bus 13)
MEM stage:  ALU output (bus 19) 
                   Data Memory in (bus 20)
                   Data Memory out (bus 21)
WB stage:    Result written back to CPU registers (bus 33)


Although the CPU executes these instructions correctly, this is by no means sufficient to conclude that the CPU works properly. To test the design further, one needs a verification environment.