RISC_CPU

采用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个信号。

RISC_CPU

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的时钟,这样能保证数据流要用到控制信号时都是稳定的控制信号。

    RISC_CPU

         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位。

RISC_CPU

  • load_ir,控制指令寄存器什么时候取指令。

  • data,总线上的数据。

  • instr {opcode,ir_addr},16位的指令。高3位为操作码,低13位为地址。

    RISC_CPU

         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信号来控制累加器使能。

RISC_CPU

  • load_acc,控制累加器何时加载算数运算器的结果。

  • alu_out,算数运算器的输出。

  • accum,累加器的输出。

    RISC_CPU

         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种不同的操作码,可以分别实现多种运算以及逻辑判断等。

RISC_CPU

  • accum,累加器的输出。

  • con_alu,控制alu_cpu模块的执行。

  • opcode,instr的前3位,操作码。

  • data,总线上的数据。

  • alu_out,算术运算器的输出。

  • zero,accum求反的结果。

    RISC_CPU

          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的输出数据。

RISC_CPU

  • alu_out,算数运算器的输出。
  • datactr_ena,controller的输出。
  • data,输出到总线上的数据。

RISC_CPU

    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来控制地址的选择。

RISC_CPU

  • fetch,指令周期。
  • ir_addr,instr的后13位ir_addr。
  • pc_addr,程序计数器。
  • addr,地址。

RISC_CPU

     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。

RISC_CPU

  • load_pc,控制程序计数器何时加载ir_addr。

  • ir_addr, instr的后13位ir_addr。

  • inc_pc,控制程序计数器何时加一。

  • pc_addr,程序地址。

    RISC_CPU

      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的使能端。

RISC_CPU

  • fetch,指令周期。
  • ena_controller,控制器使能端。

RISC_CPU

    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个控制信号进行赋值。

RISC_CPU

  • 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、如果不是上述操作符,所有信号置于低位。

    RISC_CPU

    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。

RISC_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进行调试。

上一篇:通过Jexus 部署 dotnetcore


下一篇:预备作业02:体会做中学(Learning By Doing)