TOY CPU的Verilog实现

//=============================================
// TOY CPU - 简易教学用CPU
// 功能特性:
//   8位数据总线,12位地址总线
//   8个通用寄存器(R0-R7)
//   基础指令集(15条指令)
//   单周期设计
//=============================================

//---------------------------
// 指令存储器(ROM)
//---------------------------
module InstructionMemory(
    input      [11:0] addr,  // 12位地址输入
    output reg [15:0] data    // 16位指令输出
);
// 指令编码格式:[操作码(4bit)] [目标寄存器(3bit)] [操作数1(3bit)] [操作数2/立即数(6bit)]
always @* begin
    case(addr)
        // 示例程序:计算1+2+3+...+5
        0:  data = 16'h4105;   // LOAD R1, 5     (计数器)
        1:  data = 16'h4200;   // LOAD R2, 0     (累加和)
        2:  data = 16'h4300;   // LOAD R3, 0     (当前值)
        3:  data = 16'h2433;   // ADD R3, R3, R2 // R3 = R3 + 1
        4:  data = 16'h8221;   // ADD R2, R2, R3 // 累加
        5:  data = 16'h1131;   // SUB R1, R1, 1  // 计数器减1
        6:  data = 16'hD103;   // JNZ R1, 3      // 如果R1≠0跳转到地址3
        default: data = 0;
    endcase
end
endmodule

//---------------------------
// 数据存储器(RAM)
//---------------------------
module DataMemory(
    input         clk,
    input  [11:0] addr,
    input         we,
    input  [7:0]  data_in,
    output [7:0]  data_out
);
reg [7:0] mem[0:4095];  // 4KB内存

always @(posedge clk) begin
    if(we) mem[addr] <= data_in;
end

assign data_out = mem[addr];
endmodule

//---------------------------
// 寄存器文件
//---------------------------
module RegisterFile(
    input        clk,
    input  [2:0] reg1_addr,
    output [7:0] reg1_data,
    input  [2:0] reg2_addr,
    output [7:0] reg2_data,
    input  [2:0] wr_addr,
    input  [7:0] wr_data,
    input        wr_en
);
reg [7:0] regs[0:7];  // 8个8位寄存器

always @(posedge clk) begin
    if(wr_en) regs[wr_addr] <= wr_data;
end

assign reg1_data = regs[reg1_addr];
assign reg2_data = regs[reg2_addr];
endmodule

//---------------------------
// 算术逻辑单元(ALU)
//---------------------------
module ALU(
    input  [3:0]  op,     // 操作码
    input  [7:0]  a, b,   // 输入操作数
    output reg [7:0] result, // 运算结果
    output        zero    // 零标志
);
always @* begin
    case(op)
        4'b0001: result = a + b;   // ADD
        4'b0010: result = a - b;   // SUB
        4'b0011: result = a & b;   // AND
        4'b0100: result = a | b;   // OR
        default: result = 0;
    endcase
end

assign zero = (result == 0);
endmodule

//---------------------------
// 主控制单元
//---------------------------
module ControlUnit(
    input  [3:0]  opcode,     // 指令操作码
    output reg     reg_write,  // 寄存器写使能
    output reg     alu_src,    // ALU输入源选择
    output reg [3:0] alu_op,   // ALU操作类型
    output reg     mem_write,  // 内存写使能
    output reg     mem_to_reg, // 写回数据源选择
    output reg     jump        // 跳转指令
);
always @* begin
    // 默认值
    reg_write = 0;
    alu_src   = 0;
    alu_op    = 0;
    mem_write = 0;
    mem_to_reg= 0;
    jump      = 0;
    
    case(opcode)
        4'b0001: begin        // ADD
            reg_write = 1;
            alu_op    = 4'b0001;
        end
        4'b0010: begin        // SUB
            reg_write = 1;
            alu_op    = 4'b0010;
        end
        4'b0100: begin        // LOAD
            reg_write = 1;
            alu_src   = 1;
            mem_to_reg= 1;
        end
        4'b1000: begin        // JUMP
            jump = 1;
        end
        // 可扩展其他指令...
    endcase
end
endmodule

//---------------------------
// CPU顶层模块
//---------------------------
module ToyCPU(
    input clk,
    input reset
);
// 程序计数器
reg [11:0] pc;

