2017/06/08: 當時單週期cpu寫的比較倉促,沒有深入的進行調試,我準備在放假的時候重構一下代碼, 然後把博文改進一下,現在實在沒有時間,很抱歉~ 不過多週期我有調試過的,所以有需要的可以移步到我的多週期cpu設計
一、 實驗目的
(1) 掌握單週期CPU數據通路圖的構成、原理及其設計方法;
(2) 掌握單週期CPU的實現方法,代碼實現方法;
(3) 認識和掌握指令與CPU的關係;
(4) 掌握測試單週期CPU的方法。
二、 實驗內容
設計一個單週期CPU,該CPU至少能實現以下指令功能操作。需設計的指令與格式如下:
特別說明:
immediate是從PC+4地址開始和轉移到的指令之間指令條數。immediate符號擴展之後左移2位再相加。爲什麼要左移2位?由於跳轉到的指令地址肯定是4的倍數(每條指令佔4個字節),最低兩位是“00”,因此將immediate放進指令碼中的時候,是右移了2位的,也就是以上說的“指令之間指令條數”。
補充:
1、PC、寄存器組和存儲器寫狀態使用時鐘觸發。
2、指令存儲器和數據存儲器存儲單元寬度一律使用8位,即一個字節的存儲單位。不能使用32位作爲存儲器存儲單元寬度。
3、控制器部分要學會用控制信號真值表方法分析問題並寫出邏輯表達式;或者用case語句方法逐個產生各指令控制信號。
4、必須寫一段測試用的彙編程序,而且必須包含所要求的所有指令,beq指令必須檢查兩種情況:“等”和“不等”。
三、實驗原理
單週期CPU指的是一條指令的執行在一個時鐘週期內完成,然後開始下一條指令的執行,即一條指令用一個時鐘週期完成。電平從低到高變化的瞬間稱爲時鐘上升沿,兩個相鄰時鐘上升沿之間的時間間隔稱爲一個時鐘週期。時鐘週期一般也稱振盪週期(如果晶振的輸出沒有經過分頻就直接作爲CPU的工作時鐘,則時鐘週期就等於振盪週期。若振盪週期經二分頻後形成時鐘脈衝信號作爲CPU的工作時鐘,這樣,時鐘週期就是振盪週期的兩倍。)
CPU在處理指令時,一般需要經過以下幾個步驟:
(1) 取指令(IF):根據程序計數器PC中的指令地址,從存儲器中取出一條指令,同時,PC根據指令字長度自動遞增產生下一條指令所需要的指令地址,但遇到“地址轉移”指令時,則控制器把“轉移地址”送入PC,當然得到的“地址”需要做些變換才送入PC。
(2) 指令譯碼(ID):對取指令操作中得到的指令進行分析並譯碼,確定這條指令需要完成的操作,從而產生相應的操作控制信號,用於驅動執行狀態中的各種操作。
(3) 指令執行(EXE):根據指令譯碼得到的操作控制信號,具體地執行指令動作,然後轉移到結果寫回狀態。
(4) 存儲器訪問(MEM):所有需要訪問存儲器的操作都將在這個步驟中執行,該步驟給出存儲器的數據地址,把數據寫入到存儲器中數據地址所指定的存儲單元或者從存儲器中得到數據地址單元中的數據。
(5) 結果寫回(WB):指令執行的結果或者訪問存儲器中得到的數據寫回相應的目的寄存器中。
單週期CPU,是在一個時鐘週期內完成這五個階段的處理。
圖2是一個簡單的基本上能夠在單週期CPU上完成所要求設計的指令功能的數據通路和必要的控制線路圖。其中指令和數據各存儲在不同存儲器中,即有指令存儲器和數據存儲器。訪問存儲器時,先給出地址,然後由讀或寫信號控制操作。對於寄存器組,讀操作時,先給出地址,輸出端就直接輸出相應數據;而在寫操作時,在WE使能信號爲1時,在時鐘邊沿觸發寫入。圖中控制信號作用如表1所示,表2是ALU運算功能表。
相關部件及引腳說明:
Instruction Memory:指令存儲器,
Iaddr,指令存儲器地址輸入端口
IDataIn,指令存儲器數據輸入端口(指令代碼輸入端口)
IDataOut,指令存儲器數據輸出端口(指令代碼輸出端口)
RW,指令存儲器讀寫控制信號,爲1寫,爲0讀
Data Memory:數據存儲器,
Daddr,數據存儲器地址輸入端口
DataIn,數據存儲器數據輸入端口
DataOut,數據存儲器數據輸出端口
RD,數據存儲器讀控制信號,爲1讀
WR,數據存儲器寫控制信號,爲1寫
Register File:寄存器組
Read Reg1,rs寄存器地址輸入端口
Read Reg2,rt寄存器地址輸入端口
Write Reg,將數據寫入的寄存器端口,其地址來源rt或rd字段
Write Data,寫入寄存器的數據輸入端口
Read Data1,rs寄存器數據輸出端口
Read Data2,rt寄存器數據輸出端口
WE,寫使能信號,爲1時,在時鐘上升沿寫入
ALU: 算術邏輯單元
result,ALU運算結果
zero,運算結果標誌,結果爲0輸出1,否則輸出0
需要說明的是以上數據通路圖是根據要實現的指令功能的要求畫出來的,同時,還必須確定ALU的運算功能(當然,以上指令沒有完全用到提供的ALU所有功能,但至少必須能實現以上指令功能操作)。從數據通路圖上可以看出控制單元部分需要產生各種控制信號,當然,也有些信號必須要傳送給控制單元。從指令功能要求和數據通路圖的關係得出以上表1,這樣,從表1可以看出各控制信號與相應指令之間的相互關係,根據這種關係就可以得出控制信號與指令之間的關係表(留給學生完成),再根據關係表可以寫出各控制信號的邏輯表達式,這樣控制單元部分就可實現了。
指令執行的結果總是在時鐘下降沿開始保存到寄存器和存儲器中,PC的改變是在時鐘上升沿進行的,這樣穩定性較好。另外,值得注意的問題,設計時,用模塊化的思想方法設計,關於ALU設計、存儲器設計、寄存器組設計等等,也是必須認真考慮的問題。
四、實驗設備
PC機一臺,BASYS 3 板一塊,Xilinx Vivado 開發軟件一套。
五. 實驗分析與設計
下面是我設計單週期CPU的詳細過程:
1.設計Control Unit
由數據通路圖可知,
**輸入信號爲:**opCode、zero
輸出信號爲: PCWre, ALUSrcA, ALUSrcB,DBDataSrc,RegWre,InsMemRW, RD,WR, ExtSel,RegDst,PCSrc,ALUOp
設計control unit,必須列出控制信號與指令的關係表
然後根據該表assign對應的值。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 20:43:40
// Design Name:
// Module Name: controlUnit
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module controlUnit(
// 根據數據通路圖定義輸入和輸出
input [5:0] opCode,
input zero,
output PCWre,
output ALUSrcA,
output ALUSrcB,
output DBDataSrc,
output RegWre,
output InsMemRW,
output RD,
output WR,
output ExtSel,
output RegDst,
output PCSrc,
output [2:0] ALUOp
);
// 根據opcode定義控制信號爲1或0
assign PCWre = (opCode == 6'b111111) ? 0 : 1;
assign ALUSrcA = (opCode == 6'b011000) ? 1 : 0;
assign ALUSrcB = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100110 || opCode == 6'b100111) ? 1 : 0;
assign DBDataSrc = (opCode == 6'b100111) ? 1 : 0;
assign RegWre = (opCode == 6'b100110 || opCode == 6'b110000 || opCode == 6'b111111) ? 0 : 1;
assign InsMemRW = 0;
assign RD = (opCode == 6'b100111) ? 0 : 1;
assign WR = (opCode == 6'b100110) ? 0 : 1;
assign ExtSel = (opCode == 6'b010000) ? 0 : 1;
assign RegDst = (opCode == 6'b000001 || opCode == 6'b010000 || opCode == 6'b100111) ? 0 : 1;
assign PCSrc = (opCode == 6'b110000 && zero == 1) ? 1 : 0;
assign ALUOp[2] = (opCode == 6'b010000 || opCode == 6'b010001 || opCode == 6'b010000) ? 1 : 0;
assign ALUOp[1] = 0;
assign ALUOp[0] = (opCode == 6'b000010 || opCode == 6'b010001 || opCode == 6'b110000) ? 1 : 0;
endmodule
2.設計ALU
由數據通路圖可知,
輸入信號爲: ReadData1, ReadData2,inExt,insa,ALUSrcA,ALUSrcB,ALUOp
輸出信號爲: zero, result
然後根據表2 ALU運算功能表對result與zero賦值
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 21:32:08
// Design Name:
// Module Name: ALU
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module ALU(
// 根據數據通路圖定義下列的輸入輸出
input [31:0] ReadData1,
input [31:0] ReadData2,
input [31:0] inExt,
input [5:0] insa,
input ALUSrcA, ALUSrcB,
input [2:0] ALUOp,
output reg zero,
output reg [31:0] result
);
// 定義兩個輸入端口
wire [31:0] A;
wire [31:0] B;
// ALU輸入端口的數據選擇器
assign A = ALUSrcA ? insa :ReadData1;
assign B = ALUSrcB? inExt : ReadData2;
// 只要輸入的值發生變化,就執行begin與end之間的內容
always @(ReadData1 or ReadData2 or inExt or ALUSrcA or ALUSrcB or ALUOp or A or B)
begin
case(ALUOp)
// 根據ALUOp相應的實現運算功能
3'b000: begin
result = A + B;
zero = (result == 0)? 1 : 0;
end
3'b001: begin
result = A - B;
zero = (result == 0)? 1 : 0;
end
3'b010: begin
result = (A < B) ? 1 : 0;
zero = (result == 0)? 1 : 0;
end
3'b100: begin
result = A | B;
zero = (result == 0)? 1 : 0;
end
3'b101: begin
result = A & B;
zero = (result == 0)? 1 : 0;
end
3'b011: begin
result = B << A;
zero = (result == 0)? 1 : 0;
end
3'b110: begin
result = A ^ B;
zero = (result == 0)? 1 : 0;
end
3'b111: begin
result = A ^~ B;
zero = (result == 0)? 1 : 0;
end
endcase
end
endmodule
3.設計PC
由數據通路圖可知,
輸入信號爲: clk, Reset, PCWre, PCSrc, immediate,
輸出信號爲: Address
判斷是否有Reset信號,如果有,將PC置爲0;
判斷是否有PCSrc信號,如果有,將immediate作爲偏移值加上PC中原有值存在pc中;
否則pc自增。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 21:47:33
// Design Name:
// Module Name: PC
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module PC(clk, Reset, PCWre, PCSrc, immediate, Address);
// 根據數據通路圖定義輸入輸出
input clk, Reset, PCWre, PCSrc;
input [15:0] immediate; // 從指令中取出進行符號擴展後得來的
output [31:0] Address;
reg [31:0] Address;
// clock上升沿到來或Reset下降沿到來時,執行下列函數
always @(posedge clk or negedge Reset)
begin
if (Reset == 0) begin
Address = 0;
end
else if (PCWre) begin // PCWre爲1時PC更改,PCWre爲0時PC不更改
if (PCSrc) Address = Address + 4 + immediate*4; // 跳轉指令
else Address = Address + 4; // 跳轉到下一指令
end
end
endmodule
4.設計signZeroExtend
由數據通路圖可知,
輸入信號爲: immediate, ExtSel
輸出信號爲: out
符號擴展很簡單,根據立即數的最高位進行補位:
如果立即數最高位爲1,則前面全補1;
如果立即數最高位爲0,則前面全補0.
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 21:52:27
// Design Name:
// Module Name: signZeroExtend
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module signZeroExtend(
// 根據數據通路圖定義輸入和輸出
input [15:0] immediate,
input ExtSel,
output [31:0] out
);
assign out[15:0] = immediate; // 後15位存儲立即數
assign out[31:16] = ExtSel? (immediate[15]? 16'hffff : 16'h0000) : 16'h0000; // 前16位根據立即數符號進行補1或0的操作
endmodule
5.設計DataMemory
由數據通路圖可知,
輸入信號爲: Daddr, DataIn,RD,WR
輸出信號爲: DataOut
根據WR,RD判斷數據的讀寫,然後執行相應的讀寫操作。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 22:06:47
// Design Name:
// Module Name: dataMemory
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module dataMemory(
// 根據數據通路圖定義輸入和輸出
input [31:0] DAddr,
input [31:0] DataIn,
input RD,
input WR,
output reg [31:0] DataOut
);
// 實驗要求:指令存儲器和數據存儲器存儲單元寬度一律使用8位
// 所以將一個32位的數據拆成4個8位的存儲器單元存儲
// 將4個8位存儲器恢復成32位存儲器
reg[7:0] memory[0:127];
reg[31:0] address;
// read data
always @(RD) begin
if (RD == 0) begin
// 因爲一條指令由4個存儲單元存儲,所以要乘以4
address = (DAddr << 2);
// DataOut是32位的,將4個八位的內存單元合併生成32位
// 左移24位用於設置前八位,以此類推
DataOut = (memory[address]<<24)+(memory[address+1]<<16)+(memory[address+2]<<8)+memory[address+3];
end
end
// write data
integer i;
initial begin
for (i = 0; i < 128; i = i+1) memory[i] <= 0;
end
always @(WR or DAddr or DataIn)
begin
if (WR == 0) begin
address = DAddr << 2;
memory[address] = DataIn[31:24];
memory[address+1]= DataIn[23:16];
memory[address+2]=DataIn[15:8];
memory[address+3]=DataIn[7:0];
end
end
endmodule
6.設計instructionMemory
由數據通路圖可知,
instructionMemory
輸入信號爲: pc
輸出信號爲: op, rs, rt, rd, immediate,sa
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 23:00:48
// Design Name:
// Module Name: instructionMemory
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module instructionMemory(
// 根據數據通路圖定義輸入和輸出
input [31:0] pc,
output [5:0] op,
output [4:0] rs, rt, rd,
output [15:0] immediate,
output [5:0] sa);
// 實驗要求:指令存儲器和數據存儲器存儲單元寬度一律使用8位
// 所以將一個32位的指令拆成4個8位的存儲器單元存儲
// 從文件取出後將他們合併爲32位的指令
reg [7:0] mem[0:127];
reg [31:0] address;
reg [31:0] instruction;
initial begin
$readmemb("D:/Xilinx/VivadoProject/SingleCPU/instructions.txt", mem); // 從文件中讀取指令二進制代碼賦值給mem
instruction = 0; // 指令初始化
end
always @(pc) begin
// pc中一個單元是1byte,即8位,那麼32位地址需要4個單元
// pc++ <=> pc += 4(100),即pc的最後兩位都爲0
// 從第三位開始取,即是代表指令的個數
address = pc[5:2] << 2; // 因爲4個內存單元存儲一個指令,所以除以4得到第一個內存單元的下標
// 將4個8位的內存單元合併爲32位的指令
instruction = (mem[address]<<24) + (mem[address+1]<<16) + (mem[address+2]<<8) + mem[address+3];
end
// output
assign op = instruction[31:26];
assign rs = instruction[25:21];
assign rt = instruction[20:16];
assign rd = instruction[15:11];
assign immediate = instruction[15:0];
assign sa = instruction[10:6];
endmodule
Instructions文件的部分截圖:
每4行構成一個32位的指令,對照指令表便可寫出來。
7.設計Regfile
由數據通路圖可知,
輸入信號爲: clk, RegWre, RegOut, opCode, rs, rt, rd, im, ALUM2Reg,
dataFromALU, dataFromRW
輸出信號爲: Data1,Data2
下面列出我測試的指令列表:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/23 22:18:52
// Design Name:
// Module Name: Regfile
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module Regfile(clk, RegWre, RegDst, opCode, rs, rt, rd, im, DBDataSrc, dataFromALU, dataFromRW, Data1, Data2);
// 根據數據通路圖定義輸入和輸出
input clk, RegDst, RegWre, DBDataSrc;
input [5:0] opCode;
input [4:0] rs, rt, rd;
input [10:0] im;
input [31:0] dataFromALU, dataFromRW;
output [31:0] Data1, Data2;
wire [4:0] writeReg; // 要寫的寄存器端口
wire [31:0] writeData; // 要寫的數據
// RegDst爲真時,處理R型指令,rd爲目標操作數寄存器,爲假時處理I型指令,詳見控制信號作用表
assign writeReg = RegDst? rd : rt;
// ALUM2Reg爲0時,使用來自ALU的輸出,爲1時,使用來自數據存儲器(DM)的輸出,詳見控制信號作用表
assign writeData = DBDataSrc? dataFromRW : dataFromALU; // 實現數據選擇器
// 初始化寄存器
reg [31:0] register[0:31];
integer i;
initial begin
for (i = 0; i < 32; i = i+1) register[i] <= 0;
end
// output:隨register變化而變化
// Data1 爲ALU運算時的A,當指令爲sll時,A的值從立即數的16位中獲得
// Data2 位ALU運算中的B,其值始終是爲rt
assign Data1 = (opCode == 6'b011000) ? im[10:6] : register[rs];
assign Data2 = register[rt];
always @(RegDst or RegWre or DBDataSrc or writeReg or writeData) begin
if (RegWre && writeReg) register[writeReg] = writeData; // 防止數據寫入0號寄存器(writeReg=0)
end
endmodule
8.編寫頂層模塊
定義各個模塊的input與output,然後調用各個模塊。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/24 00:56:01
// Design Name:
// Module Name: SingleCycleCPU
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module SingleCycleCPU(
input clk, Reset, CLR,
output wire[5:0] opCode,
output wire[31:0] Out1, Out2, curPC, Result
);
wire[2:0] ALUOp;
wire[31:0] Extout, DMOut;
wire[15:0] immediate;
wire[4:0] rs, rt, rd;
wire[5:0] sa;
wire zero, PCWre, ALUSrcA, ALUSrcB, DBDataSrc, RegWre,InsMemRW, RD, WR, ExtSel, RegDst, PCSrc;
ALU alu(Out1, Out2, Extout, sa, ALUSrcA, ALUSrcB, ALUOp, zero, Result);
PC pc(clk, Reset, PCWre, PCSrc, immediate, curPC);
controlUnit CU(opCode, zero, PCWre, ALUSrcA, ALUSrcB, DBDataSrc, RegWre, InsMemRW, RD, WR, ExtSel, RegDst, PCSrc, ALUOp);
dataMemory dm(Result, Out2, RD, WR, DMOut);
instructionMemory im(curPC, opCode, rs, rt, rd, immediate, sa);
Regfile registerfile(clk, RegWre, RegDst, opCode, rs, rt, rd, immediate, DBDataSrc, Result, DMOut, Out1, Out2);
signZeroExtend sze(immediate, ExtSel, Extout);
endmodule
9.編寫測試代碼
由於輸入只有clk與reset信號,所以測試文件中只需要輸入這兩個值即可。爲了顯示更多結果,我在測試文件中也添加了相應代碼以方便模擬。
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2017/04/24 01:21:55
// Design Name:
// Module Name: cpu_sim
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
/* input clk, Reset,
output wire[5:0] opCode,
output wire[31:0] Out1, Out2, curPC, Result*/
module SingleCircleCPUTest;
// Inputs
reg CLK;
reg Reset;
// Outputs
wire [31:0] Out1;
wire [31:0] Out2;
wire [31:0] curPC;
wire [31:0] Result;
wire [5:0] opCode;
// Instantiate the Unit Under Test (UUT)
SingleCycleCPU uut (
.clk(CLK),
.Reset(Reset),
.opCode(opCode),
.Out1(Out1),
.Out2(Out2),
.curPC(curPC),
.Result(Result)
);
initial begin
// Initialize Inputs
CLK = 0;
Reset = 0;
#50; // 剛開始設置pc爲0
CLK = 1;
#50;
Reset = 1;
forever #50 begin // 產生時鐘信號
CLK = !CLK;
end
end
endmodule
實驗截圖:
addi操作:
ori操作:
add操作:
sub操作:
and操作:or操作:beq操作:sll操作sw操作lw操作beq操作:halt操作:
以上內容皆爲本人觀點,歡迎大家提出批評和指導,我們一起探討!