1. 项目概述
这个FPGA模型机项目是我在计算机组成原理课程中的期末实践作业,目标是使用Verilog HDL在Quartus II开发环境中设计并实现一个完整的8位模型计算机系统。这个系统包含CPU核心模块、存储器、I/O接口等完整计算机组件,最终在Altera Cyclone系列FPGA开发板上成功运行。
作为计算机专业的学生,通过这个项目我深刻理解了计算机体系结构的底层工作原理。从指令集设计、数据通路构建到控制信号生成,每个环节都需要精心设计和反复调试。特别是将抽象的计算机组成原理转化为可运行的硬件描述语言代码,这个过程让我收获颇丰。
2. 模型机架构设计
2.1 整体结构设计
模型机采用经典的冯·诺依曼架构,主要包含以下核心模块:
- 程序计数器(PC):存储下一条要执行指令的地址
- 指令寄存器(IR):暂存当前执行的指令
- 随机存储器(RAM):存储指令和数据
- 运算单元(AU):执行算术运算
- 通用寄存器组:包含4个8位寄存器(R0-R3)
- 状态寄存器(PSW):存储运算状态标志
- 状态机(SM):控制指令执行周期
- 指令译码器:解析指令操作码
- 控制信号产生模块:生成各部件控制信号
各模块通过内部总线连接,采用同步时钟设计,所有操作在时钟下降沿触发。这种设计平衡了性能和复杂度,非常适合教学目的。
2.2 指令集设计
模型机实现了12条基本指令,覆盖数据传输、算术运算、控制转移等基本操作:
| 指令 | 操作码 | 功能描述 |
|---|---|---|
| MOVA | 0000 | 寄存器间数据传输 |
| MOVB | 0001 | 存储器到寄存器传输 |
| MOVC | 0010 | 寄存器到存储器传输 |
| MOVD | 0011 | 立即数到寄存器传输 |
| ADDI | 0100 | 立即数加法 |
| SUB | 0101 | 寄存器减法 |
| JMP | 0110 | 无条件跳转 |
| JG | 0111 | 大于时跳转 |
| IN | 1000 | 输入操作 |
| OUT | 1001 | 输出操作 |
| MOVI | 1010 | 间接寻址加载 |
| HALT | 1011 | 停机指令 |
每条指令设计为8位编码,高4位为操作码,低4位根据指令类型包含寄存器编号或立即数。这种编码方案在简单性和功能性之间取得了良好平衡。
3. 核心模块实现细节
3.1 程序计数器(PC)模块
PC模块负责跟踪程序执行位置,其Verilog实现如下:
verilog复制module pc(
input clk,
input ld_pc,
input in_pc,
input [7:0] jump_addr,
output reg [7:0] pc_addr
);
always @(negedge clk) begin
if(ld_pc)
pc_addr <= jump_addr;
else if(in_pc)
pc_addr <= pc_addr + 1;
end
endmodule
关键设计要点:
- 使用时钟下降沿触发,确保在时钟周期后半段稳定更新PC值
ld_pc和in_pc信号互斥,由控制模块保证不会同时有效- 跳转地址直接来自指令中的立即数字段
3.2 存储器(RAM)模块
RAM模块使用Quartus II提供的LPM_RAM_IO宏模块实现:
verilog复制module ram(
input clk,
input ram_re,
input ram_wr,
input [7:0] addr,
input [7:0] data_in,
output reg [7:0] data_out
);
// 实例化LPM_RAM_IO
lpm_ram_io ram_inst(
.address(addr),
.inclock(clk),
.outclock(clk),
.memenab(1'b1),
.wren(ram_wr),
.data(data_in),
.q(data_out)
);
endmodule
实际使用中需要注意:
- 存储器需要预先初始化,通过.mif文件加载指令和数据
- 读操作和写操作不能同时进行,需要控制模块协调
- 地址总线宽度决定了可寻址空间(本设计为8位地址,256字节)
3.3 运算单元(AU)设计
AU模块负责执行算术运算,核心代码如下:
verilog复制module au(
input [7:0] a,
input [7:0] b,
input [3:0] opcode,
output reg [7:0] result,
output reg g_flag
);
always @(*) begin
case(opcode)
4'b0000: result = b; // MOVA
4'b0001: result = b; // MOVB
4'b0100: result = a + b; // ADD
4'b0101: begin // SUB
result = a - b;
g_flag = (a > b) ? 1'b1 : 1'b0;
end
4'b1001: result = a; // OUT
default: result = 8'b0;
endcase
end
endmodule
设计考虑:
- 采用组合逻辑设计,运算结果实时更新
- SUB指令会同时更新状态寄存器的G标志
- 输出指令(OUT)只是简单传递数据到总线
4. 控制单元实现
4.1 指令译码器
指令译码器根据指令高4位识别操作类型:
verilog复制module ins_decode(
input en,
input [3:0] ir_high,
output reg mova, movb, movc, movd, add, sub,
output reg jmp, jg, in1, out1, movi, halt
);
always @(*) begin
if(!en) begin
{mova,movb,movc,movd,add,sub,jmp,jg,in1,out1,movi,halt} = 12'b0;
end else begin
case(ir_high)
4'b0000: mova = 1'b1;
4'b0001: movb = 1'b1;
4'b0010: movc = 1'b1;
4'b0011: movd = 1'b1;
4'b0100: add = 1'b1;
4'b0101: sub = 1'b1;
4'b0110: jmp = 1'b1;
4'b0111: jg = 1'b1;
4'b1000: in1 = 1'b1;
4'b1001: out1 = 1'b1;
4'b1010: movi = 1'b1;
4'b1011: halt = 1'b1;
default: {mova,movb,movc,movd,add,sub,jmp,jg,in1,out1,movi,halt} = 12'b0;
endcase
end
end
endmodule
4.2 控制信号生成模块
这是模型机最复杂的部分,负责根据当前指令和状态生成所有控制信号:
verilog复制module con_signal(
input wire mova,movb,movc,movd,add,sub,jmp,jg,g,in1,out1,movi,halt,sm,
input [7:0] ir,
output reg sm_en,ir_ld,ram_re,ram_wr,pc_ld,pc_in,reg_we,au_en,gf_en,in_en,out_en,mux_s,
output reg [1:0] reg_sr,reg_dr,s,
output reg [3:0] au_ac
);
always @(*) begin
au_en = mova | movb | add | sub | out1 ;
sm_en = ~halt;
ir_ld = ~sm;
ram_re = movc | movi | (~sm) ;
ram_wr = movb;
gf_en = sub;
pc_ld = jmp | (jg & g);
pc_in = movi | (~sm);
reg_we = mova | movc | movd | movi | add | sub | in1;
in_en = in1;
reg_dr = ir[3:2];
out_en = out1;
reg_sr = ir[1:0];
if(movb) s = 2'b10;
else if(movc) s = 2'b01;
else s = 2'b00;
au_ac = ir[7:4];
mux_s = mova | movc | movi | add | sub | in1 ;
end
endmodule
控制信号生成采用组合逻辑设计,确保信号能够及时响应指令变化。每个控制信号的生成都有明确的逻辑依据,与指令功能和数据通路设计严格对应。
5. 系统集成与测试
5.1 功能仿真测试
在Quartus II中建立测试平台,编写测试程序验证各指令功能:
verilog复制initial begin
// 测试MOVD R0, 5
#10 ir = 8'b00110000; data_in = 8'd5; sm = 1;
// 测试MOVD R1, 10
#20 ir = 8'b00110101; data_in = 8'd10;
// 测试ADD R0, R1
#20 ir = 8'b01000001;
// 测试OUT R0
#20 ir = 8'b10010000;
// 测试HALT
#20 ir = 8'b10110000;
end
仿真波形应显示:
- R0正确加载立即数5
- R1正确加载立即数10
- ADD指令执行后,R0值变为15
- OUT指令正确输出R0值
- HALT指令停止状态机
5.2 FPGA板级测试
将设计下载到Cyclone IV EP4CE6开发板进行实测:
-
引脚分配:
- 时钟:PIN_23 (50MHz)
- 复位:PIN_24
- 输出显示:PIN_110-PIN_118 (8位LED)
-
测试程序:
assembly复制MOVD R0, 5 ; R0 = 5
MOVD R1, 10 ; R1 = 10
ADD R0, R1 ; R0 = R0 + R1
OUT R0 ; 显示R0
HALT ; 停止
预期结果:开发板上LED显示00001111(15的二进制),验证加法指令正确执行。
6. 数码管显示扩展
6.1 设计原理
为增强模型机的输出能力,我增加了4位共阴数码管显示模块,主要功能:
- 将8位二进制补码转换为有符号十进制显示
- 支持-128到+127的范围显示
- 采用动态扫描技术实现稳定显示
6.2 关键实现代码
verilog复制module comp8_seg_display(
input clk,
input [7:0] comp8,
output reg [3:0] seg_sel,
output [0:7] seg_data
);
// 时钟分频生成1kHz扫描时钟
reg [12:0] cnt;
reg clk_1k;
always @(posedge clk) begin
if(cnt == 5000) begin
cnt <= 0;
clk_1k <= ~clk_1k;
end else begin
cnt <= cnt + 1'b1;
end
end
// 补码转绝对值
reg sign;
reg [7:0] abs_num;
always @(*) begin
sign = comp8[7];
abs_num = sign ? (~comp8 + 1'b1) : comp8;
end
// BCD转换
reg [3:0] hun, ten, one;
always @(*) begin
hun = abs_num / 7'd100;
ten = (abs_num % 7'd100) / 4'd10;
one = abs_num % 4'd10;
end
// 动态扫描驱动
reg [1:0] sel_cnt;
reg [7:0] seg_code;
always @(posedge clk_1k) begin
sel_cnt <= sel_cnt + 2'b01;
end
always @(*) begin
case(sel_cnt)
2'd0: begin // 符号位
seg_sel = 4'b1110;
seg_code = sign ? 8'b00000010 : 8'b00000000;
end
2'd1: begin // 百位
seg_sel = 4'b1101;
seg_code = (hun == 1) ? 8'b01100000 : 8'b00000000;
end
// 十位和个位段码生成...
endcase
end
assign seg_data = seg_code;
endmodule
6.3 实际应用效果
将模型机的输出连接到数码管模块后:
- 正数显示如"+25"
- 负数显示如"-19"
- 动态扫描无闪烁,视觉效果良好
这个扩展不仅提升了模型机的实用性,也让我深入理解了数字系统设计中数据表示和显示驱动的原理。
7. 开发经验与技巧
7.1 Quartus II开发技巧
-
模块化设计:将各功能模块分开实现,通过顶层模块实例化连接。这样便于单独测试和调试。
-
仿真验证:
- 先进行功能仿真验证逻辑正确性
- 通过时序仿真检查建立/保持时间是否满足
- 使用Signal Tap II逻辑分析仪进行在线调试
-
约束文件:
- 正确定义时钟约束
- 合理设置输入输出延迟
- 对关键路径添加额外约束
7.2 FPGA实现注意事项
-
时钟管理:
- 全局时钟网络使用专用时钟引脚
- 跨时钟域信号需要同步处理
- 避免使用门控时钟
-
复位策略:
- 使用异步复位、同步释放
- 复位信号要有足够持续时间
- 避免复位信号毛刺
-
I/O规划:
- 根据板级资源合理分配引脚
- 注意电压标准和驱动能力设置
- 对高速信号使用差分对
7.3 常见问题排查
-
综合后功能不符:
- 检查是否有多驱动
- 查看RTL视图确认电路结构
- 检查是否有未连接的端口
-
时序违规:
- 降低时钟频率测试
- 检查关键路径逻辑
- 考虑流水线设计
-
下载后不工作:
- 确认编程文件正确生成
- 检查电源和时钟是否正常
- 验证复位信号是否有效
通过这个项目,我不仅掌握了Verilog HDL的基本用法,更重要的是理解了计算机各部件如何协同工作。从最初的模块设计到最后的系统集成,每个阶段都遇到了不同的问题,但通过查阅资料、与同学讨论和反复实验,最终都得到了解决。这种从理论到实践的完整经历,让我对计算机组成原理有了更深刻的认识。
