多周期CPU设计

------更新一下bug(测试代码有毒)-------

和单周期CPU的设计相同,都是为了实现一系列的指令功能,但需要指出的是何为多周期(注意与前面写道的单周期的区别,这也是设计的关键之处)

多周期CPU指的是将整个CPU的执行过程分成几个阶段,每个阶段用一个时钟去完成,然后开始下一条指令的执行,而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。

理解完多周期与单周期的区别后,开始我们的多周期CPU设计之路(可以随时对应单周期的设计,注意联系与区别)。

需要设计的指令及格式如下:

==>算术运算指令

(1)add rd, rs, rt

000000

rs(5位)

rt(5位)

rd(5位)

reserved

功能:rd<-rs + rt

    (2)sub rd, rs, rt

000001

rs(5位)

rt(5位)

rd(5位)

reserved

完成功能:rd<-rs - rt

(3)addi  rt, rs, immediate

000010

rs(5位)

rt(5位)

immediate(16位)

功能:rt<-rs + (sign-extend)immediate

 

==>逻辑运算指令

(4)or rd, rs, rt

010000

rs(5位)

rt(5位)

rd(5位)

reserved

功能:rd<-rs | rt

(5)and rd, rs, rt

010001

rs(5位)

rt(5位)

rd(5位)

reserved

功能:rd<-rs & rt

(6)ori rt, rs, immediate

010010

rs(5位)

rt(5位)

immediate

功能:rt<-rs | (zero-extend)immediate

 

==>移位指令

(7)sll rd, rs,sa

011000

rs(5位)

未用

rd(5位)

sa

reserved

功能:rd<-rs<<(zero-extend)sa,左移sa位 ,(zero-extend)sa

 

==>传送指令

    (8)move  rd, rs

100000

rs(5位)

00000

rd(5位)

reserved

功能:rd<-rs + $0

 

==>比较指令

(9) slt rd, rs, rt

100111

rs(5位)

rt(5位)

rd(5位)

reserved

功能:如果(rs<rt),则rd=1;  否则 rd=0

 

==>存储器读写指令

(10)sw rt, immediate(rs)

110000

rs(5位)

rt(5位)

immediate(16位)

    功能:memory[rs+ (sign-extend)immediate]<-rt

(11)lw rt, immediate(rs)

110001

rs(5位)

rt(5位)

immediate(16位)

功能:rt <- memory[rs + (sign-extend)immediate]

 

==>分支指令

    (12)beq rs,rt, immediate (说明:immediate是从pc+4开始和转移到的指令之间间隔条数)    

110100

rs(5位)

rt(5位)

immediate(16位)

功能:if(rs=rt) pc <-pc+ 4 + (sign-extend)immediate <<2

 

==>跳转指令

(13)j addr    

111000

addr[27..2]

功能:pc <{pc[31..28],addr[27..2],0,0},转移

(14)jr rs    

111001

rs(5位)

未用

未用

reserved

功能:pc<-rs,转移

 

==>调用子程序指令

(15)jal addr    

111010

addr[27..2]

功能:调用子程序,pc <- {pc[31..28],addr[27..2],0,0};$31<-pc+4,返回地址设置;子程序返回,需用指令 jr  $31。

==>停机指令

(16)halt (停机指令)

111111

00000000000000000000000000(26位)

不改变pc的值,pc保持不变。


