侧边栏壁纸
    • 累计撰写 295 篇文章
    • 累计收到 517 条评论
    【IC/CPU设计】极简的RISC_CPU设计
    我的学记|刘航宇的博客

    【IC/CPU设计】极简的RISC_CPU设计

    刘航宇
    2023-04-08 / 5 评论 / 554 阅读 / 正在检测是否收录...

    CPU为SOC系统的核心环节,该项目来自于夏宇闻老师的经典教材——《Verilog 数字系统设计教程》,通过此练习方便数字ICer更好的入门
    本次项目实践环境:
    前仿: Modelsim
    综合: Design Compile

    CPU简介

    CPU(Central Processing Unit),中文全称中央处理器,作为四大U之首(CPU/GPU/TPU/NPU),是计算机系统的运算和控制核心,也是当今数字系统中不可或缺的组成部分。CPU自诞生到如今发展超过50年,借助冯诺依曼体系,CPU掀起一股又一股的科技浪潮。RISC作为精简了指令集的CPU,除了指令更加简洁,还拥有简单合理的内部结构,从而提高了运算速度。

    CPU工作的5个阶段:
    (1)取指(IF,Instruction Fetch),将指令从存储器取出到指令寄存器。每取一条指令,程序计数器自加一。
    (2)译指(ID,Instruction Decode),对取出的指令按照规定格式进行拆分和译码。
    (3)执行(EX,Execute),执行具体指令操作。
    (4)访问存储(MEM,Memory),根据指令访问存储、完成存储和读取。
    (5)写回(WB,Write Back),将计算结果写回到存储器。

    CPU内部关键结构:
    (1)算术逻辑运算器(ALU);
    (2)累加器;
    (3)程序计数器;
    (4)指令寄存器和译码器;
    (5)时序和控制部件。
    RISC_CPU内部结构和Verilog实现
    本项目中的RISC_CPU一共有9个模块组成,具体如下:
    (1)时钟发生器;
    (2)指令寄存器;
    (3)累加器;
    (4)算术逻辑运算单元;
    (5)数据控制器;
    (6)状态控制器;
    (7)主状态机;
    (8)程序计数器;
    (9)地址多路器。

    整体结构

    时钟发生器

    模块图:
    image.png
    端口描述:
    reset是高电平复位信号;
    clk是外部时钟信号;
    fetch是控制信号,是clk的八分频信号;fetch为高电平时,触发执行指令以及地址多路器输出指令地址和数据地址。
    alu_ena是算术逻辑运算单元的使能信号。

    可以看到alu_ena提前fetch高电平一个clk周期,fetch是clk的8分频信号。

    Verilog代码:

    // Description: RISC——CPU 时钟发生器
    // -----------------------------------------------------------------------------
    module clk_gen (
        input             clk         ,    // Clock
        input             reset         ,      // High level reset
        output     reg     fetch        ,     // 8 frequency division
        output      reg     alu_ena          // Arithmetic enable
    );
    
            reg [7:0] state;
            
            //One-piece state machine
            parameter S1 = 8'b0000_0001,
                      S2 = 8'b0000_0010,
                      S3 = 8'b0000_0100,
                      S4 = 8'b0000_1000,
                      S5 = 8'b0001_0000,
                      S6 = 8'b0010_0000,
                      S7 = 8'b0100_0000,
                      S8 = 8'b1000_0000,
                      idle = 8'b0000_0000;
            
            always@(posedge clk)begin
                if(reset)begin
                        fetch <= 0;
                        alu_ena <= 0;
                        state <= idle;
                end
                else begin
                        case(state)
                            S1:
                                begin 
                                    alu_ena <= 1;
                                    state <= S2;
                                end
                            S2:
                                begin
                                    alu_ena <= 0;
                                    state <= S3;
                                end
                            S3:
                                begin
                                    fetch <= 1;
                                    state <=S4;
                                end
                            S4:
                                begin
                                    state <= S5;
                                end
                            S5:
                                state <= S6;
                            S6:
                                state <= S7;
                            S7:
                                begin
                                    fetch <= 0;
                                    state <= S8;
                                end
                            S8:
                                begin
                                    state <= S1;
                                end
                            idle: state <= S1;
                            default: state <=idle;
                        endcase
                end
            end
    endmodule

    指令寄存器

    模块图:

    端口描述:
    寄存器是将数据总线送来的指令存入高8位或低8位寄存器中。
    ena信号用来控制是否寄存。
    每条指令为两个字节,16位,高3位是操作码,低13位是地址(CPU地址总线为13位,寻址空间为8K字节)。
    本设计的数据总线为8位,每条指令需要取两次,先取高8位,再取低8位。

    Verilog代码:

    // Description: RISC—CPU 指令寄存器 
    // -----------------------------------------------------------------------------
    module register (
        input     [7:0]        data         ,
        input                 clk         ,
        input                 rst         ,
        input                 ena         ,
        output reg [15:0]    opc_iraddr
        
    );
    
        reg state     ;
                //
             always@( posedge clk ) begin
                if( rst ) begin
                        opc_iraddr <= 16'b 0000_0000_0000_0000;
                        state <= 1'b 0;
                end // if rst
        
        // If load_ir from machine actived, load instruction data from rom in 2 clock periods.
        // Load high 8 bits first, and then low 8 bits.
                else if( ena ) begin
                        case( state )
                        1'b0    : begin opc_iraddr [ 15 : 8 ] <= data;                    
                                        state <= 1; 
                                  end
                        1'b1    : begin opc_iraddr [  7 : 0 ] <= data;                    
                                        state <= 0; 
                                  end
                        default : begin opc_iraddr [ 15 : 0 ] <= 16'bxxxx_xxxx_xxxx_xxxx; 
                                        state <= 1'bx; 
                                  end
                        endcase // state
                end // else if ena
          
                else state <= 1'b0;
            end 
    
    endmodule  

    累加器

    模块图:

    端口描述:
    累加器用于存放当前结果,ena信号有效时,在clk上升沿输出数据总线的数据。

    // Description: RISC-CPU  累加器模块
    // -----------------------------------------------------------------------------
    module accum (
        input                 clk     ,   // Clock
        input                 ena     ,     // Enable
        input                  rst     ,   // Asynchronous reset active high
        input [7:0]            data     ,    // Data bus
        output reg [7:0]     accum 
        
    );
    
            
            always@(posedge clk)begin
                if(rst)
                        accum <= 8'b0000_0000;//Reset
                else if(ena)
                        accum <= data;
                end
    
    endmodule 
    

    算术运算器

    模块图:
    image.png
    端口描述:
    算术逻辑运算单元可以根据输入的操作码分别实现相应的加、与、异或、跳转等基本操作运算。
    本单元支持8种操作运算。
    opcode用来选择计算模式
    data是数据输入
    accum是累加器输出
    alu_ena是模块使能信号
    clk是系统时钟
    Verilog代码:

    // Description: RISC-CPU 算术运算器
    // -----------------------------------------------------------------------------
    module alu (
        input                 clk         ,        // Clock
        input                 alu_ena        ,         // Enable
        input     [2:0]         opcode         ,          // High three bits are used as opcodes
        input     [7:0]        data        ,        // data
        input     [7:0]        accum         ,        // accum out
        output reg [7:0]    alu_out     ,
        output                 zero     
    );
    
    parameter 
                HLT     =    3'b000    ,
                SKZ     =    3'b001     ,
                ADD     =    3'b010    ,
                ANDD     =    3'b011    ,
                XORR     =    3'b100     ,
                LDA     =    3'b101    ,
                STO     =    3'b110    ,
                JMP     =    3'b111    ;
                                      
        always @(posedge clk) begin 
                if(alu_ena) begin
                casex(opcode)//操作码来自指令寄存器的输出 opc_iaddr(15..0)的第三位
                    HLT:     alu_out        <=    accum             ;
                    SKZ:     alu_out     <=    accum            ;
                    ADD:     alu_out     <=  data + accum     ;
                    ANDD:    alu_out        <=     data & accum     ;
                    XORR:     alu_out     <=    data ^ accum     ;
                    LDA :     alu_out     <=     data             ;
                    STO :     alu_out     <=    accum             ;
                    JMP :     alu_out        <=    accum            ;
                    default: alu_out     <=    8'bxxxx_xxxx    ;
                endcase
                end
        end
    
        assign zero = !accum;
    
    endmodule 

    数据控制器

    模块图:
    image.png
    端口描述:
    数据控制器的作用是控制累加器的数据输出,数据总线是分时复用的,会根据当前状态传输指令或者数据。
    数据只在往RAM区或者端口写时才允许输出,否则呈现高阻态。
    in是8bit数据输入
    data_ena是使能信号
    data是8bit数据输出
    Verilog代码:

    // Description: RISC-CPU 数据控制器
    // -----------------------------------------------------------------------------
    module datactl (
        input     [7:0]        in             ,        // Data input
        input                 data_ena     ,         // Data Enable
        output     wire [7:0]    data                  // Data output
        
    );
    
    assign  data = (data_ena )? in: 8'bzzzz_zzzz     ;
    
    endmodule 

    地址多路器

    模块图:
    image.png
    端口描述:

    用于选择输出地址是PC(程序计数)地址还是数据/端口地址。每个指令周期的前4个时钟周期用于从ROM种读取指令,输出的是PC地址;后四个时钟周期用于对RAM或端口读写。
    地址多路器和数据控制器实现的功能十分相似。
    fetch信号用来控制地址输出,高电平输出pc_addr ,低电平输出ir_addr ;
    pc_addr 指令地址;
    ir_addr ram或端口地址。

    Verilog代码:

    // Description: RISC-CPU 地址多路器
    // -----------------------------------------------------------------------------
    module adr (
        input                 fetch         ,   // enable
        input [12:0]        ir_addr     ,     //     
        input [12:0]         pc_addr     ,      //     
        output wire [12:0]    addr     
    );
    
    
    assign     addr = fetch? pc_addr :ir_addr    ;
    
    endmodule 

    程序计数器

    模块图:
    image.png
    端口描述:
    程序计数器用来提供指令地址,指令按照地址顺序存放在存储器中。包含两种生成途径:
    (1)顺序执行的情况
    (2)需要改变顺序,例如JMP指令
    rst复位信号,高电平时地址清零;
    clock 时钟信号,系统时钟;
    ir_addr目标地址,当加载信号有效时输出此地址;
    pc_addr程序计数器地址
    load地址装载信号

    Verilog代码:

    // Description: RISC-CPU 程序计数器
    // -----------------------------------------------------------------------------
    module counter (
        input [12:0]        ir_addr      ,        // program address
        input                 load          ,         // Load up signal
        input                 clock        ,        // CLock
        input                 rst         ,        // Reset
        output    reg [12:0]    pc_addr                // insert program address
    );
    
            always@(posedge clock or posedge rst) begin
                    if(rst)
                        pc_addr <= 13'b0_0000_0000_0000;
                    else if(load)
                            pc_addr <= ir_addr;
                    else
                            pc_addr <= pc_addr + 1;
            end
    
    
    endmodule 

    状态控制器&主状态机

    模块图:
    image.png
    (图左边)状态机端口描述:
    状态控制器接收复位信号rst,rst有效,控制输出ena为0,fetch有效控制ena为1。

    // Description: RISC-CPU 状态控制器
    // -----------------------------------------------------------------------------
    module machinectl (
        input clk             ,        // Clock
        input rst             ,         // Asynchronous reset
        input fetch         ,          // Asynchronous reset active low
        output reg ena                 // Enable 
        
    );
    
            always@(posedge clk)begin
                    if(rst)
                            ena <= 0;
                    else if(fetch)
                            ena <=1;
            end
    
    endmodule 

    (图右边)主状态端口描述:
    主状态机是CPU的控制核心,用于产生一系列控制信号。
    指令周期由8个时钟周期组成,每个时钟周期都要完成固定的操作。
    (1)第0个时钟,CPU状态控制器的输出rd和load_ir 为高电平,其余为低电平。指令寄存器寄存由ROM送来的高8位指令代码。
    (2)第1个时钟,与上一个时钟相比只是inc_pc从0变为1,故PC增1,ROM送来低8位指令代码,指令寄存器寄存该8位指令代码。
    (3)第2个时钟,空操作。
    (4)第3个时钟,PC增1,指向下一条指令。
    操作符为HLT,输出信号HLT为高。
    操作符不为HLT,除PC增1外,其余控制线输出为0.
    (5)第4个时钟,操作。
    操作符为AND,ADD,XOR或LDA,读取相应地址的数据;
    操作符为JMP,将目的地址送给程序计数器;
    操作符为STO,输出累加器数据。
    (6)第5个时钟,若操作符为ANDD,ADD或者XORR,算术运算器完成相应的计算;
    操作符为LDA,就把数据通过算术运算器送给累加器;
    操作符为SKZ,先判断累加器的值是否为0,若为0,PC加1,否则保持原值;
    操作符为JMP,锁存目的地址;
    操作符为STO,将数据写入地址处。
    (7)第6个时钟,空操作。
    (8)第7个时钟,若操作符为SKZ且累加器为0,则PC值再加1,跳过一条指令,否则PC无变化。

    // Description: RISC-CPU 主状态机
    // -----------------------------------------------------------------------------
    module machine (
        input         clk             ,        // Clock
        input          ena                ,         // Clock Enable
        input         zero            ,          // Asynchronous reset active low
        input [2:0]    opcode             ,        // OP code
        output     reg inc_pc             ,        //
        output  reg load_acc        ,         //    
        output    reg    load_pc         ,         //
        output     reg rd                 ,        //
        output     reg wr                 ,         //
        output     reg load_ir         ,         //
        output     reg datactl_ena     ,         //
        output  reg halt             
    );
    
        reg  [2:0] state  ;
    //parameter 
            parameter 
                        HLT     = 3'b000    ,
                        SKZ     = 3'b001    ,
                        ADD     = 3'b010    ,
                        ANDD     = 3'b011    ,
                        XORR     = 3'b100    ,
                        LDA     = 3'b101    ,
                        STO     = 3'b110    ,
                        JMP     = 3'b111    ;
            always@(negedge clk) begin
                    if(!ena)  //收到复位信号rst,进行复位操作
                        begin
                            state <= 3'b000;
                            {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                            {wr,load_ir,datactl_ena,halt} <= 4'b0000;
                        end
                    else
                        ctl_cycle;
            end
    
                //------- task ctl_cycle -------
                
                task ctl_cycle;
                    begin
                        casex(state)
                            3'b000:   //load high 8bits in struction
                                    begin    
                                        {inc_pc,load_acc,load_pc,rd} <= 4'b0001;
                                        {wr,load_ir,datactl_ena,halt} <= 4'b0100;
                                        state <= 3'b001;
                                    end
                            3'b001://pc increased by one then load low 8bits instruction
                                    begin
                                        {inc_pc,load_acc,load_pc,rd} <= 4'b1001;
                                        {wr,load_ir,datactl_ena,halt} <= 4'b0100;
                                        state <= 3'b010;
                                    end
                            3'b010: //idle
                                    begin
                                        {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                        {wr,load_ir,datactl_ena,halt} <= 4'b0000;
                                        state <= 3'b011;
                                    end
                            3'b011:  //next instruction address setup 分析指令开始点
                                    begin
                                        if(opcode == HLT)//指令为暂停HLT
                                            begin
                                                {inc_pc,load_acc,load_pc,rd} <= 4'b1000;
                                                {wr,load_ir,datactl_ena,halt} <= 4'b0001;
                                            end
                                        else
                                            begin
                                                {inc_pc,load_acc,load_pc,rd} <= 4'b1000;
                                                {wr,load_ir,datactl_ena,halt} <= 4'b0000;
                                            end
                                    state <= 3'b100;
                                    end
                            3'b100: //fetch oprand
                                    begin
                                        if(opcode == JMP)
                                            begin
                                                {inc_pc,load_acc,load_pc,rd} <= 4'b0010;
                                                {wr,load_ir,datactl_ena,halt} <= 4'b0000;
                                            end
                                        else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
                                                begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b0001;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0000;
                                                
                                                end
                                        else if(opcode == STO)
                                                begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0010;    
                                                end
                                        else    
                                            begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                            end
                                        state <= 3'b101;                                
                                    end
                            3'b101://operation
                                    begin
                                        if(opcode == ADD || opcode == ANDD ||opcode ==XORR ||opcode == LDA)//过一个时钟后与累加器的内存进行运算
                                            begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b0101;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                            end
                                        else if(opcode == SKZ && zero == 1)// & and &&
                                                begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b1000;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                                end
                                            else if(opcode == JMP)
                                                    begin
                                                        {inc_pc,load_acc,load_pc,rd} <= 4'b1010;
                                                        {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                                    end
                                            else if(opcode == STO)
                                                        begin//过一个时钟后吧wr变为1,写到RAM中
                                                            {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                            {wr,load_ir,datactl_ena,halt} <= 4'b1010;    
                                                        end
                                            else    
                                                    begin
                                                            {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                            {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                                    end
                                            state <= 3'b110;
                                    end
                            3'b110:
                                        begin
                                            if(opcode == STO)
                                                begin
                                                    {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                    {wr,load_ir,datactl_ena,halt} <= 4'b0010;    
                                                end
                                            else if(opcode == ADD || opcode == ANDD || opcode == XORR || opcode == LDA)
                                                    begin
                                                        {inc_pc,load_acc,load_pc,rd} <= 4'b0001;
                                                        {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                                    end
                                            else
                                                    begin
                                                        {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                        {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                                    end
                                        state <= 3'b111;
                                        end
                            3'b111:
                                    begin
                                        if(opcode == SKZ && zero == 1)
                                            begin
                                                {inc_pc,load_acc,load_pc,rd} <= 4'b1000;
                                                {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                            end
                                        else
                                            begin
                                                {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                                {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                            end
                                    state <= 3'b000;
                                    end
                            default:
                                        begin
                                            {inc_pc,load_acc,load_pc,rd} <= 4'b0000;
                                            {wr,load_ir,datactl_ena,halt} <= 4'b0000;    
                                            state <= 3'b000;
                                        end
                        endcase
                    end
                endtask
    
    endmodule 
    

    外围模块

    为了对RISC-CPU进行测试,需要对ROM、RAM和地址译码器进行设计。

    地址译码器

    模块说明:
    地址译码器用于产生选通信号,选通ROM或者RAM
    1FFFH —— 1800H RAM(范围):1_1xxx_xxxx_xxxx
    17FFH —— 0000H ROM(范围):0_xxxx_xxxx_xxxx+1_0xxx_xxxx_xxxx

    Verilog代码:

    // Description: RISC-CPU 地址译码器
    // -----------------------------------------------------------------------------
    module addr_decode (
        input [12:0]    addr     ,   // Address
        output reg         ram_sel ,    // Ram sel
        output reg         rom_sel     // Rom sel
    );
    
    
            always@(addr)begin
                casex(addr)
                        13'b1_1xxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b01;
                        13'b0_xxxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b10;
                        13'b1_0xxx_xxxx_xxxx:{rom_sel,ram_sel} <= 2'b10;
                        default: {rom_sel,ram_sel} <= 2'b00;
                endcase
                        
            end
    
    endmodule 

    RAM

    模块说明:
    RAM用于存放临时数据,可读可写。

    Verilog代码:

    // Description: RISC-CPU RAM模块
    // -----------------------------------------------------------------------------
    module ram (
        input                 ena          ,            // Enable
        input                 read        ,             // read Enable
        input                 write        ,              // write Enable
        inout wire [7:0]    data         ,            // data
        input [9:0]            addr                     // address
    );
    
        reg [7:0]    ram [10'h3ff:0]    ;
        
            assign data = (read && ena )? ram[addr]:8'h zz;
            
            always@(posedge write) begin
                    ram[addr] <= data;
            end
    endmodule 
    

    ROM

    模块说明:
    RAM用于存放只读数据。

    Verilog代码:

    // Description: RISC-CPU ROM模块
    // -----------------------------------------------------------------------------
    module rom (
        input     [12:0]        addr     ,
        input                 read     ,
        input                 ena     ,
        output wire [7:0]    data 
    );
            reg [7:0] memory [13'h1ff:0];
            assign data = (read && ena)? memory[addr]:8'b zzzz_zzzz;
    
    endmodule 
    

    顶层模块

    模块图:


    Verilog代码:

    // Description: RISC-CPU 顶层模块
    // -----------------------------------------------------------------------------
    //`include "clk_gen.v"
    //`include "accum.v"
    //`include "adr.v"
    //`include "alu.v"
    //`include "machine.v"
    //`include "counter.v"
    //`include "machinectl.v"
    //`iclude "machine.v"
    //`include "register.v"
    //`include "datactl.v"
    module RISC_CPU (
            input                 clk     ,
            input                 reset     ,
            output     wire         rd         ,
            output     wire         wr         ,
            output     wire         halt     ,
            output     wire          fetch     ,
            //addr
            output     wire [12:0]    addr     ,
            output     wire [12:0]    ir_addr ,
            output     wire [12:0]    pc_addr ,
            inout     wire [7:0]    data     ,
            //op
            output     wire [2:0]    opcode     
    );
    
            wire [7:0] alu_out     ; 
            wire [7:0] accum    ;
    
            wire          zero         ;
            wire         inc_pc        ;
            wire        load_acc    ;
            wire         load_pc        ;
            wire        load_ir        ;
            wire        data_ena    ;
            wire         contr_ena    ;
            wire         alu_ena        ;
    
    //inst
    
            clk_gen mclk_gen(
                .clk         (clk         ),
                .reset         (reset         ),
                .fetch         (fetch        ),
                .alu_ena     (alu_ena    )
                );
                
            register m_register(
                    .data         (data                 ),
                    .ena         (load_ir             ),
                    .rst         (reset                 ),
                    .clk         (clk                 ),
                    .opc_iraddr ({opcode,ir_addr}    )
                    );
    
            accum m_accum(
                    .data      (alu_out        ),
                    .ena     (load_acc         ),
                    .clk     (clk             ),
                    .rst     (reset           ),
                    .accum     (accum             )
                    );
    
            alu m_alu(
                .data         (data         ),
                .accum         (accum         ),
                .clk         (clk         ),
                .alu_ena     (alu_ena     ),
                .opcode     (opcode     ),
                .alu_out     (alu_out     ),
                .zero         (zero         )
                );
    
            machinectl m_machinectl(
                .clk         (clk         ),
                .rst         (reset         ),
                .fetch         (fetch         ),
                .ena         (contr_ena     )
                );
    
            machine m_machine(
                .inc_pc     (inc_pc         ),
                .load_acc     (load_acc         ),
                .load_pc     (load_pc         ),
                .rd         (rd             ),
                .wr         (wr             ),
                .load_ir     (load_ir         ),
                .clk         (clk             ),
                .datactl_ena(data_ena         ),
                .halt         (halt             ),
                .zero         (zero             ),
                .ena         (contr_ena         ),
                .opcode         (opcode         )
                );
    
            datactl m_datactl(
                .in         (alu_out         ),
                .data_ena     (data_ena         ),
                .data         (data             )
                );
    
            adr m_adr(
                .fetch      (fetch         ),
                .ir_addr     (ir_addr     ),
                .pc_addr     (pc_addr     ),
                .addr         (addr         )
                );
    
            counter m_counter(
                .clock         (inc_pc     ),
                .rst         (reset         ),
                .ir_addr     (ir_addr     ),
                .load         (load_pc     ),
                .pc_addr     (pc_addr     )
                );
    
    endmodule 
    

    Testbench

    Testbench包含三个测试程序,这个部分不能综合。

    Test1程序

    TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
    若各条指令正确,应该在地址2E(hex)处,在执行HLT时刻停止。若程序在任何其他位置停止,则必有一条指令运行错误,可以按照注释找到错误的指令。

    test1汇编程序:(.pro文件/存放于ROM)

    //机器码-地址-汇编助记符-注释
    @00
    //address statement
    111_0000     //00 BEGIN: JMP TST_JMP
    0011_1100
    000_0000    //02 HLT //JMP did not work
    0000_0000
    000_00000    //04    HLT //JMP did not load PC skiped
    0000_0000    
    101_1100   //06 JMP_OK: LDA DATA
    0000_0000
    001_00000  //08 SKZ
    0000_0000
    000_0000    //0a HLT
    0000_0000
    101_11000    //0C LDA DATA_2
    0000_0001
    001_00000    //0E SKZ
    0000_0000
    111_0000    //10    JMP SKZ_OK
    001_0100
    000_0000    //12    HLT
    0000_0000
    110_11000    //14    SKZ_OK: STO TEMP
    0000_0010
    101_11000    //16    LDA DATA_1
    0000_0000
    110_11000    //18    STO TEMP
    0000_0010
    101_11000    //1A LDA TEMP
    0000_0010
    001_00000    //1C SKZ
    0000_0000
    000_00000    //1E HLT
    0000_0000
    100_11000    //20 XOR DATA_2
    0000_0001
    001_00000     //22    SKZ
    0000_0000
    111_00000    //24     JMP XOR_OK
    0010_1000
    000_00000    //26 HLT
    0000_0000
    100_11000    //28    XOR_OK XOR DATA_2
    0000_0001
    001_00000    //2A    SKZ
    0000_0000
    000_00000    //2C HLT
    0000_0000
    000_0000    //2E END
    0000_0000
    111_00000    //30    JMP BEGIN
    0000_0000
    
    @3c
    111_00000 //3c TST_JMP IMR OK
    0000_0110
    000_00000    //3E HLT
    

    test1数据文件:(.dat/存放于RAM)

    /-----------------------------------
    @00        ///address statement at RAM
    00000000    //1800  DATA_1
    11111111    //1801 DATA_2
    10101010    //1082    TEMP

    Test2程序
    TEST1程序用于验证RISC-CPU的逻辑功能,根据汇编语言由人工编译的。
    这个程序是用来测试RISC-CPU的高级指令集,若执行正确,应在地址20(hex)处在执行HLT时停止。

    test2汇编程序:

    @00
    101_11000    //00    BEGIN
    0000_0001
    011_11000    //02    AND DATA_3
    0000_0010
    100_11000    //04    XOR DATA_2
    0000_0001    
    001_00000    //06    SKZ
    0000_0000
    000_00000    //08 HLT
    0000_0000
    010_11000    //0A    ADD DATA_1
    0000_0000
    001_00000    //0C    SKZ
    0000_0000
    111_00000    //0E    JMP    ADD_OK
    0001_0010
    111_00000    //10    HLT
    0000_0000
    100_11000    //12    ADD_OK XOR DATA_3
    0000_0010
    010_11000    //14    ADD DATA_1
    0000_0000
    110_11000    //16    STO TEMP
    0000_0011    
    101_11000    //18    LDA DATA_1
    0000_0000
    010_11000    //1A    ADD TEMP
    0000_0001
    001_00000    //1C    SKZ
    0000_0000
    000_00000    //1E    HLT
    0000_0000
    000_00000    //END    HLT
    0000_0000
    111_00000    //JMP BEGIN
    0000_0000

    test2数据文件:

    @00
    00000001    //1800    DATA_1
    10101010    //1801    DATA_2
    11111111    //1802    DATA_3
    00000000    //1803    TEMP

    Test3程序

    TEST3程序是一个计算0~144的斐波那契数列的程序,用来验证CPU整体功能。

    test3汇编程序:

    @00
    101_11000    //00    LOOP:LDA FN2
    0000_0001
    110_11000    //02    STO TEMP
    0000_0010
    010_11000    //04    ADD    FN1
    0000_0000
    110_11000    //06    STO FN2
    0000_0001
    101_11000    //08    VLDA TEMP
    0000_0010
    110_11000    //0A    STO    FN1
    0000_0000
    100_11000    //0C    XOR    LIMIT
    0000_0011
    001_00000    //0E    SKZ
    0000_0000
    111_00000    //10    JMP    LOOP
    0000_0000
    000_00000    //12    DONE HLT
    0000_0000

    test3数据文件:

    @00
    00000001        //1800    FN1
    00000000        //1801    FN2
    00000000        //1802    TEMP
    10010000        //1803    LIMIT

    完整的testbench

    Verilog代码:

    // Description: RISC-CPU 测试程序
    // -----------------------------------------------------------------------------
    `include "RISC_CPU.v"
    `include "ram.v"
    `include "rom.v"
    `include "addr_decode.v"
    
    `timescale 1ns/1ns
    
    `define PERIOD 100 // matches clk_gen.v
    
    module cputop_tb;
      reg [( 3 * 8 ): 0 ] mnemonic; // array that holds 3 8 bits ASCII characters
      reg  [ 12 : 0 ] PC_addr, IR_addr;
      reg  reset_req, clock;
      wire [ 12 : 0 ] ir_addr, pc_addr; // for post simulation.
      wire [ 12 : 0 ] addr;
      wire [  7 : 0 ] data;
      wire [  2 : 0 ] opcode;           // for post simulation.
      wire fetch;                       // for post simulation.
      wire rd, wr, halt, ram_sel, rom_sel;
      integer test;
      
      //-----------------DIGITAL LOGIC----------------------
      RISC_CPU t_cpu (.clk( clock ),.reset( reset_req ),.halt( halt ),.rd( rd ),.wr( wr ),.addr( addr ),.data( data ),.opcode( opcode ),.fetch( fetch ),.ir_addr( ir_addr ),.pc_addr( pc_addr ));
      ram t_ram (.addr ( addr [ 9 : 0 ]),.read ( rd ),.write ( wr ),.ena ( ram_sel ),.data ( data ));
      rom t_rom (.addr ( addr          ),.read ( rd ),              .ena ( rom_sel ),.data ( data ));
      addr_decode t_addr_decoder (.addr( addr ),.ram_sel( ram_sel ),.rom_sel( rom_sel ));
      
      //-------------------SIMULATION-------------------------
      initial begin
        clock = 0;
        // display time in nanoseconds
        $timeformat ( -9, 1, "ns", 12 );
        display_debug_message;
        sys_reset;
        test1; $stop;
        test2; $stop;
        test3;
        $finish; // simulation is finished here.
      end // initial
      
      task display_debug_message;
        begin
          $display ("\n************************************************"  );
          $display (  "* THE FOLLOWING DEBUG TASK ARE AVAILABLE:      *"  );
          $display (  "* \"test1;\" to load the 1st diagnostic program. *");
          $display (  "* \"test2;\" to load the 2nd diagnostic program. *");
          $display (  "* \"test3;\" to load the     Fibonacci  program. *");
          $display (  "************************************************\n");
        end
      endtask // display_debug_message
      
      task test1;
        begin
          test = 0;
          disable MONITOR;
          $readmemb ("test1.pro", t_rom.memory );
          $display ("rom loaded successfully!");
          $readmemb ("test1.dat", t_ram.ram );
          $display ("ram loaded successfully!");
          #1 test = 1;
          #14800;
          sys_reset;
        end
      endtask // test1
      
      task test2;
        begin
          test = 0;
          disable MONITOR;
          $readmemb ("test2.pro", t_rom.memory );
          $display ("rom loaded successfully!");
          $readmemb ("test2.dat", t_ram.ram );
          $display ("ram loaded successfully!");
          #1 test = 2;
          #11600;
          sys_reset;
        end
      endtask // test2
      
      task test3;
        begin
          test = 0;
          disable MONITOR;
          $readmemb ("test3.pro", t_rom.memory );
          $display ("rom loaded successfully!");
          $readmemb ("test3.dat", t_ram.ram );
          $display ("ram loaded successfully!");
          #1 test = 3;
          #94000;
          sys_reset;
        end
      endtask // test1
      
      task sys_reset;
        begin
          reset_req = 0;
          #( `PERIOD * 0.7 ) reset_req = 1;
          #( 1.5 * `PERIOD ) reset_req = 0;
        end
      endtask // sys_reset
      
      //--------------------------MONITOR--------------------------------
      always@( test ) begin: MONITOR
        case( test )
          1: begin // display results when running test 1
            $display("\n*** RUNNING CPU test 1 - The Basic CPU Diagnostic Program ***");
            $display("\n        TIME      PC      INSTR      ADDR      DATA          ");
            $display("         ------    ----    -------    ------    ------         ");
            while( test == 1 )@( t_cpu.pc_addr ) begin // fixed
              if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
                #60  PC_addr <= t_cpu.pc_addr - 1;
                     IR_addr <= t_cpu.ir_addr;
                #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
              end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
            end // while test == 1 @ t_cpu.pc_addr
          end
            
          2: begin // display results when running test 2
            $display("\n*** RUNNING CPU test 2 - The Basic CPU Diagnostic Program ***");
            $display("\n        TIME      PC      INSTR      ADDR      DATA          ");
            $display("         ------    ----    -------    ------    ------         ");
            while( test == 2 )@( t_cpu.pc_addr ) begin // fixed
              if(( t_cpu.pc_addr % 2 == 1 )&&( t_cpu.fetch == 1 )) begin // fixed
                #60  PC_addr <= t_cpu.pc_addr - 1;
                     IR_addr <= t_cpu.ir_addr;
                #340 $strobe("%t %h %s %h %h", $time, PC_addr, mnemonic, IR_addr, data ); // Here data has been changed t_cpu.m_register.data
              end // if t_cpu.pc_addr % 2 == 1 && t_cpu.fetch == 1
            end // while test == 2 @ t_cpu.pc_addr
          end
            
          3: begin // display results when running test 3
            $display("\n*** RUNNING CPU test 3 - An Executable Program **************");
            $display("***** This program should calculate the fibonacci *************");
            $display("\n        TIME      FIBONACCI NUMBER          ");
            $display("         ------    -----------------_         ");
            while( test == 3 ) begin
              wait( t_cpu.opcode == 3'h 1 ) // display Fib. No. at end of program loop
              $strobe("%t     %d", $time, t_ram.ram [ 10'h 2 ]);
              wait( t_cpu.opcode != 3'h 1 );
            end // while test == 3
          end
        endcase // test
      end // MONITOR: always@ test
      
      //-------------------------HALT-------------------------------
      always@( posedge halt ) begin // STOP when HALT intruction decoded
        #500 $display("\n******************************************");
             $display(  "** A HALT INSTRUCTION WAS PROCESSED !!! **");
             $display(  "******************************************");
      end // always@ posedge halt
      
      //-----------------------CLOCK & MNEMONIC-------------------------
      always#(`PERIOD / 2 ) clock = ~ clock;
      
      always@( t_cpu.opcode ) begin // get an ASCII mnemonic for each opcode
        case( t_cpu.opcode )
          3'b 000 : mnemonic = "HLT";
          3'b 001 : mnemonic = "SKZ";
          3'b 010 : mnemonic = "ADD";
          3'b 011 : mnemonic = "AND";
          3'b 100 : mnemonic = "XOR";
          3'b 101 : mnemonic = "LDA";
          3'b 110 : mnemonic = "STO";
          3'b 111 : mnemonic = "JMP";
          default : mnemonic = "???";
        endcase 
      end 
    endmodule 
    $ readmemb ( "test1. pro" ,t_ rom. . memory );
    $ readmemb ( "testl. dat",t_ ram_ . ram);
    即可把编译好的汇编机器码装人虚拟ROM,把需要参加运算的数据装人虚拟RAM就可以开始仿真。上面语句中的第一项为打开的文件名,后一项为系统层次管理下的ROM模块和RAM模块中的存储器memory和ram。

    源代码&脚本

    前仿真结果

    test1

    test2

    test3

    DC后仿真

    采用SMIC180工艺在典型环境下进行测试
    时序报告:

    面积报告:

    功耗报告:

    综合电路图:


    总结

    该项目更加偏向于教学练习,CPU也是数字IC的重要研究方向,对此感兴趣的同学可以找点论文和开源资料进行学习。可以进一步优化如流水线、运算单元,扩展成SOC系统等。

    5
    Design Compile(DC)优化性、高性能性综合
    « 上一篇 2023-04-11
    【硬件算法】Verilog之FPGA实现信号移相法
    下一篇 » 2023-04-06

    评论 (5)

    取消
    1. 头像
      sss
      Windows 10 · Google Chrome

      回复

      回复
    2. 头像
      SAS
      Windows 10 · Google Chrome

      不逆 不耻 不憾

      回复
    3. 头像
      QUI
      Windows 10 · Google Chrome

      为你,千千万万遍。

      回复
    4. 头像
      无殇
      iPhone · QQ Browser

      强💪

      回复
    5. 头像
      小孩子
      Windows 10 · Google Chrome

      相信十年后的八月,我们还会相遇。

      回复