采用Top-Down設計方法,深入理解CPU的運作原理,本文參照夏宇聞老師的《Verilog 數字系統設計教程》,並做了相應的修改。仿真工具采用Mentor公司的ModelSim。
1、CPU概述
CPU(Central Processing Unit),即中央處理單元。它必須能夠與讀取外部的指令和數據,並且能夠分析指令進而做出執行。
- (1)程序和數據輸入到計算機的存儲器中。
- (2)對指令做出處理。
- a.讀取指令
- b.分析指令
- c.執行指令
RISC(Reduced Instructions Set Computer),即精簡指令集計算機。RISC_CPU與一般CPU的不同之處在於,它的時序控制信號形成部件是用硬布線邏輯實現的而不是采用微程序控制的方式。所謂的硬布線邏輯也就是用觸發器和邏輯門直接連接所構成的狀態機和組合邏輯,故產生控制序列的速度比用微程序控制方式快的多。因為這樣做省去了讀取微指令的時間。
2、RISC_CPU結構
RSIC_CPU是一個復雜的數字邏輯電路,但是它的基本部件並不復雜。
基本部件包括下面幾個部分:
- clk_gen,時鍾發生器
- IR,指令寄存器
- accumulator,累加器
- alu_cpu,算術運算器
- data_ctl,數據控制器
- addr,地址多路器
- pc_counter,程序計數器
- controller_ena,控制器使能
- controller,控制器
以上各個部件在控制器的控制下有條不紊地執行指令。下面我將一一介紹一下各個部件。
2.1 時鍾發生器
時鍾發生器主要作用是用clk信號產生clk1、fetch、con_alu這3個信號。
fetch,是clk的8分頻信號
- a.執行完一條指令需要8個clk時鍾周期。
- b.當fetch為高電平時,clk能觸發controller的使能端,使CPU開始工作。
- c.fetch可以控制addr輸出的是pc_addr還是ir_addr。
con_alu,是clk的8分頻信號,占空比是1:7
- 控制alu_cpu只在特定的時候才能運行。
clk1,是clk的反向信號
-
用來控制controller的時鍾,這樣能保證數據流要用到控制信號時都是穩定的控制信號。
module clk_gen( clk, rst, clk1, fetch, con_alu ); input clk,rst; output fetch,con_alu,clk1; reg fetch,con_alu; wire clk1; reg [2:0]count; assign clk1=~clk; always@(posedge clk or negedge rst) begin if(!rst) count<=0; else if(count==7) count<=0; else count<=count+1; end always@(posedge clk or negedge rst) begin if (!rst) begin fetch<=0; con_alu<=0; end else case(count) 0:begin fetch<=1; con_alu<=0; end 1:begin fetch<=1; con_alu<=0; end 2:begin fetch<=1; con_alu<=0; end 3:begin fetch<=1; con_alu<=0; end 4:begin fetch<=0; con_alu<=0; end 5:begin fetch<=0; con_alu<=1; end 6:begin fetch<=0; con_alu<=0; end 7:begin fetch<=0; con_alu<=0; end default:begin fetch<=0; con_alu<=0; end endcase end endmodule
2.2 指令寄存器
在時鍾的控制下將總線上指令送入寄存器,但是到底什么時候總線上傳送指令,什么時候寄存這些都必須由controller發出的load_ir信號來控制。一條指令又16位組成,那么必須要取兩次才能取到一條指令。我們用state來控制是取高8位還是低8位信號,state為0取高8位指令,為1取低8位。
-
load_ir,控制指令寄存器什么時候取指令。
-
data,總線上的數據。
-
instr {opcode,ir_addr},16位的指令。高3位為操作碼,低13位為地址。
module IR( clk, rst, ena, data, instr ); input clk,rst,ena; input [7:0] data; output [15:0] instr; reg [15:0] instr; reg state; always@(posedge clk or negedge rst) begin if(!rst) begin instr<=16'b0000000000000000; state<=1'b0; end else begin if(ena) begin casex(state) 1'b0:begin instr[15:8]<=data; state<=1'b1; end 1'b1:begin instr[7:0]<=data; state<=1'b0; end default:begin instr<=16'bxxxxxxxxxxxx; state<=1'bx; end endcase end end end endmodule
2.3 累加器
累加器用於存放當前的結果,它是雙目運算中的一個數據來源,通過controller發出的load_acc信號來控制累加器使能。
-
load_acc,控制累加器何時加載算數運算器的結果。
-
alu_out,算數運算器的輸出。
-
accum,累加器的輸出。
module accumulator( clk, rst, ena, data, accum ); input clk,rst,ena; input [7:0] data; output [7:0] accum; reg [7:0] accum; always@(posedge clk or negedge rst) begin if(!rst) accum<=8'b00000000; else if(ena) accum<=data; end endmodule
2.4 算數運算器
算數運算器它根據8種不同的操作碼,可以分別實現多種運算以及邏輯判斷等。
-
accum,累加器的輸出。
-
con_alu,控制alu_cpu模塊的執行。
-
opcode,instr的前3位,操作碼。
-
data,總線上的數據。
-
alu_out,算術運算器的輸出。
-
zero,accum求反的結果。
module alu_cpu( clk, rst, con_alu, data, accum, opcode, zero, alu_out ); input clk,rst,con_alu; input [7:0] data,accum; input [2:0] opcode; output [7:0] alu_out; output zero; reg [7:0] alu_out; parameter HLT=3'b000, SKZ=3'b001, ADD=3'b010, AND=3'b011, XOR=3'b100, LDA=3'b101, STO=3'b110, JMP=3'b111; always@(posedge clk or negedge rst) begin if(!rst) alu_out<=8'b00000000; else begin if(con_alu) begin casex(opcode) HLT:alu_out<=accum; SKZ:alu_out<=accum; ADD:alu_out<=accum+data; AND:alu_out<=data&accum; XOR:alu_out<=data^accum; LDA:alu_out<=data; STO:alu_out<=accum; JMP:alu_out<=accum; default:alu_out<=8'bxxxxxxxx; endcase end end end assign zero=!accum; endmodule
2.5 數據控制器
數據控制器的作用是控制算數運算器的結果何時輸出到總線上。總線上不同時候傳送的東西也不相同,有時候傳送rom指令,有時候傳送ram數據,有時候傳送算數運算器alu_out的輸出數據。
- alu_out,算數運算器的輸出。
- datactr_ena,controller的輸出。
- data,輸出到總線上的數據。
module data_ctl(
in,
data_ena,
data
);
input [7:0] in;
input data_ena;
output [7:0] data;
assign data=data_ena?in:8'bzzzzzzzz;
endmodule
2.6 地址多路器
地址多路器用於選擇輸出的是pc_addr(rom)還是ir_addr(ram)。由於每個指令周期的前4個時鍾周期都是讀取指令應該選擇pc_addr,后4個時鍾周期用於處理指令應該選擇ir_addr。用fetch來控制地址的選擇。
- fetch,指令周期。
- ir_addr,instr的后13位ir_addr。
- pc_addr,程序計數器。
- addr,地址。
module addr(
addr,
fetch,
pc_addr,
ir_addr
);
input [12:0] pc_addr,ir_addr;
input fetch;
output [12:0]addr;
assign addr=fetch?pc_addr:ir_addr;
endmodule
2.7程序計數器
指令是順序存放在rom中的,程序計數器用於提供指令地址,以便讀取指令。指令地址形成的方式有兩種:一種是順序執行pc_addr依次加一,另一種是加載ir_addr到pc_addr。
-
load_pc,控制程序計數器何時加載ir_addr。
-
ir_addr, instr的后13位ir_addr。
-
inc_pc,控制程序計數器何時加一。
-
pc_addr,程序地址。
module pc_counter( clk, rst, inc_pc, load, ir_addr, pc_addr ); input clk,rst,load,inc_pc; input [12:0] ir_addr; output [12:0] pc_addr; reg [12:0] pc_addr; always@(posedge clk or negedge rst) begin if(!rst) pc_addr<=13'b0000000000000; else if(load) pc_addr<=ir_addr; else if(inc_pc) pc_addr<=pc_addr+1; end endmodule
2.8 控制器使能
rst信號和fetch信號配合,控制controller的使能端。
- fetch,指令周期。
- ena_controller,控制器使能端。
module controller_ena(
clk,
rst,
fetch,
ena_controller
);
input clk,rst,fetch;
output ena_controller;
reg ena_controller;
always@(posedge clk or negedge rst)
begin
if(!rst)
ena_controller<=0;
else
if(fetch)
ena_controller<=1;
end
endmodule
2.9 控制器
控制器是整個CPU的核心部分,用於產生一系列的控制信號,啟動或者停止某些部件。CPU何時進行讀取RAM/ROM的數據以及對RAM進行寫操作都是通過狀態機來控制的。執行一條指令需要8個時鍾周期,由state從0~7計數。每一個時鍾周期都完成固定的操作。簡單講,就是對
inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena這8個控制信號進行賦值。
-
000:指令寄存器存放ROM送來的高8位指令代碼。
-1、 inc_pc,rd,load_ir置於高位,其余置於低位。
-2、程序計數器加一,將rom中的數據讀出到總線上,指令寄存器將寄存來自總線上的高8位指令。 -
001:指令寄存器存放ROM送來的低8位指令。
-1、inc_pc,rd,load_ir置於高位,其余置於低位。
-2、程序計數器加一(指向下一條指令的地址),將rom中的數據讀出到總線上,指令寄存器將寄存來自總線上的低8位指令。 -
010:空操作。
-1、所有信號置於低位。
-2、用作數據的緩沖。 -
011:,根據操作碼做不同的操作。
-1、如果操作碼為HLT,HALT置於高位,其余置於低位。
-2、如果操作碼不是HLT,所有信號置於低位。 -
100:,根據操作碼做不同的操作。
-1、如果操作符為AND、ADD、XOR、LDA,讀相應地址的數據,rd置於高位,其余置於低位。
-2、如果操作符為JMP,將目的地址送給程序計數器,load_pc置於高位,其余置於低位。
-3、如果操作符為STO,將累加器上的數據放入指令給出的地址,datactr_ena置於高位,其余置於低位。
-4、如果操作符不是上述情況,所有信號置於低位。 -
101:根據操作碼做不同的操作。
-1、如果操作符為AND、ADD、XOR、LDA,算術運算器要做出相應的計算,rd、load_acc置於高位,其余置於低位。
-2、如果操作符為SKZ,先判斷累加器的值是否為0,如果是0,則inc_pc置於高位,其余置於低位;否則所有信號置於低位。
-3、如果操作符為JMP,鎖存目標地址,load_pc置於高位,其余置於低位。
-4、如果操作符為STO,將累加器上的數據寫入指定地址,wr,datactr_ena置於高位,其余各位置於低位。
-5、如果操作符不是上述情況,所有信號置於低位。 -
110:空操作。不對指令做出相應,控制總線上的輸出數據。
-1、如果操作符為STO,datactr_ena置於高位,其余置於低位。
-2、如果操作符為AND、ADD、XOR、LDA,rd置於高位,其余置於低位。
-3、如果操作符不是上述情況,所有信號置於低位。 -
111:根據操作碼不同做不同的操作。
-1、如果操作符為SKZ且累加器的輸出為0,inc_pc置於高位,其余置於低位。
-2、如果不是上述操作符,所有信號置於低位。
module controller( clk, rst, ena, zero, opcode, load_acc, load_pc, rd, wr, load_ir, HALT, datactr_ena, inc_pc ); input clk,rst,ena,zero; input [2:0]opcode; output inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena; reg inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena; reg [2:0]state; parameter HLT=3'b000, SKZ=3'b001, ADD=3'b010, AND=3'b011, XOR=3'b100, LDA=3'b101, STO=3'b110, JMP=3'b111; always@(posedge clk or negedge rst) begin if(!rst) begin state<=3'b000; {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena} <=8'b00000000; end else if(ena) controller_cycle; end task controller_cycle; begin case(state) 3'b000:begin {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b10010100; state<=3'b001; end 3'b001:begin {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b10010100; state<=3'b010; end 3'b010:begin {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b011; end 3'b011:begin if(opcode==HLT) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000010; else {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b100; end 3'b100:begin if(opcode==JMP) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00100000; else if((opcode==ADD)||(opcode==AND)||(opcode==XOR)||(opcode==LDA)) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00010000; else if(opcode==STO) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b0000001; else {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b101; end 3'b101:begin if((opcode==ADD)||(opcode==AND)||(opcode==XOR)||(opcode==LDA)) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b01010000; else if((opcode==SKZ)&&(zero==1)) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b10000000; else if(opcode==JMP) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00100000; else if(opcode==STO) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00001001; else {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b110; end 3'b110:begin if(opcode==STO) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b0000001; else if((opcode==ADD)||(opcode==AND)||(opcode==XOR)||(opcode==LDA)) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00010000; else {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b111; end 3'b111:begin if((opcode==SKZ)&&(zero==1)) {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b10000000; else {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b000; end default:begin {inc_pc,load_acc,load_pc,rd,wr,load_ir,HALT,datactr_ena}<=8'b00000000; state<=3'b000; end endcase end endtask endmodule
3 集成各個組件
將CPU的各個組件按照對應的信號線連接起來,就形成了一個CPU。
module cpu( clk,
rst,
data,
rd,
wr,
addr,
HALT,
fetch
);
input clk,rst;
output rd,wr,HALT,fetch;
output[12:0]addr;
inout[7:0]data;
wire con_alu,clk1;
//wire[15:0] instr;
wire[2:0] opcode;
wire[12:0] ir_addr;
wire[7:0] accum,alu_out;
wire zero;
wire ena_controller;
wire[12:0] pc_addr;
wire load_acc,load_pc,load_ir,datactr_ena,inc_pc;
clk_gen clk_gen_m (.clk(clk),
.rst(rst),
.clk1(clk1),
.fetch(fetch),
.con_alu(con_alu));
IR IR_m (.clk(clk),
.rst(rst),
.ena(load_ir),
.data(data),
.instr({opcode,ir_addr}));
accumulator accumulator_m (.clk(clk),
.rst(rst),
.ena(load_acc),
.data(alu_out),
.accum(accum));
alu_cpu alu_cpu_m (.clk(clk),
.rst(rst),
.con_alu(con_alu),
.data(data),
.accum(accum),
.opcode(opcode),
.zero(zero),
.alu_out(alu_out));
data_ctl data_ctl_m (.in(alu_out),
.data_ena(datactr_ena),
.data(data));
pc_counter pc_counter_m (.clk(clk),
.rst(rst),
.inc_pc(inc_pc),
.load(load_pc),
.ir_addr(ir_addr),
.pc_addr(pc_addr));
addr addr_m ( .addr(addr),
.fetch(fetch),
.pc_addr(pc_addr),
.ir_addr(ir_addr));
controller_ena controller_ena_m (.clk(clk),
.rst(rst),
.fetch(fetch),
.ena_controller(ena_controller));
controller controller_m (.clk(clk1),
.rst(rst),
.ena(ena_controller),
.zero(zero),
.opcode(opcode),
.load_acc(load_acc),
.load_pc(load_pc),
.rd(rd),
.wr(wr),
.load_ir(load_ir),
.HALT(HALT),
.datactr_ena(datactr_ena),
.inc_pc(inc_pc));
endmodule
小結:
其實我們通過分析可以得知,一個相對復雜的數字電路設計,都可以划分成很簡單的電路來實現,重要的是如何控制這些簡單電路相互協調工作。上面我們介紹了CPU的構成,下一篇文章我們就討論一下如何對這個CPU進行調試。