//=============================================
// 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
指令存储器 (InstructionMemory)
• 存储程序指令的ROM
• 16位指令格式:4位操作码 + 3个3位寄存器地址 + 6位立即数
• 示例程序实现累加计算
寄存器文件 (RegisterFile)
• 包含8个8位通用寄存器
• 支持双端口读取,单端口写入
ALU (算术逻辑单元)
• 支持加法、减法、逻辑与、逻辑或操作
• 输出零标志用于条件跳转
控制单元 (ControlUnit)
• 解析操作码生成控制信号
• 控制数据通路选择和多路复用
数据通路特点
• 单周期设计:所有操作在一个时钟周期完成
• 支持立即数加载和寄存器-寄存器操作
• 简单分支跳转机制
操作码 | 指令 | 功能 |
---|---|---|
0001 | ADD | 寄存器加法 |
0010 | SUB | 寄存器减法 |
0100 | LOAD | 加载立即数到寄存器 |
1000 | JUMP | 条件跳转 |
这个设计可以通过以下方式扩展:
以下是一个针对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
• 测试指令:
0: 16'h4105; // LOAD R1,5
1: 16'h4200; // LOAD R2,0
• 预期结果:
• R1 = 8’h05
• R2 = 8’h00
• 验证方法:监控寄存器文件内容
• 测试程序:
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指令正确性
• 循环控制逻辑
• 寄存器间数据传递
• 测试条件:
• 初始R1=5,经过5次SUB后应为0
• 预期结果:
• R1最终值 = 8’h00
• 程序正确退出循环
• 验证方法:
• 检查零标志触发
• 确认PC跳转逻辑
(需CPU添加STORE指令支持)
// 示例存储指令
16'hA200; // STORE R2, [R0] (假设操作码A为存储)
• 验证步骤:
使用仿真工具(如ModelSim、iverilog)运行测试:
iverilog -o toycpu_tb ToyCPU.v ToyCPU_TB.v
vvp toycpu_tb
预期输出:
=== 测试1:LOAD指令验证 ===
通过:R1加载立即数5
通过:R2初始化为0
=== 测试2:ADD运算验证 ===
通过:累加和应为15(1+2+3+4+5)
=== 测试3:JUMP指令验证 ===
通过:计数器R1应减至0
所有测试通过!
波形分析建议:
• 监控关键信号:
◦ pc(程序计数器)
◦ instr(当前指令)
◦ regs[1-3](关键寄存器)
◦ alu_result(运算结果)
◦ zero_flag(零标志位)
边界测试:
// 8位溢出测试
16'h41FF; // LOAD R1,255
16'h4201; // LOAD R2,1
16'h8212; // ADD R2,R2,R1 // 应得0(进位丢失)
逻辑运算测试:
16'h43AA; // LOAD R3,0xAA
16'h4455; // LOAD R4,0x55
16'h3434; // AND R3,R3,R4 // 结果应为0x00
16'h4434; // OR R4,R3,R4 // 结果应为0xFF
嵌套循环测试:
// 外层循环(R1=3)
16'h4103;
// 内层循环(R2=2)
LOOP_INNER:
16'h4202;
16'h1131; // SUB R1,1
16'hD1FD; // JNZ跳转
该测试框架可根据实际指令集扩展,建议使用自动化测试工具实现回归测试,确保CPU功能稳定性。