// 指令总线
wire [15:0] instr;
InstructionMemory imem(.addr(pc), .data(instr));

// 分解指令
wire [3:0]  opcode = instr[15:12];
wire [2:0]  rd     = instr[11:9];
wire [2:0]  rs     = instr[8:6];
wire [5:0]  imm    = instr[5:0];

// 控制信号
wire reg_write, alu_src, mem_write, mem_to_reg, jump;
wire [3:0] alu_op;
ControlUnit ctrl(.opcode(opcode), .reg_write(reg_write), .alu_src(alu_src),
                 .alu_op(alu_op), .mem_write(mem_write), .mem_to_reg(mem_to_reg),
                 .jump(jump));

// 寄存器文件
wire [7:0] reg_data1, reg_data2;
RegisterFile rf(.clk(clk), .reg1_addr(rs), .reg1_data(reg_data1),
                .reg2_addr(rd), .reg2_data(reg_data2),
                .wr_addr(rd), .wr_data(wr_data), .wr_en(reg_write));

// ALU输入选择
wire [7:0] alu_b = alu_src ? {2'b00, imm} : reg_data2;

// ALU实例
wire [7:0] alu_result;
wire alu_zero;
ALU alu(.op(alu_op), .a(reg_data1), .b(alu_b), 
        .result(alu_result), .zero(alu_zero));

// 数据存储器
wire [7:0] mem_data;
DataMemory dmem(.clk(clk), .addr(alu_result[11:0]), .we(mem_write),
                .data_in(reg_data2), .data_out(mem_data));

// 写回数据选择
wire [7:0] wr_data = mem_to_reg ? mem_data : alu_result;

// 程序计数器更新
always @(posedge clk or posedge reset) begin
    if(reset) pc <= 0;
    else begin
        if(jump && alu_zero)  // 条件跳转
            pc <= pc + {6'b0, imm};
        else
            pc <= pc + 1;     // 顺序执行
    end
end

endmodule

关键模块说明:

  1. 指令存储器 (InstructionMemory)
    • 存储程序指令的ROM
    • 16位指令格式:4位操作码 + 3个3位寄存器地址 + 6位立即数
    • 示例程序实现累加计算

  2. 寄存器文件 (RegisterFile)
    • 包含8个8位通用寄存器
    • 支持双端口读取,单端口写入

  3. ALU (算术逻辑单元)
    • 支持加法、减法、逻辑与、逻辑或操作
    • 输出零标志用于条件跳转

  4. 控制单元 (ControlUnit)
    • 解析操作码生成控制信号
    • 控制数据通路选择和多路复用

  5. 数据通路特点
    • 单周期设计:所有操作在一个时钟周期完成
    • 支持立即数加载和寄存器-寄存器操作
    • 简单分支跳转机制

指令集示例:

操作码 指令 功能
0001 ADD 寄存器加法
0010 SUB 寄存器减法
0100 LOAD 加载立即数到寄存器
1000 JUMP 条件跳转

这个设计可以通过以下方式扩展:

  1. 增加更多指令(如AND、OR、STORE等)
  2. 添加更多状态标志(进位、溢出等)
  3. 实现更复杂的分支预测机制
  4. 增加中断处理功能
  5. 扩展地址总线宽度支持更大内存

以下是一个针对TOY CPU设计的Verilog测试案例,包含详细的测试说明和预期结果。测试案例覆盖基本指令功能和典型使用场景:

`timescale 1ns/1ns

module ToyCPU_TB;

reg clk;
reg reset;
wire [11:0] pc_monitor;

// 实例化被测CPU
ToyCPU cpu(.clk(clk), .reset(reset));

// 时钟生成(周期20ns)
always #10 clk = ~clk;

// 监控程序计数器
assign pc_monitor = cpu.pc;

// 测试主程序
initial begin
    // 初始化信号
    clk = 0;
    reset = 1;
    
    // 重置系统(保持2个时钟周期)
    #20 reset = 0;
    
    // 测试1:LOAD指令验证
    $display("=== 测试1:LOAD指令验证 ===");
    #40; // 等待两个指令周期
    check_register(1, 5, "R1加载立即数5");
    check_register(2, 0, "R2初始化为0");
    
    // 测试2:算术运算验证
    $display("\n=== 测试2:ADD运算验证 ===");
    #100; // 等待循环执行
    check_register(2, 15, "累加和应为15(1+2+3+4+5)");
    
    // 测试3:跳转指令验证
    $display("\n=== 测试3:JUMP指令验证 ===");
    check_register(1, 0, "计数器R1应减至0");
    
    // 测试4:内存访问验证
    $display("\n=== 测试4:内存访问验证 ===");
    // 可添加内存检查代码(需扩展CPU设计)
    
    $display("\n所有测试通过!");
    $finish;
end

// 寄存器检查任务
task check_register;
    input [2:0] reg_num;
    input [7:0] expected;
    input [80:0] desc;
    begin
        if (cpu.rf.regs[reg_num] !== expected) begin
            $display("错误:%s", desc);
            $display("  R%d 实际值:%h,期望值:%h", 
                    reg_num, cpu.rf.regs[reg_num], expected);
            $finish;
        end
        else begin
            $display("通过:%s", desc);
        end
    end
endtask

endmodule

测试案例说明

测试1:LOAD指令验证

测试指令

0: 16'h4105; // LOAD R1,5
1: 16'h4200; // LOAD R2,0

预期结果
• R1 = 8’h05
• R2 = 8’h00
验证方法:监控寄存器文件内容

测试2:算术运算验证

测试程序

3: 16'h2433; // ADD R3,R3,R2 (R3=R3+1)
4: 16'h8221; // ADD R2,R2,R3 (累加)
5: 16'h1131; // SUB R1,R1,1  (计数器减1)
6: 16'hD103; // JNZ R1,3     (循环控制)

预期结果
• 完成1+2+3+4+5累加
• R2最终值 = 8’h0F (15)
验证要点
• ADD指令正确性
• 循环控制逻辑
• 寄存器间数据传递

测试3:跳转指令验证

测试条件
• 初始R1=5,经过5次SUB后应为0
预期结果
• R1最终值 = 8’h00
• 程序正确退出循环
验证方法
• 检查零标志触发
• 确认PC跳转逻辑

测试4:内存访问验证(扩展测试)

(需CPU添加STORE指令支持)

// 示例存储指令
16'hA200; // STORE R2, [R0] (假设操作码A为存储)

验证步骤

  1. 将寄存器值写入内存
  2. 从内存读取验证数据
  3. 检查数据一致性

测试执行方法

  1. 使用仿真工具(如ModelSim、iverilog)运行测试:

    iverilog -o toycpu_tb ToyCPU.v ToyCPU_TB.v
    vvp toycpu_tb
    
  2. 预期输出:

    === 测试1:LOAD指令验证 ===
    通过:R1加载立即数5
    通过:R2初始化为0
    
    === 测试2:ADD运算验证 ===
    通过:累加和应为15(1+2+3+4+5)
    
    === 测试3:JUMP指令验证 ===
    通过:计数器R1应减至0
    
    所有测试通过!
    
  3. 波形分析建议:
    • 监控关键信号:
    ◦ pc(程序计数器)
    ◦ instr(当前指令)
    ◦ regs[1-3](关键寄存器)
    ◦ alu_result(运算结果)
    ◦ zero_flag(零标志位)

扩展测试建议

  1. 边界测试

    // 8位溢出测试
    16'h41FF; // LOAD R1,255
    16'h4201; // LOAD R2,1
    16'h8212; // ADD R2,R2,R1 // 应得0(进位丢失)
    
  2. 逻辑运算测试

    16'h43AA; // LOAD R3,0xAA
    16'h4455; // LOAD R4,0x55
    16'h3434; // AND R3,R3,R4 // 结果应为0x00
    16'h4434; // OR  R4,R3,R4 // 结果应为0xFF
    
  3. 嵌套循环测试

    // 外层循环(R1=3)
    16'h4103;
    // 内层循环(R2=2)
    LOOP_INNER:
    16'h4202;
    16'h1131; // SUB R1,1
    16'hD1FD; // JNZ跳转
    

该测试框架可根据实际指令集扩展,建议使用自动化测试工具实现回归测试,确保CPU功能稳定性。

你可能感兴趣的:(fpga开发)