前言
本篇文章将简单讲解CPU之间各部分的功能及接线,并提供Verilog模拟CPU的各个组成部分。该CPU可以完成一些操作,如:加减法,与或,指令跳转等,最后提供testbench用于测试该CPU的工作情况是否符合预期。
CPU简单讲解
CPU及相关硬件的作用:
Random Access Memory(RAM):随机存储寄存器,即内存,包含所有CPU要处理的数据RAM包含一系列的地址,每个地址对应的都是一个二进制数据。CPU一般将会按顺序从RAM中读取数据,但实际上是可以以任意顺序访问RAM。当计算机运行时,它向RAM发送一个地址以获得那个程序。通常,RAM不会有任何操作,除非CPU与RAM连线的读使能为高电平。如果读使能为高电平,电路导通,RAM将返回这个地址的数据给CPU。从此CPU开始处理数据,处理完成后,它会向RAM发送一个地址,并将读使能置高,接着获取下一个数据。如果CPU需要向RAM写入数据,它将输出一个值和一个地址,并将写使能置高,将数据写入RAM对应的地址上。RAM的地址上存储的二进制数可能表示一条指令,一个数字,一个字符,一个地址等等。
Instruction Set(IS):指令集,即CPU可以完成指令种类的集合,比如两个数相加,移动某个数去某个新地址等等。
Control Unit(CU):控制单元,队长,它从RAM中接收指令,通过解析这个指令,变成其他元件可以理解的命令,即向其他元件输出控制信号。
Arithmetic Logic Unit(ALU):算术逻辑单元,是受控制单元控制的一个元件,ALU执行所有的数学运算,例如之前提到的加法指令中的加法。ALU有两个输入口,inputA和inputB。ALU的工作过程如下:CU从RAM收到了指令,告诉ALU该做什么运算,ALU执行运算并输出结果。除此之外,ALU将会对CU输出一个状态标志信号,表明当前状态和下一个时钟需要做的事。当CU与寄存器的写使能为高电平的时候,ALU输出的结果将会被暂时性的存储到寄存器上。那么如何将寄存器上的数据读出?CU实际上也会有个与寄存器的读使能信号连线,当它为高电平时,寄存器输出该数据。寄存器的输出线路会连在CPU总线上,总线是一组连在计算机里多个元件之间的线路。总线上会有更多独立的寄存器(Group Registers),它们同样通过读使能和写使能与CU相连。它们用于存储刚刚那个寄存器放在总线上的数据。这些组寄存器用于在多个操作间存储数字。
Temporary Register:临时寄存器,由于总线上同一时间一般只能有一个数据,而ALU有两个输入端口,这就意味着我们需要先存好一个数据来自于组寄存器中的数据),再与总线上的数据(来自于组寄存器中的数据)进行运算。存放数据提供给ALU的寄存器就叫临时寄存器。需要注意的是,临时寄存器不需要和CU有读写使能的连线,因为它并不会与总线上的其他寄存器产生冲突。
Instruction Register(IR):指令寄存器,用于存放CU从RAM中读到的指令,它也不需要和CU有读写使能的连线,因为它并不会与总线上的其他寄存器产生冲突。它只会将指令输出给CU,基于这个指令,CU会告诉ALU执行什么运算。
Instruction Address Register:指令地址寄存器,在我的Verilog程序里也叫Program Counter(PC),CPU需要它去了解下一个指令在RAM中的何处,在没有“跳转”指令发生的前提下,下一条指令所在的地址通常是当前指令的所在地址+1,但如果发生了跳转,则新地址应为上一条指令在RAM中的地址+offset,offset随不同的跳转指令而各不相同(是一个变化的值)。因为CU并不需要读取PC中的数据,仅需要写入一条指令所在的地址,因此它们之间只需要有写使能连线,而不需要读使能连线。RAM收到PC传递给它的地址后,将会将这个地址上的数据传给IR,再由IR传给CU。
相关图示
Verilog代码
alu_mux.v
module alu_mux (clk, rst, en_in,offset, rd_q,rs_q,alu_in_sel,alu_a,alu_b,en_out
);input [15:0] rd_q, rs_q ;
input clk, rst, en_in, alu_in_sel ;
input [7:0] offset ;
output reg [15:0] alu_a, alu_b ;
output reg en_out ;always @ (negedge rst or posedge clk) beginif (rst == 1'b0) beginalu_a <= 16'b0000_0000_0000_0000 ;alu_b <= 16'b0000_0000_0000_0000 ;en_out <= 1'b0;end else if (en_in == 1'b1) beginalu_a <= rd_q;en_out <= 1'b1;if (alu_in_sel == 1'b0) alu_b <= {{8{offset[7]}}, offset[7:0]} ; else alu_b <= rs_q; endelse en_out <= 1'b0;end
endmodule/* alu_mux: 用于给alu输入数据,若alu_in_sel==0,则说明只需要一个操作数若alu_in_sel==1,则需要两个操作数当en_in是能有效时,输出使能有效
*/
alu.v
`timescale 1ns / 1ps`define B15to0H 3'b000
`define AandBH 3'b011
`define AorBH 3'b100
`define AaddBH 3'b001
`define AsubBH 3'b010
`define leftshift 3'b101
`define rightshift 3'b110module alu (clk, rst, en_in, alu_a, alu_b, alu_func, en_out, alu_out
);input [15:0] alu_a, alu_b ;
input clk, rst, en_in ;
input [2:0] alu_func ;
output reg [15:0] alu_out ;
output reg en_out ;always @ (negedge rst or posedge clk) beginif (rst == 1'b0) beginalu_out <= 16'b0000_0000_0000_0000 ;en_out <= 1'b0 ;end else beginif (en_in == 1'b1) beginen_out <= 1'b1;case (alu_func)B15to0H: alu_out <= alu_b ; AandBH: alu_out <= a & b ; AorBH: alu_out <= a | b ; AaddBH: alu_out <= a + b ; AsubBH: alu_out <= a - b ; leftshift: alu_out <= (alu_out << 1) ; rightshift: alu_out <= (alu_out >> 1) ;default: alu_out <= alu_out ; endcaseendelse en_out <= 1'b0;endend
endmodule/*根据输入alu_func的值,确定如何将两个操作数alu_a和alu_b运算得到alu_out
*/
control_unit.v
module control_unit (clk,rst,en,en_alu,en_ram_out,ins,offset_addr,en_ram_in, en_group_pulse,en_pc_pulse,reg_en,alu_in_sel,alu_func,pc_ctrl
);/*en: Control_Unit的使能信号en_alu: alu的输出使能信号,即用于告诉control unit此时alu有输出en_ram_out: RAM的输出使能信号,即用于告诉control unit此时RAM有输出ins: 当前的指令
*/
input clk, rst, en, en_alu, en_ram_out ;
input [15:0] ins ;/*en_ram_in: RAM输入使能,告诉RAM此时有数据输入en_group_pulse: 与datapath同步时钟信号?en_pc_pulse: 连datapath的en_pc_pulsealu_in_sel: 连datapath的alu_in_seloffset_addr: 连datapath的offset_addrreg_en: 连datapath的reg_enalu_func: 连datapath的alu_funcpc_ctrl: 连datapath的pc_ctrl
*/
output en_ram_in, en_group_pulse, en_pc_pulse, alu_in_sel ;
output reg [7:0] offset_addr ;
output [3:0] reg_en ;
output [2:0] alu_func ;
output [1:0] pc_ctrl ;wire [15:0] ir_out ;
wire en_out ;ir ir1(.clk(clk),.rst(rst),.ins(ins),.en_in(en_ram_out),.en_out(en_out),.ir_out(ir_out));state_transition state_transition1(.clk(clk),.rst(rst),.en_in(en),.en1(en_out),.en2(en_alu),.rd(ir_out[11:10]),.opcode(ir_out[15:12]),.en_fetch_pulse(en_ram_in), .en_group_pulse(en_group_pulse),.en_pc_pulse(en_pc_pulse),.pc_ctrl(pc_ctrl),.reg_en(reg_en),.alu_in_sel(alu_in_sel),.alu_func(alu_func) );always @ (en_out or ir_out) beginoffset_addr = ir_out[7:0] ;endendmodule
cpu.v
module cpu(clk,rst,en_in,en_ram_out,addr,ins,en_ram_in
);input clk, rst ,en_in, en_ram_out ;
input [15:0] ins ;
output [15:0] addr ;
output en_ram_in ;wire en_pc_pulse, en_group_pulse, alu_in_sel, en_alu ;
wire [1:0] pc_ctrl ;
wire [3:0] reg_en ;
wire [2:0] alu_func ;
wire [7:0] offset_addr ;data_path data_path1(.clk(clk),.rst(rst),.offset(ins[7:0]),.offset_addr(offset_addr),.en_pc_pulse(en_pc_pulse),.pc_ctrl(pc_ctrl),.en_in(en_group_pulse),.reg_en(reg_en),.rd(ins[11:10]),.rs(ins[9:8]),.alu_in_sel(alu_in_sel),.alu_func(alu_func),.en_out(en_alu),.pc_out(addr)
); control_unit control_unit1(.clk(clk),.rst(rst),.en(en_in),.en_alu(en_alu), .en_ram_out(en_ram_out),.ins(ins),.offset_addr(offset_addr),.en_ram_in(en_ram_in),.en_group_pulse(en_group_pulse),.en_pc_pulse(en_pc_pulse),.reg_en(reg_en),.alu_in_sel(alu_in_sel),.alu_func (alu_func),.pc_ctrl(pc_ctrl)
);
endmodule
data_path.v
module data_path (clk, rst,offset_addr, en_pc_pulse,pc_ctrl, offset, en_in,reg_en, alu_in_sel,alu_func, en_out, pc_out,rd,rs
);input clk, rst, en_pc_pulse, en_in, alu_in_sel ;
input [7:0] offset_addr, offset ;
input [1:0] pc_ctrl, rd, rs ;
input [3:0] reg_en ;
input [2:0] alu_func ;
output en_out ;
output [15:0] pc_out ;wire [15:0] rd_q, rs_q, alu_a, alu_b, alu_out ;
wire en_out_group, en_out_alu_mux ; pc pc1(clk(clk),rst(rst), en_in(en_pc_pulse),pc_ctrl(pc_ctrl),offset_addr(offset_addr), pc_out(pc_out)
);reg_group reg_group1(.clk(clk),.rst(rst),.en_in(en_in),.reg_en(reg_en),.d_in(alu_out),.rd(rd),.rs(rs),.rd_q(rd_q),.en_out(en_out_group),.rs_q(rs_q)
);alu_mux alu_mux1( .clk(clk),.rst(rst),.en_in(en_out_group),.rd_q(rd_q),.rs_q(rs_q),.offset(offset),.alu_in_sel(alu_in_sel),.alu_a(alu_a),.en_out(en_out_alu_mux), .alu_b(alu_b)
);alu alu1(.clk(clk),.rst(rst),.en_in(en_out_alu_mux), .alu_a(alu_a),.alu_b(alu_b),.alu_func(alu_func),.en_out(en_out),.alu_out(alu_out )
);
endmodule /*datapath包含alu,alu_mux等模块
*/
ir.v
module ir(clk,rst,ins,en_in,en_out,ir_out
);input clk, rst ;
input [15:0] ins ;
input en_in ;
output reg en_out ;
output reg [15:0] ir_out ;always @ (posedge clk or negedge rst) beginif (!rst) beginir_out <= 16'b000000000000 ;en_out <= 1'b1 ;endelse beginif (en_in) beginen_out <= 1'b1 ;ir_out <= ins ;endelse en_out <= 1'b0 ;end
end
endmodule/* IR: Instruction Register, 用于存放当前即将执行的指令在使能有效时,将指令输出给state_transition完成状态转移
*/
pc.v
`timescale 1ns / 1psmodule pc(clk,rst, en_in,pc_ctrl, // 控制指令寄存器的下一曲值offset_addr, pc_out
);input clk, rst, en_in ;
input wire [1:0] pc_ctrl ;
input wire [7:0] offset_addr ;
output reg [15:0] pc_out ;always @ (posedge clk or negedge rst) beginif (rst == 0) pc_out <= 0 ;else beginif (en_in == 1) begincase (pc_ctrl) 2'b00: pc_out <= pc_out ; 2'b01: pc_out <= pc_out + 1 ;2'b10: pc_out <= {8'b00000000, offset_addr[7:0]} ; // jump去指定的地址2'b11: pc_out <= pc_out + offset_addr ; default: pc_out <= pc_out ;endcaseendendend
endmodule/*pc: program counter
*/
reg_group.v
`timescale 1ns / 1psmodule reg_group(clk, rst, en_in, reg_en, d_in, rd,rs, en_out, rd_q,rs_q
);
input clk, rst, en_in ; // 时钟信号,复位信号,操作四个寄存器的使能信号
input wire [3:0] reg_en ; // 用于实例化寄存器的使能信号
input wire [15:0] d_in ; // 输入数据
input wire [1:0] rd, rs ; // 用于选择如何分配寄存器的值给两个输出信号,两个输出信号均有4种取值(共有四个寄存器,每个信号读取其中一个寄存器中的值)共16种
output reg en_out ; // 输出使能
output reg [15:0] rd_q, rs_q ; // 输出信号 目标寄存器和源寄存器wire [15:0] q0, q1, q2, q3 ; // 每个寄存器的输出register reg0(.clk(clk),.rst(rst),.en(reg_en[0]), .d(d_in), .q(q0) );register reg1(.clk(clk),.rst(rst),.en(reg_en[1]), .d(d_in), .q(q1) );register reg2(.clk(clk),.rst(rst),.en(reg_en[2]), .d (d_in), .q (q2) );register reg3(.clk(clk),.rst(rst),.en(reg_en[3]), .d(d_in), .q(q3) ); always @ (posedge clk or negedge rst)if (rst == 0) beginrd_q <= 0 ; rs_q <= 0 ; en_out <= 0; endelse beginif (en_in == 1) beginen_out <= 1 ; case ({rd[1:0], rs[1:0]})4'b0000: beginrd_q <= q0 ;rs_q <= q0 ;end4'b0001: beginrd_q <= q0 ;rs_q <= q1 ;end 4'b0010: begin rd_q <= q0 ; rs_q <= q2 ; end4'b0011: begin rd_q <= q0; rs_q <= q3;end 4'b0100: begin rd_q <= q1;rs_q <= q0;end 4'b0101: begin rd_q <= q1;rs_q <= q1;end 4'b0110: begin rd_q <= q1;rs_q <= q2;end 4'b0111: begin rd_q <= q1;rs_q <= q3;end 4'b1000: begin rd_q <= q2 ;rs_q <= q0 ;end 4'b1001: begin rd_q <= q2 ; rs_q <= q1 ; end 4'b1010: begin rd_q <= q2 ;rs_q <= q2 ;end 4'b1011: begin rd_q <= q2 ;rs_q <= q3 ;end 4'b1100: begin rd_q <= q3 ;rs_q <= q0 ;end 4'b1101: begin rd_q <= q3 ; rs_q <= q1 ; end 4'b1110: begin rd_q <= q3 ;rs_q <= q2 ;end 4'b1111: begin rd_q <= q3 ;rs_q <= q3 ;end default: beginrd_q <= 0 ;rs_q <= 0 ;endendcaseendelse en_out <= 0 ;end
endmodule /* 在每个时钟上升沿,确定如何将哪些寄存器中的值赋给rd_q和rs_q,rd_q和rs_q将用于给alu_a和alu_b赋值
*/
register.v
`timescale 1ns / 1psmodule register(clk,rst,en, d, q
);input clk, rst, en ;
input wire [15:0] d ;
output reg [15:0] q ;always @ (posedge clk or negedge rst) beginif (rst == 0) q <= 0 ;else if (en == 1) q <= d ; else q <= q ; end
endmodule/*寄存器模块,在时钟上升沿且Control Unit给出的控制信号en为高时,更新寄存器,否则锁存数据
*/
state_transistion.v
module state_transition(clk,rst,en_in,en1,en2,rd,opcode,en_fetch_pulse,en_group_pulse,en_pc_pulse,pc_ctrl,reg_en,alu_in_sel,alu_func
);input clk, rst ;
input en_in ; // 表示此时有指令需要处理,从空闲状态转为取指状态
input en1 ; // 接收指令寄存器的使能,只有有指令来临时,才会进行状态转移
input en2 ; // 接收alu的输出
input [1:0] rd ; // destination register 目的寄存器
input [3:0] opcode ; // 指令中的操作码,不同的操作码对应不同的next_state/*en_ram_in: RAM输入使能,告诉RAM此时有数据输入en_group_pulse: 与datapath同步时钟信号?en_pc_pulse: 连datapath的en_pc_pulsealu_in_sel: 连datapath的alu_in_selreg_en: 连datapath的reg_enalu_func: 连datapath的alu_funcpc_ctrl: 连datapath的pc_ctrl
*/
output reg en_fetch_pulse ;
output reg en_group_pulse ;
output reg en_pc_pulse ;
output reg [1:0] pc_ctrl;
output reg [3:0] reg_en ;
output reg alu_in_sel ;
output reg [2:0] alu_func ;reg en_fetch_reg, en_fetch ;
reg en_group_reg, en_group ; // group reg的写控制信号和读控制信号
reg en_pc_reg, en_pc ;
reg [3:0] current_state, next_state ; parameter Initial = 4'b0000 ;
parameter Fetch = 4'b0001 ;
parameter Decode = 4'b0010 ;
parameter Execute_Moveb = 4'b0011 ;
parameter Execute_Add = 4'b0100 ;
parameter Execute_Sub = 4'b0101 ;
parameter Execute_And = 4'b0110 ;
parameter Execute_Or = 4'b0111 ;
parameter Execute_Jump = 4'b1000 ;
parameter Write_back = 4'b1001 ;always @ (posedge clk or negedge rst) begin // 有限状态机的现态与次态的转移if (!rst)current_state <= Initial ;else current_state <= next_state ;
endalways @ (current_state or en_in or en1 or en2 or opcode) begincase (current_state)Initial: beginif (en_in)next_state = Fetch ;elsenext_state = Initial ;endFetch: beginif (en1) next_state = Decode ;elsenext_state = current_state ;endDecode: begincase (opcode) 4'b0000: next_state = Execute_Moveb ;4'b0010: next_state = Execute_Add ;4'b0101: next_state = Execute_Sub ;4'b0111: next_state = Execute_And ;4'b1001: next_state = Execute_Or ;4'b1010: next_state = Execute_Jump ;default: next_state = current_state ;endcaseendExecute_Moveb: beginif (en2) // 如果此时alu确实有数输出,证明这个状态完成了next_state = Write_back ;elsenext_state = current_state ;endExecute_Add: beginif(en2) // 如果此时alu确实有数输出,证明这个状态完成了next_state = Write_back ;elsenext_state = current_state ;endExecute_Sub: beginif(en2) // 如果此时alu确实有数输出,证明这个状态完成了next_state = Write_back ;elsenext_state = current_state ;endExecute_And: beginif(en2) // 如果此时alu确实有数输出,证明这个状态完成了next_state = Write_back ;elsenext_state = current_state ;end Execute_Or: beginif(en2) // 如果此时alu确实有数输出,证明这个状态完成了next_state = Write_back ;elsenext_state = current_state ;end Execute_Jump: next_state = Fetch ;Write_back: next_state = Fetch ;default: next_state = current_state ;endcase
end// 用于输出控制信号
always @ (rst or next_state) beginif (!rst) beginen_fetch = 1'b0 ;en_group = 1'b0 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endelse begincase (next_state)Initial: beginen_fetch = 1'b0 ;en_group = 1'b0 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endFetch: beginen_fetch = 1'b1 ; // 此时需要取指en_group = 1'b0 ;en_pc = 1'b1 ;pc_ctrl = 2'b01 ; // 取下一个指令reg_en = 4'b0000 ; alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endDecode: beginen_fetch = 1'b0 ;en_group = 1'b0 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endExecute_Moveb: beginen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endExecute_Add: beginen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b001 ;endExecute_Sub: begin// please add your code hereen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b001 ;endExecute_And: begin// please add your code hereen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b001 ;endExecute_Or: begin// please add your code hereen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b001 ;endExecute_Jump: begin// please add your code hereen_fetch = 1'b0 ;en_group = 1'b1 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b001 ;endWrite_back: begincase (rd)2'b00: reg_en = 4'b0001 ;2'b01: reg_en = 4'b0010 ;2'b10: reg_en = 4'b0100 ;2'b11: reg_en = 4'b1000 ;default: reg_en = 4'b0000 ;endcaseenddefault: beginen_fetch = 1'b0 ;en_group = 1'b0 ;en_pc = 1'b0 ;pc_ctrl = 2'b00 ;reg_en = 4'b0000 ;alu_in_sel = 1'b0 ;alu_func = 3'b000 ;endendcaseend
endalways @ (posedge clk or negedge rst) beginif (!rst) beginen_fetch_reg <= 1'b0 ;en_pc_reg <= 1'b0 ;en_group_reg <= 1'b0 ;endelse beginen_fetch_reg <= en_fetch ; en_pc_reg <= en_pc ;en_group_reg <= en_group ;end
endalways @ (en_fetch or en_fetch_reg)en_fetch_pulse = en_fetch & (~en_fetch_reg) ;always @ (en_pc_reg or en_pc)en_pc_pulse = en_pc & (~en_pc_reg) ; // 此时需要读入下一个指令,且当此时指令寄存器中为空时,请求下一条指令always @ (en_group_reg or en_group)en_group_pulse = en_group & (~en_group_reg) ;endmodule
Testbench
`timescale 1ns / 1psu();
reg clk,rst,en_in,en_ram_out;
reg [15:0] ins;
wire en_ram_in;
wire [15:0] addr;cpu test_cpu(.clk (clk),.rst (rst),.en_in (en_in),.en_ram_in (en_ram_in), .ins (ins), .en_ram_out (en_ram_out),.addr (addr)
);parameter Tclk = 10;initial begin//define clkendinitial begin//define rst endinitial begin //define en_in and en_ram_out
endinitial begin//define ins ,you can assign 0000_0000_0000_0001//0000_0100_0000_0010 and so on to ins.endinitial begin#(Tclk*400) $stop;
endendmodule
如有疑问,欢迎打扰。