设计原理

    (1) 取指令(IF):根据程序计数器pc中的指令地址,从存储器中取出一条指令,同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,当然得到的“地址”需要做些变换才送入pc。

    (2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,确定这条指令需要完成的操作,从而产生相应的操作控制信号,用于驱动执行状态中的各种操作。

    (3) 指令执行(EXE):根据指令译码得到的操作控制信号,具体地执行指令动作,然后转移到结果写回状态。

    (4) 存储器访问(MEM):所有需要访问存储器的操作都将在这个步骤中执行,该步骤给出存储器的数据地址,把数据写入到存储器中数据地址所指定的存储单元或者从存储器中得到数据地址单元中的数据。

    (5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据写回相应的目的寄存器中。

    实验中就按照这五个阶段进行设计,这样一条指令的执行最长需要五个(小)时钟周期才能完成,但具体情况怎样?要根据该条指令的情况而定,有些指令不需要五个时钟周期的,这就是多周期的CPU。


MIPS32的指令的三种格式:

R类型:

31       26 25       21 20      16 15       11 10        6 5       0

    op       

     rs       

      rt      

      rd       

      sa    

   func  

  6位         5位       5位       5位        5位        6位

I类型:

31        26 25         21 20        16 15                       0

     op      

       rs        

       rt        

    immediate        

6位         5位          5位                16位

J类型:

31        26 25                                                0

     op      

              address                           

6位                            26位

其中,

op:为操作码;

rs:为第1个源操作数寄存器,寄存器地址(编号)是00000~11111,00~1F;

rt:为第2个源操作数寄存器,或目的操作数寄存器,寄存器地址(同上);

rd:为目的操作数寄存器,寄存器地址(同上);

sa:为位移量(shift amt),移位指令用于指定移多少位;

func:为功能码,在寄存器类型指令中(R类型)用来指定指令的功能;

immediate:为16位立即数,用作无符号的逻辑操作数、有符号的算术操作数、数据加载(Laod)/数据保存(Store)指令的数据地址字节偏移量和分支指令中相对程序计数器(PC)的有符号偏移量;

   address:为地址。


状态的转移有的是无条件的,例如从IF状态转移到ID 和 EXE状态就是无条件的;有些是有条件的,例如ID 或 EXE状态之后不止一个状态,到底转向哪个状态由该指令功能,即指令操作码决定。每个状态代表一个时钟周期。


图3是多周期CPU控制部件的电路结构,三个D触发器用于保存当前状态,是时序逻辑电路,RST用于初始化状态“000“,另外两个部分都是组合逻辑电路,一个用于产生下一个阶段的状态,另一个用于产生每个阶段的控制信号。从图上可看出,下个状态取决于指令操作码和当前状态;而每个阶段的控制信号取决于指令操作码、当前状态和反映运算结果的状态zero标志等。

 

图4是一个简单的基本上能够在单周期上完成所要求设计的指令功能的数据通路和必要的控制线路图。其中指令和数据各存储在不同存储器中,即有指令存储器和数据存储器。访问存储器时,先给出地址,然后由读/写信号控制(1-写,0-读。当然,也可以由时钟信号控制,但必须在图上画出来)。对于寄存器组,读操作时,给出寄存器地址(编号),输出端就直接输出相应数据;而在写操作时,在 WE使能信号为1时,在时钟边沿触发写入。图中控制信号功能如表1所示,表2是ALU运算功能表。

特别提示,图上增加IR指令寄存器,目的是使指令代码保持稳定,还有pc增加写使能控制信号pcWre,也是确保pc适时修改,原因都是和多周期工作的CPU有关。ADR、BDR、ALUout、ALUM2DR四个寄存器不需要写使能信号,其作用是切分数据通路,将大组合逻辑切分为若干个小组合逻辑,大延时变为多个分段小延时。

表1 控制信号作用

控制信号名

状态“0”

状态“1”

PCWre

PC不更改,相关指令:halt

PC更改,相关指令:除指令halt外

ALUSrcB

来自寄存器堆data2输出,相关指令:add、sub、addi、or、and、ori、move、beq、slt

来自sign或zero扩展的立即数,相关指令:addi、ori、lw、sw、sll

ALUM2Reg

来自ALU运算结果的输出,相关指令:add、sub、addi、or、and、ori、slt、sll、move

来自数据存储器(Data MEM)的输出,相关指令:lw

RegWre

无写寄存器组寄存器,相关指令:

beq、j、sw、jr、halt

寄存器组寄存器写使能,相关指令:add、sub、addi、or、and、ori、move、slt、sll、lw、jal

WrRegData

写入寄存器组寄存器的数据来自pc+4(pc4),相关指令:jal,写$31

写入寄存器组寄存器的数据来自存储器、寄存器组寄存器和ALU运算结果,相关指令:add、addi、sub、or、and、ori、slt、sll、move、lw

InsMemRW

读指令存储器(Ins. Data),初始化为0

写指令存储器

DataMemRW

读数据存储器(Data MEM),相关指令:lw

写数据存储器,相关指令:sw

IRWre

IR(指令寄存器)不更改

IR寄存器写使能。向指令存储器发出读指令代码后,这个信号也接着发出,在时钟上升沿,IR接收从指令存储器送来的指令代码。与每条指令都相关。

 

ALUOp[2..0]

ALU 8种运算功能选择(000-111),看功能表

PCSrc[1..0]

00:pc<-pc+4,相关指令:add、addi、sub、or、ori、and、move、

    slt、sll、sw、lw、beq(zero=0)

01:pc<-pc+4+(sign-extend)immediate,同时zero=1,相关指令:beq

10:pc<-rs,相关指令:jr

11:pc<-pc(31..28],addr,0,0  ,相关指令:j、jal

RegOut[1..0]

写寄存器组寄存器的地址,来自:

00:0x1F($31),相关指令:jal,用于保存返回地址($31<-pc+4)

01:rt字段,相关指令:addi、ori、lw

10:rd字段,相关指令:add、sub、or、and、move、slt、sll

11:未用

ExtSel[1..0]

00:(zero-extend)sa,相关指令:sll

01:(zero-extend)immediate,相关指令:ori

10:(sign-extend)immediate,相关指令:addi、lw、sw、beq

11:未用

相关部件及引脚说明:

InstructionMemory指令存储器

        Iaddr,指令地址输入端口

        DataIn,存储器数据输入端口

        DataOut,存储器数据输出端口

        RW,指令存储器读写控制信号,为1写,为0读

DataMemory数据存储器

        Daddr,数据地址输入端口

        DataIn,存储器数据输入端口

        DataOut,存储器数据输出端口

        RW,数据存储器读写控制信号,为1写,为0读

RegisterFile:(寄存器组)

        Read Reg1,rs寄存器地址输入端口

        Read Reg2,rt寄存器地址输入端口

        Write Reg,将数据写入的寄存器,其地址输入端口(rt、rd)

        Write Data,写入寄存器的数据输入端口

        Read Data1,rs寄存器数据输出端口

        Read Data2,rt寄存器数据输出端口

        WE,写使能信号,为1时,在时钟上升沿写入

IR:    指令寄存器,用于存放正在执行的指令代码

ALU

        result,ALU运算结果

        zero,运算结果标志,结果为0输出1,否则输出0

 

表2 ALU运算功能表       

ALUOp[2..0]

功能

描述

000

Y = A + B

001

Y = A – B

010

if (A<B)

Y = 1; else Y = 0;

比较A与B

011

Y = A>>B

A右移B位

100

Y = A<<B

A左移B位

101

Y = A ∨ B

110

Y = A ∧ B

111

Y = A ⊕ B

异或

 


分析与设计

此次实验是在上次单周期CPU基础上的改进,基本框架是相同的,但是相比单周期CPU的设计最大的不同就是“多周期”,何为多周期,在实现上与单周期又有何区别?简单的来说就是每个时钟周期内只执行一个阶段,而不是像单周期那样一个时钟周期就执行完整个指令,这就是单周期与多周期的主要区别。当然,由此也衍生出了其他的几个区别,比如,数据传输的延迟问题,增加的跳转指令等使得数据通路图变得复杂了很多。

根据这些区别,就可以开始在单周期CPU基础进行改进了。具体如下:

首先,确定每个指令的状态转化关系,具体转化图见上面原理分析,例如指令add的指令状态转化是IF(000)->ID(001)->EXE(110)->WB(111)->IF;所以,需要设置两个3位的状态变量(stage)和(next_stage)来表示状态的转变。由于指令是用来控制指令执行的,所以需要把指令状态的转变实现发在控制单元(controlUnit)中。

其次,就是数据传输延迟的问题,从数据通路图中可以看出,寄存器(RegisterFile)输出处存在两个延迟(ADR)和(BDR),计算单元(ALU)的输出处存在一个延迟,数据存储器(DataMemory)输出存在一个延迟,指令寄存器(InsMemory)输出处存在一个延迟,当然这里延迟需要控制信号IRWre的额外控制。综上来看,前四个延迟可以设计一个叫DataLate的简单模板模块(因为它们的输入、输出完全相同),具体实现如下。最后一个延迟可以放在INSMemory模块中。

module DataLate(input [31:0] i_data,

                input clk,

                output reg [31:0] o_data);

 

   always @(negedge clk) begin

       o_data = i_data;

    end

endmodule

最后就是融入增加的跳转指令,比如根据数据通路图增添了一个如下的地址模块


另外,其他增加的数据线的增加就具体加入到相应模块中作为输入、输出。

所以,现在在单周期CPU的基础上,可以画出整个多周期CPU的逻辑图。


一、         控制单元(controlUnit.v)

相比单周期的CU,多周期的CU在输入输出上大致相同,但具体控制内容、存在比较大的差别。

1、指令状态转化的实现,前面已经提到。

2、控制信号的赋值。由于多周期指令信号控制状态的不同而可能改变,所以这里实现各控制信号的时候不再像单周期那样单纯利用操作码来实现。

类似的,写出控制信号与指令、指令状态的关系表,如下:

Stage

Ins

Zero

PCWre

ALUSrcB

ALUM2Reg

RegWre

WrRegData

InsMemRW

DataMemRW

IRWre

ExtSel[1..0]

PCSrc

[1..0]

RegOut

[1..0]

ALUOp[2..0]

sif

(000)

x

x

1

x

x

0

x

1

0

1

xx

xx

xx

xxx

sid

(001)

j

x

0

x

x

0

x

x

0

0

xx

11

xx

xxx

jal

x

0

x

x

1

0

x

0

0

xx

11

00

xxx

jr

x

0

x

x

0

x

x

0

0

xx

10

xx

xxx

halt

x

0

x

x

0

x

x

0

0

xx

xx

xx

xxx

exe1

(110)

add

x

0

0

x

0

x

x

0

0

xx

xx

xx

000

sub

x

0

0

x

0

x

x

0

0

xx

xx

xx

001

addi

x

0

1

x

0

x

x

0

0

10

xx

xx

000

or

x

0

0

x

0

x

x

0

0

xx

xx

xx

101

and

x

0

0

x

0

x

x

0

0

xx

xx

xx

110

ori

x

0

1

x

0

x

x

0

0

01

xx

xx

101

move

x

0

0

x

0

x

x

0

0

xx

xx

xx

000

slt

x

0

0

x

0

x

x

0

0

xx

xx

xx

010

sll

x

0

1

x

0

x

x

0

0

00

xx

xx

100

exe2

(101)

beq

0

0

0

x

0

x

x

0

0

10

00

xx

001

beq

1

0

0

x

0

x

x

0

0

10

01

xx

001

exe3

(010)

sw

x

0

1

x

0

x

x

0

0

10

xx

xx

000

lw

x

0

1

x

0

x

x

0

0

10

xx

xx

000

smem

(011)

sw

x

0

x

x

0

x

x

1

0

10

00

xx

xxx

lw

x

0

x

x

0

x

x

0

0

10

xx

xx

xxx

wb1

(111)

add

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

sub

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

addi

x

0

x

0

1

1

x

0

0

xx

00

01

xxx

or

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

and

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

ori

x

0

x

0

1

1

x

0

0

xx

00

01

xxx

move

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

slt

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

sll

x

0

x

0

1

1

x

0

0

xx

00

10

xxx

wb2

(100)

lw

x

0

x

1

1

1

x

0

0

xx

00

01

xxx

 

根据以上关系表,写出对应控制信号的实现。

`timescale 1ns / 1ps


module controlUnit(input [5:0] opcode, 
                   input zero, clk, Reset,
		   output reg PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW,
		   output reg [1:0] ExtSel, RegOut, PCSrc,
						 output reg [2:0] ALUOp, state_out);
    parameter [2:0] sif = 3'b000,   // IF state
	                 sid = 3'b001,   // ID state
						  exe1 = 3'b110,  // add、sub、addl、or、and、ori、move、slt、sll
						  exe2 = 3'b101,  // beq
						  exe3 = 3'b010,  // sw、lw
						  smem = 3'b011,  // MEM state
						  wb1 = 3'b111,   // add、sub、addl、or、and、ori、move、slt、sll
						  wb2 = 3'b100;   // lw
						  
	 parameter [5:0] addi = 6'b000010,
                    ori = 6'b010010,
                    sll = 6'b011000,
                    add = 6'b000000,
                    sub = 6'b000001,
                    move = 6'b100000,
                    slt = 6'b100111,
                    sw = 6'b110000,
                    lw = 6'b110001,
                    beq = 6'b110100,
                    j = 6'b111000,
                    jr = 6'b111001,
                    Or = 6'b010000,
                    And = 6'b010001,
                    jal = 6'b111010,
                    halt = 6'b111111;
								 
	 reg [2:0] state, next_state;
	
	initial begin
	   PCWre = 0;
		InsMemRW = 0;
		IRWre = 0;
		WrRegData = 0;
		RegWre = 0;
		ALUSrcB = 0;
		ALUM2Reg = 0;
		DataMemRW = 0;
		ExtSel = 2'b11;
		RegOut = 2'b11;
		PCSrc = 2'b00;
		ALUOp = 0;
		state = sif;
		state_out = state;
	end
	
	always @(posedge clk) begin
	     if (Reset == 0) begin
		      state <= sif;
		  end else begin
		      state <= next_state;
		  end
		  state_out = state;
	 end
	
	always @(state or opcode) begin
	case(state)
	    sif: next_state = sid;
		 sid: begin
		     case (opcode[5:3])
			      3'b111: next_state = sif; // j, jal, jr, halt等指令
               3'b110: begin
                   if (opcode == 6'b110100) next_state = exe2; // beq指令
                   else next_state = exe3; // sw, lw指令
               end
               default: next_state = exe1; // add, sub, slt, sll等指令
           endcase
		 end
		 exe1: next_state = wb1;
       exe2: next_state = sif;
       exe3: next_state = smem;
		 smem: begin
		     if (opcode == 6'b110001) next_state = wb2; // lw指令
                else next_state = sif; // sw指令
       end
       wb1: next_state = sif;
       wb2: next_state = sif;
       default: next_state = sif;
	endcase
	end
		 
	always @(state) begin
	
        // 确定PCWre的值
        if (state == sif && opcode != halt) PCWre = 1;
        else PCWre = 0;
		  
        // 确定InsMemRW的值
        InsMemRW = 1;
		  
        // 确定IRWre的值
        if (state == sif) IRWre = 1;
        else IRWre = 0;
		  
        // 确定WrRegData的值
        if (state == wb1 || state == wb2) WrRegData = 1;
        else WrRegData = 0;
		  
        // 确定RegWre的值
        if (state == wb1 || state == wb2 || opcode == jal) RegWre = 1;
        else RegWre = 0;
        
		  // 确定ALUSrcB的值
        if (opcode == addi || opcode == ori || opcode == sll || opcode == sw || opcode == lw) ALUSrcB = 1;
        else ALUSrcB = 0;
        
		  // 确定DataMemRW的值
        if (state == smem && opcode == sw) DataMemRW = 1;
        else DataMemRW = 0;
        
		  // 确定ALUM2Reg的值
        if (state == wb2) ALUM2Reg = 1;
        else ALUM2Reg = 0;
        
		  // 确定ExtSel的值
        if (opcode == ori) ExtSel = 2'b01;
        else if (opcode == sll) ExtSel = 2'b00;
        else ExtSel = 2'b10;
        
		  // 确定RegOut的值
        if (opcode == jal) RegOut = 2'b00;
        else if (opcode == addi || opcode == ori || opcode == lw) RegOut = 2'b01;
        else RegOut = 2'b10;
        
		  // 确定PCSrc的值
        case(opcode)
            j: PCSrc = 2'b11;
            jal: PCSrc = 2'b11;
            jr: PCSrc = 2'b10;
            beq: begin
                if (zero) PCSrc = 2'b01;
                else PCSrc = 2'b00;
            end
            default: PCSrc = 2'b00;
        endcase
        
		  // 确定ALUOp的值
        case(opcode)
            sub: ALUOp = 3'b001;
            Or: ALUOp = 3'b101;
            And: ALUOp = 3'b110;
            ori: ALUOp = 3'b101;
            slt: ALUOp = 3'b010;
            sll: ALUOp = 3'b100;
            beq: ALUOp = 3'b001;
            default: ALUOp = 3'b000;
        endcase
        
		  // 防止在IF阶段写数据
        if (state == sif) begin
            RegWre = 0;
            DataMemRW = 0;
        end
    end
	
	 
endmodule

二、         算术运算单元(ALU)

模块ALU接收寄存器的数据和控制信号作为输入,将结果输出,具体设计如下:

`timescale 1ns / 1ps
module ALU(input [31:0] ReadData1, ReadData2, inExt,
           input ALUSrcB,
			  input [2:0] ALUOp,
			  output wire zero,
			  output reg [31:0] result);
	 
	 initial begin
        result = 0;
    end

    wire [31:0] B;
	 assign B = ALUSrcB? inExt : ReadData2;
	 assign zero = (result? 0 : 1);
	 
	 always @(ReadData1 or ReadData2 or B or ALUOp) begin
        case(ALUOp)
            3'b000: result = ReadData1 + B;  // A + B
            3'b001: result = ReadData1 - B;  // A - B
            3'b010: result = (ReadData1 < B ? 1 : 0);  // 比较A与B
            3'b011: result = ReadData1 >> B; // A右移B位
            3'b100: result = ReadData1 << B; // A左移B位
            3'b101: result = ReadData1 | B; // 或
            3'b110: result = ReadData1 & B; // 与
            3'b111: result = (~ReadData1 & B) | (ReadData1 & ~B); // 异或
        default: result = 0;
    endcase
  end
	 
endmodule


三、         PC模块(PC)

相比单周期的PC单元,这里的PC模块中多了一个四选一的的地址数据选择器,目的在于根据控制信号正确匹配pc地址,同样输出当前PC地址,具体设计如下:

`timescale 1ns / 1ps
module PC(input clk, Reset, PCWre,
          input [1:0] PCSrc,
          input wire [31:0] imm, addr, RDout1,
          output reg [31:0] Address);
			 
	 always @(PCWre or negedge Reset) begin // 这里和单周期不太一样,存在延迟的问题,只有当pcWre改变的时候或者Reset改变的时候再检测
        if (Reset == 0) begin
            Address = 0;
        end else if (PCWre) begin
            if (PCSrc == 2'b00) begin
				    Address = Address+4;
				end else if (PCSrc == 2'b01) begin
				    Address = imm*4+Address+4;
				end else if (PCSrc == 2'b10) begin
				    Address = RDout1;
				end else if (PCSrc == 2'b11) begin
				    Address = addr;
				end
        end
    end

endmodule

四、         PCAddr模块(补充address)

用于跳转指令的地址补充,输出32位的地址,模块实现如下:

`timescale 1ns / 1ps

module PCAddr(input [25:0] in_addr,
              input [31:0] PC0,
				  output reg [31:0] addr);
    wire [27:0] mid;
	 assign mid = in_addr << 2;
    always @(in_addr) begin
        addr <= {PC0[31:28], mid[27:0]};
    end

endmodule

五、         扩展单元(Extend)

相比单周期的扩展,此处的扩展内容多了一些,包括sa扩展、立即数扩展等,扩展选择由控制信号ExtSel控制,最后输出完整32位数据。

`timescale 1ns / 1ps

module Extend(input [15:0] in_num,
              input [1:0] ExtSel,
				  output reg [31:0] out);
				  
    always @(in_num or ExtSel) begin
        case(ExtSel)
            2'b00: out <= {{27{0}}, in_num[10:6]}; // 扩充 sa
            2'b01: out <= {{16{0}}, in_num[15:0]}; // 扩充立即数, 如 ori指令
            2'b10: out <= {{16{in_num[15]}}, in_num[15:0]}; // 符号扩充立即数,如addi、lw、sw、beq指令
            default: out <= {{16{in_num[15]}}, in_num[15:0]}; // 默认符号扩展
        endcase
    end

endmodule

六、         数据存储单元(DataMemory)

数据存储单元的功能是读取数据,根据数据通路图可以有如下模块设计:

`timescale 1ns / 1ps

module DataMemory(input [31:0] addr, Data2,
                  input DataMemRW,
		  output reg [31:0] DataOut);
					  
	 reg [7:0] memory [0:63];
	 integer i;
	 initial begin
	     for (i = 0; i < 64; i = i+1) memory[i] <= 0;
	 end
	 
    always @(addr or Data2 or DataMemRW) begin
      if (DataMemRW) begin // write data
          memory[addr] = Data2[31:24];
          memory[addr+1] = Data2[23:16];
          memory[addr+2] = Data2[15:8];
          memory[addr+3] = Data2[7:0];
      end else begin // read data
          DataOut[31:24] = memory[addr];
          DataOut[23:16] = memory[addr+1];
          DataOut[15:8] = memory[addr+2];
          DataOut[7:0] = memory[addr+3];
      end
    end

endmodule

七、         指令存储单元(InsMemory)

将指令集以二进制的文件(my_store.txt)存入当前目录,然后通过读取文件的方式将指令存储到内存中,最后实现指令的读取。其中,

内部指令实现:

将需要测试的汇编指令程序转化为指令代码,如下:

地址

汇编程序

指令代码

op(6)

rs(5)

rt(5)

rd(5)/immediate (16)

16进制数代码

0x00000000

j 0x00000008

111000

00 00000000 00000000 00000010

 

0x00000004

jr  $31

111001

11111

00000

0x00000008

addi  $1,$0,8

000010

00000

00001

0000 0000 0000 1000

=

08010008

0x0000000C

ori  $2,$0,2

010010

00000

00010

0000 0000 0000 0010

=

48020002

0x00000010

add  $3,$1,$2

000000

00001

00010

00011 00000000000

=

00221800

0x00000014

sub  $4,$1,$2

000001

00001

00010

00100 00000000000

=

04222000

0x00000018

and  $5,$3,$2

010001

00011

00010

00101 00000000000

=

44622800

0x0000001C

or  $6,$1,$2

010000

00001

00010

00110 00000000000

=

40223000

0x00000020

move  $11,$1

100000

00001

00000

01011 00000000000

=

80205800

0x00000024

slt  $7,$1,$2

100111

00001

00010

00111 00000000000

=

9C223800

0x00000028

slt  $8,$2,$1

100111

00010

00001

01000 00000000000

=

9C414000

0x0000002C

sll  $2,$2,2

011000

00010

00000

00010 00010 000000

=

60401080

0x00000030

beq  $1,$2,-2 转02C

110100

00001

00010

1111 1111 1111 1110

=

D022FFFE

0x00000034

sw  $9,0($3)

110000

00011

01001

0000 0000 0000 0000

=

C0690000

0x00000038

jal  0x00000004

111010

00 00000000 00000000 0000001

=

 

0x0000003C

lw  $10,2($1)

110001

00001

01010

0000 0000 0000 0010

=

C42A0002

0x00000040

halt

111111

00000

00000

0000000000000000

=

FC000000

 

根据上表,可以创建my_store.txt的二进制指令文件,从而进行存取,二进制文件如下:


最终模块设计如下:

`timescale 1ns / 1ps

module InsMemory(input [31:0] addr,
                 input InsMemRW, IRWre, clk,
					  output reg [31:0] ins);
					  
    reg [31:0] ins_out;
	 reg [7:0] mem [0:127];
	 
	 initial begin
	     $readmemb("my_store.txt", mem);
		  //ins_out = 0;
	 end

    always @( addr or InsMemRW) begin
        if (InsMemRW) begin
          ins_out[31:24] = mem[addr];
          ins_out[23:16] = mem[addr+1];
          ins_out[15:8] = mem[addr+2];
          ins_out[7:0] = mem[addr+3];
        end
	 end
	 
	 always @(posedge clk) begin
	     if (IRWre) ins <= ins_out;
	 end

endmodule

八、         寄存器单元(RegFile)

寄存器文件单元的功能是接收instructionMemory中的rs,rt,rd作为输入,输出对应寄存器的数据,从而达到取寄存器里的数据的目的,具体设计如下:

需要注意的是,在其内部实现的过程中,为了防止0号寄存器写入数据需要在writeReg的时候多加入一个判断条件,即writeReg不等于0时写入数据。

`timescale 1ns / 1ps

module RegFile(input [4:0] rs, rt, rd,
               input clk, RegWre, WrRegData,
		      	input [1:0] RegOut,
			  	   input [31:0] PC4, memData,
			 	   output reg [31:0] data1, data2);

    reg [31:0] i_data;
	 
	 
	 reg [4:0] temp;
	 
	 reg [31:0] register [0:31];
	 integer i;
    initial begin
        for (i = 0 ; i < 32; i = i+1) 
		      register[i] = 0;
    end
    
    always @(negedge clk) begin
	     case(RegOut)
	         2'b00: temp = 5'b11111;
		      2'b01: temp = rt;
		      2'b10: temp = rd;
				default temp = 0;
        endcase
		  assign i_data = WrRegData? memData : PC4;
	     assign data1 = register[rs];
        assign data2 = register[rt];
        if ((temp != 0) && (RegWre == 1)) begin // temp != 0 确保零号寄存器不会改变
            register[temp] <= i_data;
        end
    end

endmodule

九、         二选一数据模块(DataSelect_2)

简单的数据二选一,用于数据存储单元后面的数据选择,可见数据通路图,实现如下:

`timescale 1ns / 1ps

module DataSelect_2(input [31:0] A, B,
                    input Sign,
						  output wire [31:0] Get);

    assign Get = Sign ? B : A;

endmodule

十、         数据延迟模板模块(DataLate)

用于数据延迟,目的是使得数据正常输入输出,从数据通路图中可知此模板可在四处地方有用,已分析,所以具体模板实现如下:

`timescale 1ns / 1ps

module DataLate(input [31:0] i_data,
                input clk,
                output reg [31:0] o_data);

    always @(negedge clk) begin
        o_data = i_data;
    end

endmodule

十一、顶层模块(Top)

顶层模块(Top)是整个CPU的控制模块,通过连接各个子模块来达到运行CPU的目的,整个模块设计可以如下:

`include "ALU.v"
`include "DataLate.v"
`include "DataMemory.v"
`include "DataSelect_2.v"
`include "Extend.v"
`include "InsMemory.v"
`include "PC.v"
`include "PCAddr.v"
`include "RegFile.v"
`include "ControlUnit.v"
`timescale 1ns / 1ps

module Top(input clk, reset,
           output wire [2:0] state_out,
           output wire [5:0] opcode,
			  output wire [4:0] rs, rt, rd,
           // output ins[31:26], ins[25:21], ins[20:16], ins[15:11],
           output wire [31:0] ins, ReadData1, ReadData2, pc0, result);
			  
	 assign opcode = ins[31:26];
	 assign rs = ins[25:21];
	 assign rt = ins[20:16];
	 assign rd = ins[15:11];

    // 数据通路
    wire [31:0] j_addr, out1, out2, result1, i_IR, extendData, LateOut1, LateOut2, DataOut;
    wire zero;
	 
    // 控制信号
    wire [2:0] ALUOp;
    wire [1:0] ExtSel, RegOut, PCSrc;
    wire PCWre, IRWre, InsMemRW, WrRegData, RegWre, ALUSrcB, DataMemRW, ALUM2Reg;

	 PC pc(clk, reset, PCWre, PCSrc, extendData, j_addr, ReadData1, pc0);

	 InsMemory insmemory(pc0, InsMemRW, IRWre, clk, ins);
	
	 PCAddr pcaddr(ins[25:0], pc0, j_addr);
	 
	 RegFile regfile(ins[25:21], ins[20:16], ins[15:11], clk, RegWre, WrRegData, RegOut, (pc0+4), LateOut2, ReadData1, ReadData2);
	
         DataLate ADR(ReadData1, clk, out1);
	 DataLate BDR(ReadData2, clk, out2);
	
	 Extend extend(ins[15:0], ExtSel, extendData);
	 
         ALU alu(out1, out2, extendData, ALUSrcB, ALUOp, zero, result);
	 
	 DataLate ALUout(result, clk, result1);
	 
	 DataMemory datamemory(result1, out2, DataMemRW, DataOut);
	 
	 DataSelect_2 dataselect_2(result, DataOut, ALUM2Reg, LateOut1);
	
	 DataLate ALUM2DR(LateOut1, clk, LateOut2);
	 
	controlUnit control(ins[31:26], zero, clk, reset,PCWre, InsMemRW, IRWre, WrRegData, RegWre, ALUSrcB, ALUM2Reg, DataMemRW, ExtSel, RegOut, PCSrc, ALUOp, state_out);
	

endmodule


十二、测试程序(test)

从顶层模块中可以看出整个CPU的输入只有时钟信号clk和重置信号Reset,所以测试程序代码比较简单。(参照单周期CPU)

             clk = 0;

              reset= 0;

              clk= ~clk;

 

              //Wait 100 ns for global reset to finish

              #100;

             reset = 1;

              forever #100 clk = ~clk;

 

最后,套路贴结果(部分):



至此,完工!!!

  • 52
    点赞
  • 239
    收藏
    觉得还不错? 一键收藏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值