feat(saolei): 新增纯 C 扫雷(MMIO 显示 + 中断读鼠标),扩展 CPU 外设并清理仓库

## 背景

在原有五级流水 CPU 的基础上,用一颗 RISC-V 工具链把"纯 C 写的扫雷"编译成机器码
固化进片内 ROM 运行,并为此把 MMIO 外设从"只有一个 LED 寄存器"扩展成
显示(VGA 显存)+ 七段数码管 + 鼠标事件(含中断)的完整子系统。
原 LED MMIO 的语义保持不变,原有跑马灯/中断/指令回归不受影响。

## 硬件(pipelined-cpu/srcs)

- cpu21_mmio.v:扩展成 MMIO 中枢,新增
  * 七段数码管寄存器(0x1004,8 位十六进制)
  * 鼠标寄存器组(0x1100+):STATUS/DATA/ACK/CTRL/INPUT/TIME/FRAME/EVCNT/LEVEL/MAGIC
    —— 含 16 项事件 FIFO 与 mouse_irq_o(读空自动回 0,不丢事件)
  * 显示控制器(0x1140+):CTRL(光标可见/背景色)、CURSOR(列、行)
  * 显存窗口(0x2000-0x27FF):写端口输出到板级顶层的双口 RAM,读保持组合读
  * LEVEL(0x1120):软件中断寄存器,写 1 拉起 level_irq_o(用作"关卡入口")
  * 地址译码、字节写合并、读数据 mux 全部按原风格实现
- 新增外设 RTL:cpu21_vga.v(640x480@60 按格子扫描,8x8 字模放大 2 倍)、
  cpu21_seg7.v(8 位数码管动态扫描)、cpu21_pointer.v(5 向按键→鼠标事件,1 ms 去抖
  + 事件排队)、cpu21_ps2_mouse.v(可选真鼠标,PS/2 11 位帧接收 + 3 字节包解析)
- cpu21_riscv_redirect_int_bpb.v:新增外设总线端口(显存写/读回、鼠标事件输入、
  中断输出、数码管数据、帧脉冲),例化扩展后的 cpu21_mmio
- cpu21_board_top.v:接入显存双口 RAM + VGA + 数码管 + 鼠标事件源,中断接线为
  irq_i = {SW2, LEVEL|SW1, 鼠标|SW0};ROM 宏切换(定义 SAOLEI_ROM 加载扫雷程序);
  ROM_WORDS 1024→8192
- cpu21_board_top.xdc:新增按键、SW[3]、8 位数码管、VGA、PS/2 的全部引脚约束
- tb_board_top.v:新端口显式接 0/1,避免悬空影响原跑马灯回归
- create_project.tcl:把 cpu21_saolei_rom.vh 按 Verilog Header 条件加入

## 软件(saolei/)

- sw/mmio.h:全部 MMIO 地址与格子编码(换地址只改这一个文件)
- sw/saolei.c:纯 C 扫雷 —— 背景"伪操作系统" main()(开始界面)+ 关卡 level_main()
  (在 IRQ1 里跑完整局)+ 棋盘位图/布雷(首点安全)/BFS 展开/插旗/胜负判定/逐格绘制
- sw/isr.c:**中断读鼠标的代码**。三个固定地址入口:
  * isr1_entry(.text.isr1=0x100)关卡入口:保存全部调用者保存寄存器 +
    csrrsi 打开 MIE(让鼠标中断可嵌套)→ level_main
  * isr2_entry(.text.isr2=0x200)鼠标读取:只存 ra → 纯 C 主体 isr_body
  * isr3_entry(.text.isr3=0x300)备用:直接 uret
- sw/start.c:设 sp、清 .bss、进 main(只有这一处必要内联汇编)
- sw/linker.ld:复位 0x0、三个中断入口 0x100/0x200/0x300、栈顶 0x0FFC;
  用 ASSERT 禁止 .data/.rodata(这颗核读不到 ROM 里的数据)

## 构建与工具链适配(saolei/tools)

- build.ps1:一键 C → .o → ELF → .hex + cpu21_saolei_rom.vh,任一步失败立刻停止
- check_isa.py:用"指令白名单"逐条检查反汇编,出现本核未实现的指令即报错并给修复提示
- elf2hex.py:裸二进制 → $readmemh hex + ROM 初始化头文件
- 三处针对本核的关键配置(都已写进脚本注释):
  1) -march=rv32im_zicsr(GCC 15 起 rv32im 不含 Zicsr,csrrsi 会报错)
  2) 不能禁链接器松弛:gcc 函数调用默认是 auipc+jalr,靠松弛压成 jal,
     禁掉后 35 条 auipc 会留在代码里(本核没有 auipc);
     用 -msmall-data-limit=0 防止松弛引入 gp 相对寻址
  3) 中断入口地址必须写在段声明上(.text.isr1 0x100 : ...),
     只写 ". = 0x100;" 会被 ld 忽略(实测 isr1 掉到了 0x14)
- 实测结果:text=3368B、data=0、bss=1436B,842 条指令全部通过 ISA 检查,
  入口地址 _start=0x0 / isr1_entry=0x100 / isr2_entry=0x200 / isr3_entry=0x300

## 仓库清理

- 删除被工程取代的 saolei/hw、saolei/constrs(外设已并入 srcs/rtl)
- 删除全部 Vivado 生成物:.xpr、.runs、.sim、.cache、.hw、.ip_user_files、.Xil、
  vivado.jou/log —— 需要工程时用 source create_project.tcl 一键重建(约 0.3 MB 源文件)
- .gitignore 增加 saolei/build/(构建产物可一键重建)

## 文档

- README:更新目录结构、仓库约定(不放生成物)、MMIO 地址表、文件与模块划分表、
  上板说明,新增"扫雷程序(saolei/)"章节
- saolei/README.md:重写 —— 程序结构(背景/关卡/鼠标 + 三个中断入口)、
  本核指令子集与对应写法、构建步骤与实测数据、MMIO 地址表、上板/仿真步骤、
  操作说明、验证状态与已知可改进项

## 验证状态(如实说明)

- 已实测:C 工具链链路(编译/链接/ISA 检查/hex 生成/入口地址逐条核对)
- 未验证:所有 RTL 未经综合与仿真(本机无 Vivado),C 程序也未在仿真器/硬件上运行过;
  建议先跑 tools/run_regress.ps1 确认原回归仍全 PASS,再用 SAOLEI_ROM 宏做行为仿真
This commit is contained in:
2026-09-16 20:11:00 +08:00
commit f50639e90f
66 changed files with 19733 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+365
View File
@@ -0,0 +1,365 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 上板顶层(Nexys4DDR,xc7a100tcsg324-1)
//
// 处理器顶层 cpu21_riscv_redirect_int_bpb 暴露了 775 位调试端口(各级 PC/IR、
// 计数器、BPB/中断观测等),直接作为实现顶层会因 I/O 超限而无法布局布线
// ([Place 30-415] IO Placement failed due to overutilization)。本模块只把
// 开发板真正需要的外设引出来,其余调试端口不连接,因此不占用引脚:
//
// clk100 (E3) -> CPU 时钟(100 MHz,经 CLK_DIV 分频后给 CPU)
// rst_n (C12) -> 复位按键 CPU_RESETN,低有效,经复位同步器
// go (N17) -> 按键 BTNC:ecall 停机后按下继续运行
// irq_sw[2:0] -> 拨码开关 SW[2:0]:3 路中断源
// led[15:0] -> 板载 LED[15:0]:显示 MMIO(LED_ADDR)写入值的低 16 位
// led_we (R12) -> LED16_B:有 MMIO 写入时亮,并拉长约 0.34 s 便于观察
//
// 片内存储器:
// ROM:ROM_WORDS 字,组合读,内容由 `include "cpu21_board_rom.vh"` 给出
// (该文件由 tools/hex2vh.py 从 testbench/programs/*.hex 生成);
// RAM:RAM_WORDS 字,字节写使能 + 组合读;只响应地址 < 2^(RAM_AW+2),
// 因此 MMIO 地址不会落进 RAM(避免与 LED_ADDR 别名)。
//
// 注意:ROM_WORDS / RAM_WORDS 需为 2 的幂(默认 1024,对应地址位 [11:2])。
// -----------------------------------------------------------------------------
module cpu21_board_top #(
parameter [31:0] LED_ADDR = 32'h0000_1000,
parameter ROM_WORDS = 8192,
parameter RAM_WORDS = 1024,
parameter [31:0] USTATUS_INIT = 32'h0000_0001,
// 板载 100 MHz 的分频比(1 = 直用 100 MHz,2 = 50 MHz,…)。
// 乘除法改成多周期单元后,原来 ~94 ns 的组合乘法/除法已经不在关键路径上,
// 实测(xc7a100tcsg324-1):
// CLK_DIV=1(100 MHz):WNS = -1.1 ns,违例路径是 EX 级的
// 分支判决/中断保存 → BPB 更新,与乘除法无关(逻辑只占 ~3 ns,
// 7~8 ns 都是布线);
// CLK_DIV=2( 50 MHz):WNS ≈ +8 ns,余量充足。
// 因此默认取 2,既比原来的 16 分频快 8 倍,又能保证时序收敛。
parameter integer CLK_DIV = 2,
// 鼠标事件源:0 = 板上 5 向按键(免外设,默认);1 = PS/2 鼠标(Pmod JA)
parameter integer USE_PS2_MOUSE = 0
) (
input wire clk100, // E3
input wire rst_n, // C12:CPU_RESETN,低有效
input wire go, // N17:BTNC(同时用作鼠标左键)
input wire [ 2:0] irq_sw, // SW[2:0]:原来的 3 路中断源(保持兼容)
input wire btnu, // M18:光标上
input wire btnd, // P18:光标下
input wire btnl, // P17:光标左
input wire btnr, // M17:光标右
input wire sw_flag, // R15:SW[3],插旗模式(软件读 INPUT 寄存器)
output wire [15:0] led, // LED[15:0]
output wire led_we, // LED16_B:MMIO 写入指示
// 七段数码管(共阳极:位选/段选都是低有效)
output wire [ 7:0] an,
output wire [ 6:0] seg,
output wire dp,
// VGA
output wire [ 3:0] vga_r,
output wire [ 3:0] vga_g,
output wire [ 3:0] vga_b,
output wire vga_hs,
output wire vga_vs,
// PS/2(只有 USE_PS2_MOUSE=1 时才用)
input wire ps2_clk,
input wire ps2_data
);
localparam ROM_AW = $clog2(ROM_WORDS);
localparam RAM_AW = $clog2(RAM_WORDS);
wire arst = ~rst_n; // 异步复位(高有效)
// ---------------------------------------------------------------------------
// 时钟:100 MHz / CLK_DIV(默认 2 = 50 MHz)给 CPU。
//
// 乘除法已经是 cpu21_riscv_muldiv 里的多周期单元,关键路径上不再有组合的
// 32x32 乘法/除法;剩下的违例路径是 EX 级“分支判决/中断保存 → BPB 更新”,
// 与乘除法无关,实测 100 MHz 下 WNS = -1.1 ns,50 MHz 下余量约 +8 ns。
// 分频后的时钟由 Vivado 自动插 BUFG;XDC 里用 create_generated_clock 声明,
// 改分频比时两处要一起改。
// ---------------------------------------------------------------------------
reg [15:0] clk_cnt;
reg clk_div;
always @(posedge clk100 or posedge arst) begin
if (arst) begin
clk_cnt <= 16'd0;
clk_div <= 1'b0;
end
else if (clk_cnt == (CLK_DIV / 2 - 1)) begin
clk_cnt <= 16'd0;
clk_div <= ~clk_div;
end
else begin
clk_cnt <= clk_cnt + 16'd1;
end
end
wire clk_cpu = clk_div;
// ---------------------------------------------------------------------------
// 复位同步器:按键异步置位、释放与 CPU 时钟同步
// ---------------------------------------------------------------------------
reg [2:0] rst_sync_q;
always @(posedge clk_cpu or posedge arst) begin
if (arst)
rst_sync_q <= 3'b111;
else
rst_sync_q <= {rst_sync_q[1:0], 1'b0};
end
wire reset = rst_sync_q[2];
// CPU 接口连线(先声明:后面的 ROM/RAM 逻辑与例化都要用到)
wire [31:0] instr_addr;
wire [31:0] data_addr;
wire [31:0] data_wdata;
wire [ 3:0] data_wstrb;
wire data_we;
wire [31:0] led_data;
wire led_valid;
// 扫雷扩展外设的连线(先声明,例化在下面)
wire [31:0] disp_ctrl, disp_cursor; // 显示控制器(CPU 内部 MMIO 驱动)
wire [31:0] seg_data; // 数码管:8 位十六进制
wire seg_valid;
wire fb_we; // 显存写端口
wire [ 9:0] fb_waddr;
wire [31:0] fb_wdata;
wire [ 3:0] fb_wstrb;
wire [ 9:0] fb_raddr; // 显存读端口(VGA)
wire mouse_irq, level_irq; // 鼠标事件中断 / 软件中断(关卡入口)
wire frame_tick;
wire [15:0] sw_vec = {12'b0, sw_flag, irq_sw};
// 中断连线:SW0 与鼠标共用一个入口,SW1 与"关卡入口"共用一个入口。
wire [ 2:0] irq_vec = {irq_sw[2], (level_irq | irq_sw[1]), (mouse_irq | irq_sw[0])};
for (i = 0; i < ROM_WORDS; i = i + 1)
rom[i] = 32'h0000_0013; // NOP 填充
`ifdef SAOLEI_ROM
// 扫雷程序(由 saolei/tools/build.ps1 生成的 cpu21_saolei_rom.vh)。
// 综合/仿真时定义宏 SAOLEI_ROM 即可切换到这个程序;不定义就是下面的跑马灯。
`include "cpu21_saolei_rom.vh"
`elsif BOARD_ROM_FAST
// 仿真专用:把演示程序的延时改小的版本(综合流程不会用到)
`include "cpu21_board_rom_fast.vh"
`else
`include "cpu21_board_rom.vh"
`endif
end
// ---------------------------------------------------------------------------
// 数据 RAM:字节写使能 + 组合读
// ---------------------------------------------------------------------------
reg [31:0] ram[0:RAM_WORDS-1];
integer j;
initial begin
for (j = 0; j < RAM_WORDS; j = j + 1)
ram[j] = 32'b0;
end
wire [ROM_AW+1:2] rom_index = instr_addr[ROM_AW+1:2];
wire [RAM_AW+1:2] ram_index = data_addr[RAM_AW+1:2];
wire ram_sel = (data_addr[31:RAM_AW+2] == 0);
wire [31:0] instr_word = rom[rom_index];
wire [31:0] data_rdata = ram_sel ? ram[ram_index] : 32'b0;
always @(posedge clk_cpu) begin
if (ram_sel && data_we) begin
if (data_wstrb[0])
ram[ram_index][7:0] <= data_wdata[7:0];
if (data_wstrb[1])
ram[ram_index][15:8] <= data_wdata[15:8];
if (data_wstrb[2])
ram[ram_index][23:16] <= data_wdata[23:16];
if (data_wstrb[3])
ram[ram_index][31:24] <= data_wdata[31:24];
end
end
// ---------------------------------------------------------------------------
// 处理器:只连接上板需要的端口,调试端口留空(不产生 I/O)
// ---------------------------------------------------------------------------
cpu21_riscv_redirect_int_bpb #(
.LED_ADDR (LED_ADDR),
.USTATUS_INIT(USTATUS_INIT),
// 三个中断入口:必须与 sw/linker.ld 里
// .text.isr1/2/3 的地址一致
// IRQ1(0x100) = 关卡(游戏)入口
// IRQ2(0x200) = 鼠标读取
// IRQ3(0x300) = 备用
.IRQ1_VECTOR (32'h0000_0100),
.IRQ2_VECTOR (32'h0000_0200),
.IRQ3_VECTOR (32'h0000_0300)
) u_cpu (
.clk (clk_cpu),
.reset (reset),
.instr_i (instr_word),
.data_rdata_i(data_rdata),
.irq_i (irq_vec),
.go_i (go),
.instr_addr_o(instr_addr),
.data_addr_o (data_addr),
.data_wdata_o(data_wdata),
.data_wstrb_o(data_wstrb),
.data_we_o (data_we),
.led_data_o (led_data),
.led_valid_o(led_valid),
// 扫雷扩展外设
.seg_data_o (seg_data),
.seg_valid_o (seg_valid),
.fb_we_o (fb_we),
.fb_waddr_o (fb_waddr),
.fb_wdata_o (fb_wdata),
.fb_wstrb_o (fb_wstrb),
.fb_rdata_i (fb_rdata),
.disp_ctrl_o (disp_ctrl),
.disp_cursor_o (disp_cursor),
.mouse_ev_valid_i(ev_valid),
.mouse_dx_i (ev_dx),
.mouse_dy_i (ev_dy),
.mouse_btn_i (ev_btn),
.sw_i (sw_vec),
.frame_tick_i (frame_tick),
.mouse_irq_o (mouse_irq),
.level_irq_o (level_irq)
);
// ---------------------------------------------------------------------------
// 板载 LED:显示 MMIO 写入值的低 16 位;写入指示拉长便于观察
// ---------------------------------------------------------------------------
assign led = led_data[15:0];
reg [23:0] led_we_cnt;
always @(posedge clk_cpu or posedge arst) begin
if (arst)
led_we_cnt <= 24'd0;
else if (led_valid)
led_we_cnt <= 24'hFF_FFFF; // 约 0.34 s @50 MHz
else if (led_we_cnt != 24'd0)
led_we_cnt <= led_we_cnt - 24'd1;
end
assign led_we = (led_we_cnt != 24'd0);
// ===========================================================================
// 扫雷外设:显存 + VGA + 七段数码管 + 鼠标事件源 + 中断连线
//
// 显存是一块 1200 字节的双口 RAM(40x30 格,每格 1 字节):CPU 通过 MMIO 的
// fb_* 写端口写入,VGA 用另一个读端口组合读出。两者同处 clk_cpu 域(VGA 的
// 25 MHz 像素节拍由使能信号产生,不是独立时钟),因此没有跨时钟域问题。
// ===========================================================================
localparam integer FB_WORDS = 300; // 300 字 = 1200 字节
// ---- 显存 ----
reg [31:0] fb [0:FB_WORDS-1];
integer k;
initial begin
for (k = 0; k < FB_WORDS; k = k + 1)
fb[k] = 32'b0;
end
always @(posedge clk_cpu) begin
if (fb_we) begin
if (fb_wstrb[0])
fb[fb_waddr][7:0] <= fb_wdata[7:0];
if (fb_wstrb[1])
fb[fb_waddr][15:8] <= fb_wdata[15:8];
if (fb_wstrb[2])
fb[fb_waddr][23:16] <= fb_wdata[23:16];
if (fb_wstrb[3])
fb[fb_waddr][31:24] <= fb_wdata[31:24];
end
end
wire [31:0] fb_rdata = fb[fb_raddr];
// ---- VGA 像素节拍:clk_cpu 二分频 => 25 MHz ----
reg px_q;
always @(posedge clk_cpu or posedge arst) begin
if (arst)
px_q <= 1'b0;
else
px_q <= ~px_q;
end
wire px_tick = ~px_q;
cpu21_vga u_vga (
.clk (clk_cpu),
.reset (reset),
.tick (px_tick),
.fb_raddr_o (fb_raddr),
.fb_word_i (fb_rdata),
.ctrl_i (disp_ctrl),
.cursor_i (disp_cursor),
.vga_r (vga_r),
.vga_g (vga_g),
.vga_b (vga_b),
Tcl 一键重建
ga_hs (vga_hs),
.vga_vs (vga_vs),
.frame_tick_o(frame_tick)
);
// ---- 七段数码管(显示项目程序写的 8 位十六进制数)----
cpu21_seg7 u_seg7 (
.clk (clk_cpu),
.reset (reset),
.seg_data_i(seg_data),
.an_en_i (8'hFF),
.an_o (an),
.seg_o (seg),
.dp_o (dp)
);
// ---- 鼠标事件源 ----
wire ev_valid;
wire [7:0] ev_dx, ev_dy;
wire [4:0] ev_btn;
wire [4:0] btn_vec = {go, btnu, btnd, btnl, btnr}; // {BTNC, BTNU, BTND, BTNL, BTNR}
generate
if (USE_PS2_MOUSE != 0) begin : g_ps2
cpu21_ps2_mouse u_ps2 (
.clk (clk_cpu),
.reset (reset),
.ps2_clk_i (ps2_clk),
.ps2_clk_oe_o (),
.ps2_data_i (ps2_data),
.ps2_data_oe_o(),
.ev_valid_o (ev_valid),
.ev_dx_o (ev_dx),
.ev_dy_o (ev_dy),
.ev_btn_o (ev_btn),
.rx_count_o ()
)
;
end
else begin : g_btn
cpu21_pointer #(
.SAMPLE_DIV(50000)
) u_ptr (
.clk (clk_cpu),
.reset (reset),
.btn_i (btn_vec),
.ev_valid_o(ev_valid),
.ev_dx_o (ev_dx),
.ev_dy_o (ev_dy),
.ev_btn_o (ev_btn)
);
end
endgenerate
endmodule
`default_nettype wire
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,265 @@
## This file is a general .xdc for the Nexys4 DDR Rev. C
## To use it in a project:
## - uncomment the lines corresponding to used pins
## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project
## Clock signal
#set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_35 Sch=clk100mhz
#create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports {CLK100MHZ}];
##Switches
#set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { SW[0] }]; #IO_L24N_T3_RS0_15 Sch=sw[0]
#set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { SW[1] }]; #IO_L3N_T0_DQS_EMCCLK_14 Sch=sw[1]
#set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { SW[2] }]; #IO_L6N_T0_D08_VREF_14 Sch=sw[2]
#set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { SW[3] }]; #IO_L13N_T2_MRCC_14 Sch=sw[3]
#set_property -dict { PACKAGE_PIN R17 IOSTANDARD LVCMOS33 } [get_ports { SW[4] }]; #IO_L12N_T1_MRCC_14 Sch=sw[4]
#set_property -dict { PACKAGE_PIN T18 IOSTANDARD LVCMOS33 } [get_ports { SW[5] }]; #IO_L7N_T1_D10_14 Sch=sw[5]
#set_property -dict { PACKAGE_PIN U18 IOSTANDARD LVCMOS33 } [get_ports { SW[6] }]; #IO_L17N_T2_A13_D29_14 Sch=sw[6]
#set_property -dict { PACKAGE_PIN R13 IOSTANDARD LVCMOS33 } [get_ports { SW[7] }]; #IO_L5N_T0_D07_14 Sch=sw[7]
#set_property -dict { PACKAGE_PIN T8 IOSTANDARD LVCMOS18 } [get_ports { SW[8] }]; #IO_L24N_T3_34 Sch=sw[8]
#set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS18 } [get_ports { SW[9] }]; #IO_25_34 Sch=sw[9]
#set_property -dict { PACKAGE_PIN R16 IOSTANDARD LVCMOS33 } [get_ports { SW[10] }]; #IO_L15P_T2_DQS_RDWR_B_14 Sch=sw[10]
#set_property -dict { PACKAGE_PIN T13 IOSTANDARD LVCMOS33 } [get_ports { SW[11] }]; #IO_L23P_T3_A03_D19_14 Sch=sw[11]
#set_property -dict { PACKAGE_PIN H6 IOSTANDARD LVCMOS33 } [get_ports { SW[12] }]; #IO_L24P_T3_35 Sch=sw[12]
#set_property -dict { PACKAGE_PIN U12 IOSTANDARD LVCMOS33 } [get_ports { SW[13] }]; #IO_L20P_T3_A08_D24_14 Sch=sw[13]
#set_property -dict { PACKAGE_PIN U11 IOSTANDARD LVCMOS33 } [get_ports { SW[14] }]; #IO_L19N_T3_A09_D25_VREF_14 Sch=sw[14]
#set_property -dict { PACKAGE_PIN V10 IOSTANDARD LVCMOS33 } [get_ports { SW[15] }]; #IO_L21P_T3_DQS_14 Sch=sw[15]
## LEDs
#set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { LED[0] }]; #IO_L18P_T2_A24_15 Sch=led[0]
#set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { LED[1] }]; #IO_L24P_T3_RS1_15 Sch=led[1]
#set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { LED[2] }]; #IO_L17N_T2_A25_15 Sch=led[2]
#set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { LED[3] }]; #IO_L8P_T1_D11_14 Sch=led[3]
#set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { LED[4] }]; #IO_L7P_T1_D09_14 Sch=led[4]
#set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { LED[5] }]; #IO_L18N_T2_A11_D27_14 Sch=led[5]
#set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { LED[6] }]; #IO_L17P_T2_A14_D30_14 Sch=led[6]
#set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { LED[7] }]; #IO_L18P_T2_A12_D28_14 Sch=led[7]
#set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { LED[8] }]; #IO_L16N_T2_A15_D31_14 Sch=led[8]
#set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { LED[9] }]; #IO_L14N_T2_SRCC_14 Sch=led[9]
#set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { LED[10] }]; #IO_L22P_T3_A05_D21_14 Sch=led[10]
#set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { LED[11] }]; #IO_L15N_T2_DQS_DOUT_CSO_B_14 Sch=led[11]
#set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { LED[12] }]; #IO_L16P_T2_CSI_B_14 Sch=led[12]
#set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { LED[13] }]; #IO_L22N_T3_A04_D20_14 Sch=led[13]
#set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { LED[14] }]; #IO_L20N_T3_A07_D23_14 Sch=led[14]
#set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { LED[15] }]; #IO_L21N_T3_DQS_A06_D22_14 Sch=led[15]
#set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { LED16_B }]; #IO_L5P_T0_D06_14 Sch=led16_b
#set_property -dict { PACKAGE_PIN M16 IOSTANDARD LVCMOS33 } [get_ports { LED16_G }]; #IO_L10P_T1_D14_14 Sch=led16_g
#set_property -dict { PACKAGE_PIN N15 IOSTANDARD LVCMOS33 } [get_ports { LED16_R }]; #IO_L11P_T1_SRCC_14 Sch=led16_r
#set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { LED17_B }]; #IO_L15N_T2_DQS_ADV_B_15 Sch=led17_b
#set_property -dict { PACKAGE_PIN R11 IOSTANDARD LVCMOS33 } [get_ports { LED17_G }]; #IO_0_14 Sch=led17_g
#set_property -dict { PACKAGE_PIN N16 IOSTANDARD LVCMOS33 } [get_ports { LED17_R }]; #IO_L11N_T1_SRCC_14 Sch=led17_r
##7 segment display
#set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { CA }]; #IO_L24N_T3_A00_D16_14 Sch=ca
#set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { CB }]; #IO_25_14 Sch=cb
#set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { CC }]; #IO_25_15 Sch=cc
#set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { CD }]; #IO_L17P_T2_A26_15 Sch=cd
#set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { CE }]; #IO_L13P_T2_MRCC_14 Sch=ce
#set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { CF }]; #IO_L19P_T3_A10_D26_14 Sch=cf
#set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { CG }]; #IO_L4P_T0_D04_14 Sch=cg
#set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { DP }]; #IO_L19N_T3_A21_VREF_15 Sch=dp
#set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { AN[0] }]; #IO_L23P_T3_FOE_B_15 Sch=an[0]
#set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { AN[1] }]; #IO_L23N_T3_FWE_B_15 Sch=an[1]
#set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { AN[2] }]; #IO_L24P_T3_A01_D17_14 Sch=an[2]
#set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { AN[3] }]; #IO_L19P_T3_A22_15 Sch=an[3]
#set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { AN[4] }]; #IO_L8N_T1_D12_14 Sch=an[4]
#set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { AN[5] }]; #IO_L14P_T2_SRCC_14 Sch=an[5]
#set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { AN[6] }]; #IO_L23P_T3_35 Sch=an[6]
#set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { AN[7] }]; #IO_L23N_T3_A02_D18_14 Sch=an[7]
##Buttons
#set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { CPU_RESETN }]; #IO_L3P_T0_DQS_AD1P_15 Sch=cpu_resetn
#set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { BTNC }]; #IO_L9P_T1_DQS_14 Sch=btnc
#set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { BTNU }]; #IO_L4N_T0_D05_14 Sch=btnu
#set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { BTNL }]; #IO_L12P_T1_MRCC_14 Sch=btnl
#set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { BTNR }]; #IO_L10N_T1_D15_14 Sch=btnr
#set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { BTND }]; #IO_L9N_T1_DQS_D13_14 Sch=btnd
##Pmod Headers
##Pmod Header JA
#set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 } [get_ports { JA[1] }]; #IO_L20N_T3_A19_15 Sch=ja[1]
#set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { JA[2] }]; #IO_L21N_T3_DQS_A18_15 Sch=ja[2]
#set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { JA[3] }]; #IO_L21P_T3_DQS_15 Sch=ja[3]
#set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { JA[4] }]; #IO_L18N_T2_A23_15 Sch=ja[4]
#set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { JA[7] }]; #IO_L16N_T2_A27_15 Sch=ja[7]
#set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { JA[8] }]; #IO_L16P_T2_A28_15 Sch=ja[8]
#set_property -dict { PACKAGE_PIN F18 IOSTANDARD LVCMOS33 } [get_ports { JA[9] }]; #IO_L22N_T3_A16_15 Sch=ja[9]
#set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { JA[10] }]; #IO_L22P_T3_A17_15 Sch=ja[10]
##Pmod Header JB
#set_property -dict { PACKAGE_PIN D14 IOSTANDARD LVCMOS33 } [get_ports { JB[1] }]; #IO_L1P_T0_AD0P_15 Sch=jb[1]
#set_property -dict { PACKAGE_PIN F16 IOSTANDARD LVCMOS33 } [get_ports { JB[2] }]; #IO_L14N_T2_SRCC_15 Sch=jb[2]
#set_property -dict { PACKAGE_PIN G16 IOSTANDARD LVCMOS33 } [get_ports { JB[3] }]; #IO_L13N_T2_MRCC_15 Sch=jb[3]
#set_property -dict { PACKAGE_PIN H14 IOSTANDARD LVCMOS33 } [get_ports { JB[4] }]; #IO_L15P_T2_DQS_15 Sch=jb[4]
#set_property -dict { PACKAGE_PIN E16 IOSTANDARD LVCMOS33 } [get_ports { JB[7] }]; #IO_L11N_T1_SRCC_15 Sch=jb[7]
#set_property -dict { PACKAGE_PIN F13 IOSTANDARD LVCMOS33 } [get_ports { JB[8] }]; #IO_L5P_T0_AD9P_15 Sch=jb[8]
#set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { JB[9] }]; #IO_0_15 Sch=jb[9]
#set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { JB[10] }]; #IO_L13P_T2_MRCC_15 Sch=jb[10]
##Pmod Header JC
#set_property -dict { PACKAGE_PIN K1 IOSTANDARD LVCMOS33 } [get_ports { JC[1] }]; #IO_L23N_T3_35 Sch=jc[1]
#set_property -dict { PACKAGE_PIN F6 IOSTANDARD LVCMOS33 } [get_ports { JC[2] }]; #IO_L19N_T3_VREF_35 Sch=jc[2]
#set_property -dict { PACKAGE_PIN J2 IOSTANDARD LVCMOS33 } [get_ports { JC[3] }]; #IO_L22N_T3_35 Sch=jc[3]
#set_property -dict { PACKAGE_PIN G6 IOSTANDARD LVCMOS33 } [get_ports { JC[4] }]; #IO_L19P_T3_35 Sch=jc[4]
#set_property -dict { PACKAGE_PIN E7 IOSTANDARD LVCMOS33 } [get_ports { JC[7] }]; #IO_L6P_T0_35 Sch=jc[7]
#set_property -dict { PACKAGE_PIN J3 IOSTANDARD LVCMOS33 } [get_ports { JC[8] }]; #IO_L22P_T3_35 Sch=jc[8]
#set_property -dict { PACKAGE_PIN J4 IOSTANDARD LVCMOS33 } [get_ports { JC[9] }]; #IO_L21P_T3_DQS_35 Sch=jc[9]
#set_property -dict { PACKAGE_PIN E6 IOSTANDARD LVCMOS33 } [get_ports { JC[10] }]; #IO_L5P_T0_AD13P_35 Sch=jc[10]
##Pmod Header JD
#set_property -dict { PACKAGE_PIN H4 IOSTANDARD LVCMOS33 } [get_ports { JD[1] }]; #IO_L21N_T3_DQS_35 Sch=jd[1]
#set_property -dict { PACKAGE_PIN H1 IOSTANDARD LVCMOS33 } [get_ports { JD[2] }]; #IO_L17P_T2_35 Sch=jd[2]
#set_property -dict { PACKAGE_PIN G1 IOSTANDARD LVCMOS33 } [get_ports { JD[3] }]; #IO_L17N_T2_35 Sch=jd[3]
#set_property -dict { PACKAGE_PIN G3 IOSTANDARD LVCMOS33 } [get_ports { JD[4] }]; #IO_L20N_T3_35 Sch=jd[4]
#set_property -dict { PACKAGE_PIN H2 IOSTANDARD LVCMOS33 } [get_ports { JD[7] }]; #IO_L15P_T2_DQS_35 Sch=jd[7]
#set_property -dict { PACKAGE_PIN G4 IOSTANDARD LVCMOS33 } [get_ports { JD[8] }]; #IO_L20P_T3_35 Sch=jd[8]
#set_property -dict { PACKAGE_PIN G2 IOSTANDARD LVCMOS33 } [get_ports { JD[9] }]; #IO_L15N_T2_DQS_35 Sch=jd[9]
#set_property -dict { PACKAGE_PIN F3 IOSTANDARD LVCMOS33 } [get_ports { JD[10] }]; #IO_L13N_T2_MRCC_35 Sch=jd[10]
##Pmod Header JXADC
#set_property -dict { PACKAGE_PIN A14 IOSTANDARD LVDS } [get_ports { XA_N[1] }]; #IO_L9N_T1_DQS_AD3N_15 Sch=xa_n[1]
#set_property -dict { PACKAGE_PIN A13 IOSTANDARD LVDS } [get_ports { XA_P[1] }]; #IO_L9P_T1_DQS_AD3P_15 Sch=xa_p[1]
#set_property -dict { PACKAGE_PIN A16 IOSTANDARD LVDS } [get_ports { XA_N[2] }]; #IO_L8N_T1_AD10N_15 Sch=xa_n[2]
#set_property -dict { PACKAGE_PIN A15 IOSTANDARD LVDS } [get_ports { XA_P[2] }]; #IO_L8P_T1_AD10P_15 Sch=xa_p[2]
#set_property -dict { PACKAGE_PIN B17 IOSTANDARD LVDS } [get_ports { XA_N[3] }]; #IO_L7N_T1_AD2N_15 Sch=xa_n[3]
#set_property -dict { PACKAGE_PIN B16 IOSTANDARD LVDS } [get_ports { XA_P[3] }]; #IO_L7P_T1_AD2P_15 Sch=xa_p[3]
#set_property -dict { PACKAGE_PIN A18 IOSTANDARD LVDS } [get_ports { XA_N[4] }]; #IO_L10N_T1_AD11N_15 Sch=xa_n[4]
#set_property -dict { PACKAGE_PIN B18 IOSTANDARD LVDS } [get_ports { XA_P[4] }]; #IO_L10P_T1_AD11P_15 Sch=xa_p[4]
##VGA Connector
#set_property -dict { PACKAGE_PIN A3 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[0] }]; #IO_L8N_T1_AD14N_35 Sch=vga_r[0]
#set_property -dict { PACKAGE_PIN B4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[1] }]; #IO_L7N_T1_AD6N_35 Sch=vga_r[1]
#set_property -dict { PACKAGE_PIN C5 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[2] }]; #IO_L1N_T0_AD4N_35 Sch=vga_r[2]
#set_property -dict { PACKAGE_PIN A4 IOSTANDARD LVCMOS33 } [get_ports { VGA_R[3] }]; #IO_L8P_T1_AD14P_35 Sch=vga_r[3]
#set_property -dict { PACKAGE_PIN C6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[0] }]; #IO_L1P_T0_AD4P_35 Sch=vga_g[0]
#set_property -dict { PACKAGE_PIN A5 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[1] }]; #IO_L3N_T0_DQS_AD5N_35 Sch=vga_g[1]
#set_property -dict { PACKAGE_PIN B6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[2] }]; #IO_L2N_T0_AD12N_35 Sch=vga_g[2]
#set_property -dict { PACKAGE_PIN A6 IOSTANDARD LVCMOS33 } [get_ports { VGA_G[3] }]; #IO_L3P_T0_DQS_AD5P_35 Sch=vga_g[3]
#set_property -dict { PACKAGE_PIN B7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[0] }]; #IO_L2P_T0_AD12P_35 Sch=vga_b[0]
#set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[1] }]; #IO_L4N_T0_35 Sch=vga_b[1]
#set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[2] }]; #IO_L6N_T0_VREF_35 Sch=vga_b[2]
#set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 } [get_ports { VGA_B[3] }]; #IO_L4P_T0_35 Sch=vga_b[3]
#set_property -dict { PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports { VGA_HS }]; #IO_L4P_T0_15 Sch=vga_hs
#set_property -dict { PACKAGE_PIN B12 IOSTANDARD LVCMOS33 } [get_ports { VGA_VS }]; #IO_L3N_T0_DQS_AD1N_15 Sch=vga_vs
##Micro SD Connector
#set_property -dict { PACKAGE_PIN E2 IOSTANDARD LVCMOS33 } [get_ports { SD_RESET }]; #IO_L14P_T2_SRCC_35 Sch=sd_reset
#set_property -dict { PACKAGE_PIN A1 IOSTANDARD LVCMOS33 } [get_ports { SD_CD }]; #IO_L9N_T1_DQS_AD7N_35 Sch=sd_cd
#set_property -dict { PACKAGE_PIN B1 IOSTANDARD LVCMOS33 } [get_ports { SD_SCK }]; #IO_L9P_T1_DQS_AD7P_35 Sch=sd_sck
#set_property -dict { PACKAGE_PIN C1 IOSTANDARD LVCMOS33 } [get_ports { SD_CMD }]; #IO_L16N_T2_35 Sch=sd_cmd
#set_property -dict { PACKAGE_PIN C2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[0] }]; #IO_L16P_T2_35 Sch=sd_dat[0]
#set_property -dict { PACKAGE_PIN E1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[1] }]; #IO_L18N_T2_35 Sch=sd_dat[1]
#set_property -dict { PACKAGE_PIN F1 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[2] }]; #IO_L18P_T2_35 Sch=sd_dat[2]
#set_property -dict { PACKAGE_PIN D2 IOSTANDARD LVCMOS33 } [get_ports { SD_DAT[3] }]; #IO_L14N_T2_SRCC_35 Sch=sd_dat[3]
##Accelerometer
#set_property -dict { PACKAGE_PIN E15 IOSTANDARD LVCMOS33 } [get_ports { ACL_MISO }]; #IO_L11P_T1_SRCC_15 Sch=acl_miso
#set_property -dict { PACKAGE_PIN F14 IOSTANDARD LVCMOS33 } [get_ports { ACL_MOSI }]; #IO_L5N_T0_AD9N_15 Sch=acl_mosi
#set_property -dict { PACKAGE_PIN F15 IOSTANDARD LVCMOS33 } [get_ports { ACL_SCLK }]; #IO_L14P_T2_SRCC_15 Sch=acl_sclk
#set_property -dict { PACKAGE_PIN D15 IOSTANDARD LVCMOS33 } [get_ports { ACL_CSN }]; #IO_L12P_T1_MRCC_15 Sch=acl_csn
#set_property -dict { PACKAGE_PIN B13 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[1] }]; #IO_L2P_T0_AD8P_15 Sch=acl_int[1]
#set_property -dict { PACKAGE_PIN C16 IOSTANDARD LVCMOS33 } [get_ports { ACL_INT[2] }]; #IO_L20P_T3_A20_15 Sch=acl_int[2]
##Temperature Sensor
#set_property -dict { PACKAGE_PIN C14 IOSTANDARD LVCMOS33 } [get_ports { TMP_SCL }]; #IO_L1N_T0_AD0N_15 Sch=tmp_scl
#set_property -dict { PACKAGE_PIN C15 IOSTANDARD LVCMOS33 } [get_ports { TMP_SDA }]; #IO_L12N_T1_MRCC_15 Sch=tmp_sda
#set_property -dict { PACKAGE_PIN D13 IOSTANDARD LVCMOS33 } [get_ports { TMP_INT }]; #IO_L6N_T0_VREF_15 Sch=tmp_int
#set_property -dict { PACKAGE_PIN B14 IOSTANDARD LVCMOS33 } [get_ports { TMP_CT }]; #IO_L2N_T0_AD8N_15 Sch=tmp_ct
##Omnidirectional Microphone
#set_property -dict { PACKAGE_PIN J5 IOSTANDARD LVCMOS33 } [get_ports { M_CLK }]; #IO_25_35 Sch=m_clk
#set_property -dict { PACKAGE_PIN H5 IOSTANDARD LVCMOS33 } [get_ports { M_DATA }]; #IO_L24N_T3_35 Sch=m_data
#set_property -dict { PACKAGE_PIN F5 IOSTANDARD LVCMOS33 } [get_ports { M_LRSEL }]; #IO_0_35 Sch=m_lrsel
##PWM Audio Amplifier
#set_property -dict { PACKAGE_PIN A11 IOSTANDARD LVCMOS33 } [get_ports { AUD_PWM }]; #IO_L4N_T0_15 Sch=aud_pwm
#set_property -dict { PACKAGE_PIN D12 IOSTANDARD LVCMOS33 } [get_ports { AUD_SD }]; #IO_L6P_T0_15 Sch=aud_sd
##USB-RS232 Interface
#set_property -dict { PACKAGE_PIN C4 IOSTANDARD LVCMOS33 } [get_ports { UART_TXD_IN }]; #IO_L7P_T1_AD6P_35 Sch=uart_txd_in
#set_property -dict { PACKAGE_PIN D4 IOSTANDARD LVCMOS33 } [get_ports { UART_RXD_OUT }]; #IO_L11N_T1_SRCC_35 Sch=uart_rxd_out
#set_property -dict { PACKAGE_PIN D3 IOSTANDARD LVCMOS33 } [get_ports { UART_CTS }]; #IO_L12N_T1_MRCC_35 Sch=uart_cts
#set_property -dict { PACKAGE_PIN E5 IOSTANDARD LVCMOS33 } [get_ports { UART_RTS }]; #IO_L5N_T0_AD13N_35 Sch=uart_rts
##USB HID (PS/2)
#set_property -dict { PACKAGE_PIN F4 IOSTANDARD LVCMOS33 } [get_ports { PS2_CLK }]; #IO_L13P_T2_MRCC_35 Sch=ps2_clk
#set_property -dict { PACKAGE_PIN B2 IOSTANDARD LVCMOS33 } [get_ports { PS2_DATA }]; #IO_L10N_T1_AD15N_35 Sch=ps2_data
##SMSC Ethernet PHY
#set_property -dict { PACKAGE_PIN C9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDC }]; #IO_L11P_T1_SRCC_16 Sch=eth_mdc
#set_property -dict { PACKAGE_PIN A9 IOSTANDARD LVCMOS33 } [get_ports { ETH_MDIO }]; #IO_L14N_T2_SRCC_16 Sch=eth_mdio
#set_property -dict { PACKAGE_PIN B3 IOSTANDARD LVCMOS33 } [get_ports { ETH_RSTN }]; #IO_L10P_T1_AD15P_35 Sch=eth_rstn
#set_property -dict { PACKAGE_PIN D9 IOSTANDARD LVCMOS33 } [get_ports { ETH_CRSDV }]; #IO_L6N_T0_VREF_16 Sch=eth_crsdv
#set_property -dict { PACKAGE_PIN C10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXERR }]; #IO_L13N_T2_MRCC_16 Sch=eth_rxerr
#set_property -dict { PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[0] }]; #IO_L13P_T2_MRCC_16 Sch=eth_rxd[0]
#set_property -dict { PACKAGE_PIN D10 IOSTANDARD LVCMOS33 } [get_ports { ETH_RXD[1] }]; #IO_L19N_T3_VREF_16 Sch=eth_rxd[1]
#set_property -dict { PACKAGE_PIN B9 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXEN }]; #IO_L11N_T1_SRCC_16 Sch=eth_txen
#set_property -dict { PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[0] }]; #IO_L14P_T2_SRCC_16 Sch=eth_txd[0]
#set_property -dict { PACKAGE_PIN A8 IOSTANDARD LVCMOS33 } [get_ports { ETH_TXD[1] }]; #IO_L12N_T1_MRCC_16 Sch=eth_txd[1]
#set_property -dict { PACKAGE_PIN D5 IOSTANDARD LVCMOS33 } [get_ports { ETH_REFCLK }]; #IO_L11P_T1_SRCC_35 Sch=eth_refclk
#set_property -dict { PACKAGE_PIN B8 IOSTANDARD LVCMOS33 } [get_ports { ETH_INTN }]; #IO_L12P_T1_MRCC_16 Sch=eth_intn
##Quad SPI Flash
#set_property -dict { PACKAGE_PIN K17 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[0] }]; #IO_L1P_T0_D00_MOSI_14 Sch=qspi_dq[0]
#set_property -dict { PACKAGE_PIN K18 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[1] }]; #IO_L1N_T0_D01_DIN_14 Sch=qspi_dq[1]
#set_property -dict { PACKAGE_PIN L14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[2] }]; #IO_L2P_T0_D02_14 Sch=qspi_dq[2]
#set_property -dict { PACKAGE_PIN M14 IOSTANDARD LVCMOS33 } [get_ports { QSPI_DQ[3] }]; #IO_L2N_T0_D03_14 Sch=qspi_dq[3]
#set_property -dict { PACKAGE_PIN L13 IOSTANDARD LVCMOS33 } [get_ports { QSPI_CSN }]; #IO_L6P_T0_FCS_B_14 Sch=qspi_csn
@@ -0,0 +1,96 @@
## cpu21_board_top.xdc —— Nexys4DDR (xc7a100tcsg324-1) 上板引脚
## 引脚编号取自 ConstraintsData/Nexys4DDR_Master.xdc(母版默认全部注释)。
## 只约束 cpu21_board_top 实际使用的端口。
## 100 MHz 板载时钟(分频器输入)
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk100 }]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk100 }]
## CPU 时钟:板上 100 MHz 经 CLK_DIV(=2) 分频到 50 MHz
## (与 cpu21_board_top.v 的 CLK_DIV 保持一致;改分频比时这里也要改。
## 乘除法已是多周期单元,不再需要 16 分频;实测 100 MHz 下剩余违例路径是
## EX 级分支判决/中断保存 → BPB 更新,50 MHz 则余量充足)
create_generated_clock -name cpu_clk -source [get_ports { clk100 }] \
-divide_by 2 [get_pins { clk_div_reg/Q }]
## 复位按键(CPU_RESETN,低有效)与"继续运行"按键(BTNC)
set_property -dict { PACKAGE_PIN C12 IOSTANDARD LVCMOS33 } [get_ports { rst_n }]
set_property -dict { PACKAGE_PIN N17 IOSTANDARD LVCMOS33 } [get_ports { go }]
## 拨码开关 SW[2:0] 作为三路中断源
set_property -dict { PACKAGE_PIN J15 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[0] }]
set_property -dict { PACKAGE_PIN L16 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[1] }]
set_property -dict { PACKAGE_PIN M13 IOSTANDARD LVCMOS33 } [get_ports { irq_sw[2] }]
## 16 位 LED:显示 MMIO 写入值的低 16 位
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { led[0] }]
set_property -dict { PACKAGE_PIN K15 IOSTANDARD LVCMOS33 } [get_ports { led[1] }]
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { led[2] }]
set_property -dict { PACKAGE_PIN N14 IOSTANDARD LVCMOS33 } [get_ports { led[3] }]
set_property -dict { PACKAGE_PIN R18 IOSTANDARD LVCMOS33 } [get_ports { led[4] }]
set_property -dict { PACKAGE_PIN V17 IOSTANDARD LVCMOS33 } [get_ports { led[5] }]
set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { led[6] }]
set_property -dict { PACKAGE_PIN U16 IOSTANDARD LVCMOS33 } [get_ports { led[7] }]
set_property -dict { PACKAGE_PIN V16 IOSTANDARD LVCMOS33 } [get_ports { led[8] }]
set_property -dict { PACKAGE_PIN T15 IOSTANDARD LVCMOS33 } [get_ports { led[9] }]
set_property -dict { PACKAGE_PIN U14 IOSTANDARD LVCMOS33 } [get_ports { led[10] }]
set_property -dict { PACKAGE_PIN T16 IOSTANDARD LVCMOS33 } [get_ports { led[11] }]
set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { led[12] }]
set_property -dict { PACKAGE_PIN V14 IOSTANDARD LVCMOS33 } [get_ports { led[13] }]
set_property -dict { PACKAGE_PIN V12 IOSTANDARD LVCMOS33 } [get_ports { led[14] }]
set_property -dict { PACKAGE_PIN V11 IOSTANDARD LVCMOS33 } [get_ports { led[15] }]
## LED16_B:MMIO 写入指示(拉长约 0.34 s @50 MHz)
set_property -dict { PACKAGE_PIN R12 IOSTANDARD LVCMOS33 } [get_ports { led_we }]
## 上板调试时可把 MMIO 写入的 LED 寄存器也引到 LED16_R/G,
## 但默认只用一个引脚,避免与 led_we 争用。
## ======================= 扫雷扩展外设 =======================
## 5 向按键:BTNC 已作 go(同时也当鼠标左键),另外 4 个当光标方向
set_property -dict { PACKAGE_PIN M18 IOSTANDARD LVCMOS33 } [get_ports { btnu }]
set_property -dict { PACKAGE_PIN P18 IOSTANDARD LVCMOS33 } [get_ports { btnd }]
set_property -dict { PACKAGE_PIN P17 IOSTANDARD LVCMOS33 } [get_ports { btnl }]
set_property -dict { PACKAGE_PIN M17 IOSTANDARD LVCMOS33 } [get_ports { btnr }]
## SW[3]:插旗模式(1 = 点击时插旗/取消旗)
set_property -dict { PACKAGE_PIN R15 IOSTANDARD LVCMOS33 } [get_ports { sw_flag }]
## 七段数码管(共阳极:位选/段选都是低有效)
set_property -dict { PACKAGE_PIN T10 IOSTANDARD LVCMOS33 } [get_ports { seg[0] }]
set_property -dict { PACKAGE_PIN R10 IOSTANDARD LVCMOS33 } [get_ports { seg[1] }]
set_property -dict { PACKAGE_PIN K16 IOSTANDARD LVCMOS33 } [get_ports { seg[2] }]
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { seg[3] }]
set_property -dict { PACKAGE_PIN P15 IOSTANDARD LVCMOS33 } [get_ports { seg[4] }]
set_property -dict { PACKAGE_PIN T11 IOSTANDARD LVCMOS33 } [get_ports { seg[5] }]
set_property -dict { PACKAGE_PIN L18 IOSTANDARD LVCMOS33 } [get_ports { seg[6] }]
set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { dp }]
set_property -dict { PACKAGE_PIN J17 IOSTANDARD LVCMOS33 } [get_ports { an[0] }]
set_property -dict { PACKAGE_PIN J18 IOSTANDARD LVCMOS33 } [get_ports { an[1] }]
set_property -dict { PACKAGE_PIN T9 IOSTANDARD LVCMOS33 } [get_ports { an[2] }]
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { an[3] }]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { an[4] }]
set_property -dict { PACKAGE_PIN T14 IOSTANDARD LVCMOS33 } [get_ports { an[5] }]
set_property -dict { PACKAGE_PIN K2 IOSTANDARD LVCMOS33 } [get_ports { an[6] }]
set_property -dict { PACKAGE_PIN U13 IOSTANDARD LVCMOS33 } [get_ports { an[7] }]
## VGA(640x480@60,25 MHz 像素节拍由 CPU 时钟使能产生)
set_property -dict { PACKAGE_PIN A3 IOSTANDARD LVCMOS33 } [get_ports { vga_r[0] }]
set_property -dict { PACKAGE_PIN B4 IOSTANDARD LVCMOS33 } [get_ports { vga_r[1] }]
set_property -dict { PACKAGE_PIN C5 IOSTANDARD LVCMOS33 } [get_ports { vga_r[2] }]
set_property -dict { PACKAGE_PIN A4 IOSTANDARD LVCMOS33 } [get_ports { vga_r[3] }]
set_property -dict { PACKAGE_PIN C6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[0] }]
set_property -dict { PACKAGE_PIN A5 IOSTANDARD LVCMOS33 } [get_ports { vga_g[1] }]
set_property -dict { PACKAGE_PIN B6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[2] }]
set_property -dict { PACKAGE_PIN A6 IOSTANDARD LVCMOS33 } [get_ports { vga_g[3] }]
set_property -dict { PACKAGE_PIN B7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[0] }]
set_property -dict { PACKAGE_PIN C7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[1] }]
set_property -dict { PACKAGE_PIN D7 IOSTANDARD LVCMOS33 } [get_ports { vga_b[2] }]
set_property -dict { PACKAGE_PIN D8 IOSTANDARD LVCMOS33 } [get_ports { vga_b[3] }]
set_property -dict { PACKAGE_PIN B11 IOSTANDARD LVCMOS33 } [get_ports { vga_hs }]
set_property -dict { PACKAGE_PIN B12 IOSTANDARD LVCMOS33 } [get_ports { vga_vs }]
## PS/2 鼠标(可选:Pmod JA 插 Pmod PS2,顶层 USE_PS2_MOUSE=1)
set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 PULLUP true } [get_ports { ps2_clk }]
set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 PULLUP true } [get_ports { ps2_data }]
+13
View File
@@ -0,0 +1,13 @@
000012B7
00100313
0062A023
00131313
00008EB7
006EE463
0080006F
00100313
0000F3B7
00100E13
41C383B3
FE039EE3
FD9FF06F
@@ -0,0 +1,267 @@
7d000113
00100413
01f41993
41f9d993
00000433
00c00913
00800b13
00140413
00f47413
00800293
00100313
00499993
0089e9b3
01300533
02200893
00000073
406282b3
fe0294e3
00140413
00f00f93
01f47433
01c41413
00800293
00100313
0049d993
0089e9b3
01300533
02200893
00000073
406282b3
fe0294e3
01c45413
406b0b33
000b0463
f95ff06f
000002b3
fff2c293
01029293
fff2e293
00500533
02200893
00000073
f5dff06f
00512023
00410113
01c12023
00410113
00912023
00410113
01112023
00410113
00a12023
00410113
00812023
00410113
01312023
00410113
01412023
00410113
01512023
00410113
01612023
00410113
04106b73
01612023
00410113
0040e073
00200293
01100493
00849493
01148493
01049493
00900533
02200893
00000073
01c00e13
0254d4b3
00900533
02200893
00000073
fffe0e13
fe0e16e3
00a00893
00100b13
00300a13
00100a93
01600433
00441413
014469b3
01300533
02200893
00000073
fe0416e3
415a0a33
fe0a10e3
0040f073
ffc10113
00012b03
041b1073
ffc10113
00012b03
ffc10113
00012a83
ffc10113
00012a03
ffc10113
00012983
ffc10113
00012403
ffc10113
00012503
ffc10113
00012883
ffc10113
00012483
ffc10113
00012e03
ffc10113
00012283
00200073
00612023
00410113
01c12023
00410113
00912023
00410113
01212023
00410113
01112023
00410113
00a12023
00410113
00812023
00410113
01312023
00410113
01412023
00410113
01512023
00410113
01612023
00410113
04106b73
01612023
00410113
0040e073
ff100493
00900533
02200893
00000073
00148493
fe04c8e3
00a00893
00200b13
00300a13
00100a93
01600433
00441413
014469b3
01300533
02200893
00000073
fe0416e3
415a0a33
fe0a10e3
0040f073
ffc10113
00012b03
041b1073
ffc10113
00012b03
ffc10113
00012a83
ffc10113
00012a03
ffc10113
00012983
ffc10113
00012403
ffc10113
00012503
ffc10113
00012883
ffc10113
00012903
ffc10113
00012483
ffc10113
00012e03
ffc10113
00012303
00200073
00912023
00410113
01112023
00410113
00a12023
00410113
00812023
00410113
01312023
00410113
01412023
00410113
01512023
00410113
01612023
00410113
04106b73
01612023
00410113
0040e073
00000313
02000e13
00000493
00100913
00930023
00900533
02200893
00000073
012484b3
00130313
fffe0e13
fe0e12e3
00800e13
00000313
00032483
00900533
02200893
00000073
00430313
fffe0e13
fe0e14e3
00a00893
00300b13
00300a13
00100a93
01600433
00441413
014469b3
01300533
02200893
00000073
fe0416e3
415a0a33
fe0a10e3
0040f073
ffc10113
00012b03
041b1073
ffc10113
00012b03
ffc10113
00012a83
ffc10113
00012a03
ffc10113
00012983
ffc10113
00012403
ffc10113
00012503
ffc10113
00012883
ffc10113
00012483
00200073
+90
View File
@@ -0,0 +1,90 @@
00100293
FFF00313
FFF2B513
00001FB7
00AFA023
00133513
00001FB7
00AFA023
0FF00293
FFF00313
0062C533
00001FB7
00AFA023
00100293
01F00313
00629533
00001FB7
00AFA023
FFF00293
01C00313
0062D533
00001FB7
00AFA023
4062D533
00001FB7
00AFA023
4D200293
23700313
02628533
00001FB7
00AFA023
FFF00293
FFF00313
02629533
00001FB7
00AFA023
0262B533
00001FB7
00AFA023
FFF00293
00200313
0262A533
00001FB7
00AFA023
FFF00293
00506863
10100513
00001FB7
00AFA023
60100513
00001FB7
00AFA023
FFF00293
0002DA63
60200513
00001FB7
00AFA023
0100006F
10200513
00001FB7
00AFA023
FFF00293
0002F863
10300513
00001FB7
00AFA023
60300513
00001FB7
00AFA023
FFD00493
00900533
00001FB7
00AFA023
00148493
FE9058E3
00300293
00500533
00001FB7
00AFA023
FFF28293
00100393
FE72F6E3
00400493
00900533
00001FB7
00AFA023
FFF48493
FE9068E3
00A00893
00000073
+19
View File
@@ -0,0 +1,19 @@
123452B7
00500533
00001FB7
00AFA023
FFFFF2B7
00500533
00001FB7
00AFA023
800002B7
00500533
00001FB7
00AFA023
123452B7
67828293
00500533
00001FB7
00AFA023
00A00893
00000073
+16
View File
@@ -0,0 +1,16 @@
000012B7
12345337
67830313
0062A023
0002A503
00001FB7
00AFA023
FFF00393
0072A023
07F00E13
01C280A3
0002A503
00001FB7
00AFA023
00A00893
00000073
+64
View File
@@ -0,0 +1,64 @@
00700313
00600393
02730533
00001FB7
00AFA023
FFB00313
00400393
02730533
00001FB7
00AFA023
40000337
00400393
02731533
00001FB7
00AFA023
FFF00313
00200393
02733533
00001FB7
00AFA023
FFF00313
FFF00393
02732533
00001FB7
00AFA023
06400313
00700393
02735533
00001FB7
00AFA023
02737533
00001FB7
00AFA023
FFF00313
00200393
02735533
00001FB7
00AFA023
00003337
03930313
00000393
02735533
00001FB7
00AFA023
02737533
00001FB7
00AFA023
FFF00313
01000393
02737533
00001FB7
00AFA023
12345337
67830313
01000393
02730533
00001FB7
00AFA023
000013B7
02731533
00001FB7
00AFA023
00A00893
00000073
@@ -0,0 +1,276 @@
00100493
0100006f
00100493
00200913
00300993
0100006f
00100493
00200913
00300993
0100006f
00100493
00200913
00300993
0100006f
00100493
00200913
00300993
380000ef
00100413
00100493
01f49493
00900533
02200893
00000073
0024d493
00048463
fedff06f
00900533
02200893
00000073
00100493
00249493
00900533
02200893
00000073
00048463
fedff06f
00100493
01f49493
00900533
02200893
00000073
4034d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
4044d493
00900533
02200893
00000073
00100413
01f41993
41f9d993
00000433
00c00913
00300b13
00140413
00f47413
00800293
00100313
00499993
0089e9b3
01300533
02200893
00000073
406282b3
fe0294e3
00140413
00f00f93
01f47433
01c41413
00800293
00100313
0049d993
0089e9b3
01300533
02200893
00000073
406282b3
fe0294e3
01c45413
406b0b33
000b0463
f95ff06f
000002b3
fff2c293
00829293
0ff2e293
00500533
02200893
00000073
fff00413
00000493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
0084a023
00140413
00448493
00140413
00000433
03c00493
00042983
0004aa03
0149a2b3
00028663
0134a023
01442023
ffc48493
fe9412e3
00800533
02200893
00000073
00440413
03c00493
fc9416e3
00a00893
00200293
01100493
00849493
01148493
01049493
00900533
02200893
00000073
01c00e13
0254d4b3
00900533
02200893
00000073
fffe0e13
fe0e16e3
00a00893
08800493
00849493
04848493
08700913
00891913
05490913
02997933
01091913
11096913
01200533
02200893
00000073
00a00893
00000313
02000e13
00000493
00100913
00930023
00900533
02200893
00000073
012484b3
00130313
fffe0e13
fe0e12e3
00800e13
00000313
00032483
00900533
02200893
00000073
00430313
fffe0e13
fe0e14e3
00a00893
ff100493
00900533
02200893
00000073
00148493
fe04c8e3
00a00893
00000413
00140413
00800533
02200893
00000073
00240413
00800533
02200893
00000073
00340413
00800533
02200893
00000073
00440413
00800533
02200893
00000073
00540413
00800533
02200893
00000073
00640413
00800533
02200893
00000073
00740413
00800533
02200893
00000073
00840413
00800533
02200893
02200893
00000073
00008067
@@ -0,0 +1,67 @@
00500493
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
0140006f
00100493
00200913
00300993
00400a13
fff48493
f80492e3
00100413
0ff00913
00100493
00300993
01240c63
00840a63
00100493
00200913
00300993
00400a13
00949a63
01249863
00100493
00200913
00300993
00c000ef
00a00893
00000073
00000413
00140413
00800533
02200893
00000073
00008067
+117
View File
@@ -0,0 +1,117 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 八表项全相联分支目标缓冲(BPB/BTB)。
//
// tag = PC[11:2],与 Logisim 版 BPB 中的十位 tag 拆分保持一致。
// count 为两位饱和计数器:00/01 表示不跳转,10/11 表示跳转。
// age 用于近似 LRU 替换:被选中的表项清零,其余表项自增。
// -----------------------------------------------------------------------------
module cpu21_bpb_8 (
input wire clk,
input wire reset,
input wire [31:0] predict_pc,
input wire predict_enable,
output reg predict_hit,
output reg predict_taken,
output reg [31:0] predict_target,
input wire update_enable,
input wire update_controlflow,
input wire [31:0] update_pc,
input wire [31:0] update_target,
input wire update_taken
);
reg valid [0:7];
reg [ 9:0] tag [0:7];
reg [31:0] target [0:7];
reg [ 1:0] count [0:7];
reg [ 2:0] age [0:7];
reg update_found;
reg [ 2:0] update_index;
reg replace_found;
reg [ 2:0] replace_index;
reg [ 2:0] max_age;
integer p;
integer u;
integer k;
integer selected_index;
// 预测命中查找:按 tag 全相联匹配,命中则给出目标地址与方向预测。
always @* begin
predict_hit = 1'b0;
predict_taken = 1'b0;
predict_target = 32'b0;
for (p = 0; p < 8; p = p + 1) begin
if (predict_enable && !predict_hit && valid[p] && (tag[p] == predict_pc[11:2])) begin
predict_hit = 1'b1;
predict_target = target[p];
predict_taken = count[p][1];
end
end
end
// 更新时先找匹配表项;若无匹配则优先使用无效表项,否则淘汰最旧表项。
// 下方的 age 自增实现了类似 LRU 的替换策略。
always @* begin
update_found = 1'b0;
update_index = 3'd0;
replace_found = 1'b0;
replace_index = 3'd0;
max_age = 3'd0;
for (u = 0; u < 8; u = u + 1) begin
if (!update_found && valid[u] && (tag[u] == update_pc[11:2])) begin
update_found = 1'b1;
update_index = u;
end
end
for (u = 0; u < 8; u = u + 1) begin
if (!replace_found && !valid[u]) begin
replace_found = 1'b1;
replace_index = u;
end else if (!replace_found && (age[u] >= max_age)) begin
replace_index = u;
max_age = age[u];
end
end
end
// 表项状态更新(含替换与两位饱和计数器更新)。
always @(posedge clk or posedge reset) begin
if (reset) begin
for (k = 0; k < 8; k = k + 1) begin
valid[k] <= 1'b0;
tag[k] <= 10'b0;
target[k] <= 32'b0;
count[k] <= 2'b01;
age[k] <= 3'b0;
end
end else if (update_enable && update_controlflow) begin
selected_index = update_found ? update_index : replace_index;
valid[selected_index] <= 1'b1;
tag[selected_index] <= update_pc[11:2];
target[selected_index] <= update_target;
age[selected_index] <= 3'b0;
if (!update_found) begin
// 新表项采用较弱的初始状态,避免立刻产生错误预测。
count[selected_index] <= update_taken ? 2'b10 : 2'b01;
end else if (update_taken) begin
// 实际跳转:计数器加一(饱和到 11)。
if (count[selected_index] != 2'b11) count[selected_index] <= count[selected_index] + 2'b01;
end else begin
// 实际不跳转:计数器减一(饱和到 00)。
if (count[selected_index] != 2'b00) count[selected_index] <= count[selected_index] - 2'b01;
end
for (k = 0; k < 8; k = k + 1) begin
if (valid[k] && (k != selected_index) && (age[k] != 3'b111)) age[k] <= age[k] + 3'b001;
end
end
end
endmodule
`default_nettype wire
+310
View File
@@ -0,0 +1,310 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// MMIO:把外设寄存器映射到数据存储器地址空间。
//
// 地址映射(字节地址,都是"字对齐"译码:同一 32 位寄存器内的任意字节偏移都命中,
// 具体改哪几个字节由 wstrb_i 决定,与原来 LED 的语义一致):
//
// 0x0000_1000 LED 数据寄存器(**保持原有语义不变**,led_valid_o 拉高一拍)
// 0x0000_1004 七段数码管:8 位十六进制数(每 4 位一个数码管,低 4 位 = 最右)
// 0x0000_1100 + 4*k 鼠标寄存器组(见下)
// 0x0000_1140 + 4*k 显示控制器寄存器(见下)
// 0x0000_2000 - 0x27FF 显存窗口:40x30 格、每格 1 字节(只用到前 1200 字节)
//
// 鼠标寄存器(基址 0x1100):
// +0x00 STATUS RO [0] FIFO 非空 [5:1] 项数 [6] 中断使能 [7] FIFO 满
// +0x04 DATA RO [7:0] dx [15:8] dy [20:16] 按键(读无副作用)
// +0x08 ACK WO 写任意值 -> 弹出 FIFO 一项
// +0x0C CTRL RW [0] 中断使能(复位默认 1)
// +0x10 INPUT RO [15:0] 拨码开关 [20:16] 按键(原样)
// +0x14 TIME RO 自由运行计数器(随机种子)
// +0x18 FRAME RO VGA 帧计数
// +0x1C EVCNT RO 累计鼠标事件数
// +0x20 LEVEL RW [0] 写 1 -> 拉起 level_irq_o(软件中断,用作"关卡入口");
// 关卡程序返回前要写 0 清掉,否则下次没有上升沿
// +0x24 MAGIC RO 0xCAFE0002(自检用:读得到说明 MMIO 活着)
//
// 显示控制器寄存器(基址 0x1140):
// +0x00 CTRL RW [0] 光标可见 [6:4] 背景色(3 位调色板)
// +0x04 CURSOR RW [5:0] 列(0..39) [13:8] 行(0..29)
// +0x08 MAGIC RO 0xCAFE0001
//
// 显存本体不在本模块里:写操作通过 fb_* 端口直接送到板级顶层的双口 RAM(VGA 读
// 同一块 RAM),读操作把 fb_rdata_i 组合回读数据,保持"组合读"的时序不变。
//
// 两个中断源:
// mouse_irq_o = 中断使能 && 事件 FIFO 非空(接 irq_i[0])
// level_irq_o = LEVEL 寄存器(软件中断,接 irq_i[1],用来"进入关卡")
//
// 以后要增加外设(UART、SD 卡等)时只需在这里扩展地址译码。
// -----------------------------------------------------------------------------
module cpu21_mmio #(
parameter [31:0] LED_ADDR = 32'h0000_1000,
parameter integer FB_WORDS = 300,
parameter integer FIFO_DEPTH = 16
) (
input wire clk,
input wire reset,
// MEM 级的访存请求(来自 EX/MEM 流水寄存器)
input wire mem_valid_i,
input wire mem_write_i,
input wire mem_read_i,
input wire [31:0] addr_i,
input wire [31:0] wdata_i,
input wire [ 3:0] wstrb_i,
// MMIO 读回
output wire [31:0] rdata_o,
output wire rdata_hit_o,
// LED 外设(原样保留)
output reg [31:0] led_data_o,
output reg led_valid_o,
// 七段数码管:8 位十六进制数
output reg [31:0] seg_data_o,
output reg seg_valid_o,
// 显存写端口(板级顶层的双口 RAM)
output wire fb_we_o,
output wire [ 9:0] fb_waddr_o,
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i,
// 显示控制器寄存器(给 VGA 用)
output wire [31:0] disp_ctrl_o,
output wire [31:0] disp_cursor_o,
// 鼠标事件源(来自板级顶层:按键去抖 / PS/2 解析)
input wire mouse_ev_valid_i,
input wire [ 7:0] mouse_dx_i,
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i,
input wire [15:0] sw_i,
input wire frame_tick_i,
// 中断
output wire mouse_irq_o,
output wire level_irq_o
);
// ---------------------------------------------------------------------------
// 地址译码(都按"字"对齐:同一 32 位寄存器内的任意字节偏移都算命中)
// ---------------------------------------------------------------------------
localparam [31:0] SEG_ADDR = 32'h0000_1004;
localparam [31:0] MOUSE_BASE = 32'h0000_1100;
localparam [31:0] DISP_BASE = 32'h0000_1140;
localparam integer FIFO_AW = 4;
localparam [FIFO_AW:0] FIFO_FULL_V = FIFO_DEPTH[FIFO_AW:0];
wire sel_led = (addr_i[31:2] == LED_ADDR[31:2]);
wire sel_seg = (addr_i[31:2] == SEG_ADDR[31:2]);
wire sel_mouse = (addr_i[31:8] == MOUSE_BASE[31:8]) && (addr_i[7:6] == 2'b00);
wire sel_disp = (addr_i[31:8] == DISP_BASE[31:8]) && (addr_i[7:6] == 2'b01);
// 显存窗口 0x2000 - 0x27FF(addr[12]=1 且 addr[11]=0)
wire sel_fb = (addr_i[31:13] == 19'd0) && addr_i[12] && !addr_i[11];
wire [3:0] midx = addr_i[5:2];
wire [3:0] didx = addr_i[5:2];
wire [9:0] fb_widx = addr_i[10:2];
wire fb_ok = (fb_widx < FB_WORDS);
wire led_wr = mem_valid_i && mem_write_i && sel_led;
wire led_rd = mem_valid_i && mem_read_i && sel_led;
wire seg_wr = mem_valid_i && mem_write_i && sel_seg;
wire seg_rd = mem_valid_i && mem_read_i && sel_seg;
wire mo_wr = mem_valid_i && mem_write_i && sel_mouse;
wire mo_rd = mem_valid_i && mem_read_i && sel_mouse;
wire dp_wr = mem_valid_i && mem_write_i && sel_disp;
wire dp_rd = mem_valid_i && mem_read_i && sel_disp;
wire fb_wr = mem_valid_i && mem_write_i && sel_fb && fb_ok;
wire fb_rd = mem_valid_i && mem_read_i && sel_fb && fb_ok;
assign fb_we_o = fb_wr;
assign fb_waddr_o = fb_widx;
assign fb_wdata_o = wdata_i;
assign fb_wstrb_o = wstrb_i;
assign rdata_hit_o = led_rd | seg_rd | mo_rd | dp_rd | fb_rd;
// 字节写合并:sw 改 4 字节、sb 只改 1 字节(与 RAM 的字节写语义一致)
function [31:0] merge_bytes;
input [31:0] new_v;
input [31:0] old_v;
input [ 3:0] strb;
begin
merge_bytes[7:0] = strb[0] ? new_v[7:0] : old_v[7:0];
merge_bytes[15:8] = strb[1] ? new_v[15:8] : old_v[15:8];
merge_bytes[23:16] = strb[2] ? new_v[23:16] : old_v[23:16];
merge_bytes[31:24] = strb[3] ? new_v[31:24] : old_v[31:24];
end
endfunction
// ---------------------------------------------------------------------------
// LED:保持原有语义(led_valid_o 只拉高一拍)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
led_data_o <= 32'b0;
led_valid_o <= 1'b0;
end
else begin
led_valid_o <= 1'b0;
if (led_wr) begin
led_data_o <= merge_bytes(wdata_i, led_data_o, wstrb_i);
led_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 七段数码管:8 位十六进制数(低 4 位 = 最右边那个数码管)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
seg_data_o <= 32'b0;
seg_valid_o <= 1'b0;
end
else begin
seg_valid_o <= 1'b0;
if (seg_wr) begin
seg_data_o <= merge_bytes(wdata_i, seg_data_o, wstrb_i);
seg_valid_o <= 1'b1;
end
end
end
// ---------------------------------------------------------------------------
// 显示控制器寄存器(显存本体在板级顶层)
// ---------------------------------------------------------------------------
reg [31:0] disp_ctrl_q, disp_cursor_q;
assign disp_ctrl_o = disp_ctrl_q;
assign disp_cursor_o = disp_cursor_q;
// ---------------------------------------------------------------------------
// 鼠标:事件 FIFO + 寄存器 + 中断
// ---------------------------------------------------------------------------
reg [31:0] fifo [0:FIFO_DEPTH-1];
reg [FIFO_AW-1:0] wr_ptr_q, rd_ptr_q;
reg [FIFO_AW:0] cnt_q;
reg [31:0] mctrl_q, time_q, frame_q, evcnt_q;
reg level_q;
wire fifo_empty = (cnt_q == {FIFO_AW+1{1'b0}});
wire fifo_full = (cnt_q == FIFO_FULL_V);
wire [31:0] fifo_head = fifo[rd_ptr_q];
wire irq_en = mctrl_q[0];
assign mouse_irq_o = irq_en && !fifo_empty;
assign level_irq_o = level_q;
wire push = mouse_ev_valid_i && !fifo_full;
wire pop = mo_wr && (midx == 4'd2) && !fifo_empty;
always @(posedge clk or posedge reset) begin
if (reset) begin
mctrl_q <= 32'h0000_0001; // 鼠标中断默认打开
time_q <= 32'd0;
frame_q <= 32'd0;
evcnt_q <= 32'd0;
level_q <= 1'b0;
wr_ptr_q <= {FIFO_AW{1'b0}};
rd_ptr_q <= {FIFO_AW{1'b0}};
cnt_q <= {(FIFO_AW+1){1'b0}};
disp_ctrl_q <= 32'h0000_0001; // 光标可见,背景色 0
disp_cursor_q <= 32'h0000_0000;
end
else begin
time_q <= time_q + 32'd1;
if (frame_tick_i)
frame_q <= frame_q + 32'd1;
if (push) begin
fifo[wr_ptr_q] <= {11'b0, mouse_btn_i, mouse_dy_i, mouse_dx_i};
wr_ptr_q <= wr_ptr_q + 1'b1;
evcnt_q <= evcnt_q + 32'd1;
end
if (pop)
rd_ptr_q <= rd_ptr_q + 1'b1;
cnt_q <= cnt_q + (push ? 1'b1 : 1'b0) - (pop ? 1'b1 : 1'b0);
// 鼠标寄存器:CTRL(3) / LEVEL(8)
if (mo_wr) begin
case (midx)
4'd3:
mctrl_q <= merge_bytes(wdata_i, mctrl_q, wstrb_i);
4'd8:
level_q <= wdata_i[0];
default:
;
endcase
end
// 显示控制器:CTRL(0) / CURSOR(1)
if (dp_wr) begin
case (didx)
4'd0:
disp_ctrl_q <= merge_bytes(wdata_i, disp_ctrl_q, wstrb_i);
4'd1:
disp_cursor_q <= merge_bytes(wdata_i, disp_cursor_q, wstrb_i);
default:
;
endcase
end
end
end
// ---------------------------------------------------------------------------
// 读数据(组合,与"指令存储器组合读/数据存储器组合读"的时序一致)
// ---------------------------------------------------------------------------
reg [31:0] rdata_mux;
always @* begin
rdata_mux = 32'b0;
if (led_rd) begin
rdata_mux = led_data_o;
end
else if (seg_rd) begin
rdata_mux = seg_data_o;
end
else if (fb_rd) begin
rdata_mux = fb_rdata_i;
end
else if (mo_rd) begin
case (midx)
4'd0:
rdata_mux = {24'b0, fifo_full, irq_en, cnt_q[4:0], !fifo_empty};
4'd1:
rdata_mux = fifo_head;
4'd3:
rdata_mux = mctrl_q;
4'd4:
rdata_mux = {11'b0, mouse_btn_i, sw_i};
4'd5:
rdata_mux = time_q;
4'd6:
rdata_mux = frame_q;
4'd7:
rdata_mux = evcnt_q;
4'd8:
rdata_mux = {31'b0, level_q};
4'd9:
rdata_mux = 32'hCAFE_0002;
default:
rdata_mux = 32'b0;
endcase
end
else if (dp_rd) begin
case (didx)
4'd0:
rdata_mux = disp_ctrl_q;
4'd1:
rdata_mux = disp_cursor_q;
4'd2:
rdata_mux = 32'hCAFE_0001;
default:
rdata_mux = 32'b0;
endcase
end
end
assign rdata_o = rdata_mux;
endmodule
`default_nettype wire
+106
View File
@@ -0,0 +1,106 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_pointer —— 把板上的 5 向按键变成"鼠标"事件源(板级顶层用它给 MMIO 送事件)。
//
// 事件单位:1/8 格。按键一次 = 8 个单位 = 光标移动一格,这样软件端用
// 纯整数加减(不需要任何除法)就能把累积量换算成格,配合 PS/2 鼠标时
// 手感也一致(PS/2 的原始计数直接按 1/8 格处理)。
//
// BTNU -> dy = -8 BTND -> dy = +8
// BTNL -> dx = -8 BTNR -> dx = +8
// BTNC -> 左键按下(btn[0] = 1,dx = dy = 0)
//
// 去抖方式:用 ~1 ms 的采样节拍(50 MHz 下计数 50000)采样两级同步后的按键,
// 与上一次的稳定值比较,只在"按下"的边沿产生事件。一次采样可能同时检测到
// 多个按下,此时用 press_pend_q 排队,由优先级选择器逐个上报(每个时钟周期
// 最多一个事件),因此不会丢按键。
// -----------------------------------------------------------------------------
module cpu21_pointer #(
// 采样节拍 = clk / (SAMPLE_DIV),默认 50 MHz / 50000 = 1 kHz
parameter integer SAMPLE_DIV = 50000
) (
input wire clk,
input wire reset,
input wire [4:0] btn_i, // {BTNC, BTNU, BTND, BTNL, BTNR}
output wire ev_valid_o, // 单周期脉冲
output wire [7:0] ev_dx_o, // 有符号,1/8 格
output wire [7:0] ev_dy_o,
output wire [4:0] ev_btn_o
);
// ---------------------------------------------------------------------------
// 1 ms 采样节拍
// ---------------------------------------------------------------------------
reg [15:0] div_q;
wire sample = (div_q == (SAMPLE_DIV - 1));
always @(posedge clk or posedge reset) begin
if (reset)
div_q <= 16'd0;
else if (sample)
div_q <= 16'd0;
else
div_q <= div_q + 16'd1;
end
// ---------------------------------------------------------------------------
// 两级同步 + 采样保持(采样率远低于按键抖动频率,等效去抖)
// ---------------------------------------------------------------------------
reg [4:0] btn_sync1_q, btn_sync2_q, btn_stable_q;
always @(posedge clk or posedge reset) begin
if (reset) begin
btn_sync1_q <= 5'b0;
btn_sync2_q <= 5'b0;
btn_stable_q <= 5'b0;
end
else begin
btn_sync1_q <= btn_i;
btn_sync2_q <= btn_sync1_q;
if (sample)
btn_stable_q <= btn_sync2_q;
end
end
// 只在按下的边沿产生新事件
wire [4:0] press_new = sample ? (btn_sync2_q & ~btn_stable_q) : 5'b0;
// ---------------------------------------------------------------------------
// 待上报队列 + 优先级选择(每周期最多上报一个)
// ---------------------------------------------------------------------------
reg [4:0] press_pend_q;
wire [4:0] pend_next = press_pend_q | press_new;
reg [4:0] sel;
always @* begin
sel = 5'b0;
if (pend_next[4])
sel = 5'b10000; // BTNC:点击
else if (pend_next[3])
sel = 5'b01000; // BTNU:上
else if (pend_next[2])
sel = 5'b00010; // BTND:下
else if (pend_next[1])
sel = 5'b00001; // BTNL:左
else if (pend_next[0])
sel = 5'b00100; // BTNR:右
end
always @(posedge clk or posedge reset) begin
if (reset)
press_pend_q <= 5'b0;
else
press_pend_q <= pend_next & ~sel;
end
assign ev_valid_o = |pend_next;
assign ev_dx_o = sel[1] ? 8'd248 : (sel[0] ? 8'd8 : 8'd0); // 248 = -8 的补码
assign ev_dy_o = sel[3] ? 8'd248 : (sel[2] ? 8'd8 : 8'd0);
assign ev_btn_o = {4'b0, sel[4]};
endmodule
`default_nettype wire
Tcl 一键重建
+145
View File
@@ -0,0 +1,145 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_ps2_mouse —— 可选的真鼠标输入:PS/2 鼠标(Digilent Pmod PS2 插在 JA 上)。
//
// JA[1] = PS/2 DATA,JA[2] = PS/2 CLK(两条线都需要上拉,见 XDC 里的 PULLUP)。
//
// 注意:本模块没有在真实硬件上验证过(作者手头没有 PS/2 鼠标),如果你不用真
// 鼠标,请让顶层用 cpu21_pointer(5 向按键)作为事件源即可,C 程序完全不用改。
// 板级顶层 cpu21_board_top 用参数 USE_PS2_MOUSE 选择这两者。
//
// 实现内容:
// 1) 接收:在 ps2_clk 的下降沿采样 ps2_data,收满 11 位(起始 0 + 8 数据
// LSB 先 + 奇校验 + 停止 1)后校验并拼成 3 字节一包;
// 2) 解析:byte0 = 标志(bit0 左键、bit1 右键、bit3 固定 1),byte1 = dx,
// byte2 = dy,bit7 为符号位;
// 3) 上报:当 dx/dy 非 0 或按键状态变化时产生一个事件(单位与
// cpu21_pointer 相同,即原始计数 = 1/8 格);
// 4) 本模块只做"接收",不实现主机→鼠标的命令发送(0xF4 使能上报)。多数
// PS/2 鼠标上电后就会主动上报,这类鼠标可以直接用;如果你的鼠标上电毫无
// 反应,说明它需要主机先发 0xF4 —— 那部分需要自行补上,或者直接用默认的
// 5 向按键(cpu21_pointer),C 程序完全不用改。
// -----------------------------------------------------------------------------
module cpu21_ps2_mouse (
input wire clk,
input wire reset,
// PS/2 双向线(开漏)
input wire ps2_clk_i,
output wire ps2_clk_oe_o,
input wire ps2_data_i,
output wire ps2_data_oe_o,
// 事件上报
output reg ev_valid_o,
output reg [7:0] ev_dx_o,
output reg [7:0] ev_dy_o,
output reg [4:0] ev_btn_o,
// 调试:收到的字节数(可选观察)
output reg [15:0] rx_count_o
);
// 开漏:oe = 1 时把线拉低,否则高阻(外部上拉)
assign ps2_clk_oe_o = 1'b0; // 本设计只接收,不主动拉低时钟(除了初始化)
assign ps2_data_oe_o = init_oe;
。本模块只接收,两条线都不驱动。
assign ps2_clk_oe_o = 1'b0;
assign ps2_data_oe_o = 1'b02_data_s1, ps2_data_s2;
always @(posedge clk or posedge reset) begin
if (reset) begin
ps2_clk_s1 <= 1'b1;
ps2_clk_s2 <= 1'b1;
ps2_data_s1 <= 1'b1;
ps2_data_s2 <= 1'b1;
end
else begin
ps2_clk_s1 <= ps2_clk_i;
ps2_clk_s2 <= ps2_clk_s1;
ps2_data_s1 <= ps2_data_i;
ps2_data_s2 <= ps2_data_s1;
end
end
wire clk_fall = ~ps2_clk_s1 & ps2_clk_s2; // 同步后的下降沿
wire clk_rise = ps2_clk_s1 & ~ps2_clk_s2;
// ---------------------------------------------------------------------------
// 接收移位:11 位一帧,bit0 = 起始位,bit1..8 = 数据,bit9 = 校验,bit10 = 停止
// ---------------------------------------------------------------------------
reg [3:0] bit_cnt_q;
reg [10:0] frame_q;
reg frame_ok_q;
reg [23:0] pkt_q; // 3 字节包
reg [1:0] pkt_cnt_q;
reg [7:0] byte_cnt_q; // 一包内的字节序号(0..2)
reg [7:0] dx_q, dy_q;
reg [4:0] btn_q; // {4'b0, middle, right, left} -> 只取低 3 位使用
// 校验:11 位中 1 的个数应为奇数(奇校验)
function automatic odd_parity_ok(input [10:0] f);
odd_parity_ok = ^{f[9:1]}; // data + parity 共 9 位,1 的个数应为偶数
endfunction
always @(posedge clk or posedge reset) begin
if (reset) begin
bit_cnt_q <= 4'd0;
frame_q <= 11'd0;
frame_ok_q <= 1'b0;
byte_cnt_q <= 8'd0;
pkt_q <= 24'd0;
rx_count_o <= 16'd0;
ev_valid_o <= 1'b0;
end
else begin
ev_valid_o <= 1'b0;
if (clk_fall) begin
if (bit_cnt_q == 4'd0 && ps2_data_s2 == 1'b0) begin
// 起始位有效,开始收
frame_q <= {ps2_data_s2, frame_q[10:1]};
bit_cnt_q <= 4'd1;
end
else if (bit_cnt_q != 4'd0) begin
frame_q <= {ps2_data_s2, frame_q[10:1]};
if (bit_cnt_q == 4'd10) begin
bit_cnt_q <= 4'd0;
// f = {停止, 校验, 8 数据, 起始},这里只关心数据与校验
if (odd_parity_ok(frame_q) && (frame_q[10] == 1'b1)) begin
rx_count_o <= rx_count_o + 16'd1;
case (byte_cnt_q)
8'd0: begin
// 标志字节:bit3 必须为 1(否则是鼠标发来的其他响应,丢弃)
if (frame_q[8:1][3] == 1'b1) begin
pkt_q[7:0] <= frame_q[8:1];
byte_cnt_q <= 8'd1;
end
end
8'd1: begin
pkt_q[15:8] <= frame_q[8:1]; // dx
byte_cnt_q <= 8'd2;
end
8'd2: begin
byte_cnt_q <= 8'd0;
ev_dx_o <= frame_q[8:1]; // dy
ev_dy_o <= pkt_q[15:8]; // 注意:byte1 是 dx
ev_btn_o <= {2'b0, pkt_q[2], pkt_q[1]}; // {middle, right, left}
ev_valid_o <= 1'b1;
end
default:
byte_cnt_q <= 8'd0;
endcase
end
end
else begin
bit_cnt_q <= bit_cnt_q + 4'd1;
end
end
end
end
end
endmodule
`default_nettype wire
Tcl 一键重建
+65
View File
@@ -0,0 +1,65 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// ALU:组合运算单元(只做加减/移位/逻辑/比较等单拍运算)。
//
// 乘法与除法已移到 cpu21_riscv_muldiv.v(时序多周期单元),因为组合的 a*b / a%b
// 关键路径过长(综合后约 94 ns,100 MHz 下无法收敛)。因此本模块不再实现
// ALU_MUL / ALU_DIVU:这两个操作码只作为“这是乘除法指令”的标识,对应结果由
// 顶层从 muldiv 的输出选取(result2 的 wb_sel=3 约定保持不变)。
//
// result 为主结果;result2 为第二结果,由译码器用 wb_sel=3 选取。
// -----------------------------------------------------------------------------
module cpu21_riscv_alu (
input wire [ 3:0] op,
input wire [31:0] a,
input wire [31:0] b,
output reg [31:0] result,
output reg [31:0] result2
);
localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1;
localparam ALU_SRL = 4'd2;
localparam ALU_MUL = 4'd3;
localparam ALU_DIVU = 4'd4;
localparam ALU_ADD = 4'd5;
localparam ALU_SUB = 4'd6;
localparam ALU_AND = 4'd7;
localparam ALU_OR = 4'd8;
localparam ALU_XOR = 4'd9;
localparam ALU_NOR = 4'd10;
localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12;
localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // 直接输出 B 端(供 LUI 使用)
always @* begin
result = 32'b0;
result2 = 32'b0;
case (op)
ALU_SLL: result = a << b[4:0];
ALU_SRA: result = $signed(a) >>> b[4:0];
ALU_SRL: result = a >> b[4:0];
// 乘法(ALU_MUL)与除法(ALU_DIVU)在本模块内不产生结果:
// 它们由 cpu21_riscv_muldiv 多周期单元完成,顶层用 ex_muldiv 选择其输出。
ALU_MUL: ;
ALU_DIVU: ;
ALU_ADD: result = a + b;
ALU_SUB: result = a - b;
ALU_AND: result = a & b;
ALU_OR: result = a | b;
ALU_XOR: result = a ^ b;
ALU_NOR: result = ~(a | b);
ALU_SLT: result = ($signed(a) < $signed(b)) ? 32'd1 : 32'd0;
ALU_SLTU: result = (a < b) ? 32'd1 : 32'd0;
ALU_MULH: ;
ALU_MULHSU: ;
ALU_PASSB: result = b;
default: result = 32'b0;
endcase
end
endmodule
`default_nettype wire
@@ -0,0 +1,68 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// EX 级控制流裁决单元。
//
// 条件分支的实际方向与目标都在 EX 级解析;JAL/JALR 的目标也只在 EX 级
// 得到,因此它们总是需要重定向。条件分支仅在预测方向/目标与实际不符时
// 才重定向,这样预测正确时不会产生气泡。
// -----------------------------------------------------------------------------
module cpu21_riscv_branch_unit (
input wire valid_i,
input wire [31:0] pc_i,
input wire [31:0] imm_i,
input wire [31:0] src1_i,
input wire [31:0] src2_i,
input wire branch_i,
input wire beq_i,
input wire bne_i,
input wire blt_i,
input wire bge_i,
input wire bltu_i,
input wire bgeu_i,
input wire jal_i,
input wire jalr_i,
// IF 级携带过来的预测信息
input wire pred_taken_i,
input wire [31:0] pred_target_i,
// 裁决结果
output wire branch_taken_o,
output wire controlflow_o,
output wire [31:0] target_o,
output wire redirect_valid_o,
output wire [31:0] redirect_pc_o
);
// 比较结果预先算好,避免表达式过长被自动换行拆散。
wire lt_signed = $signed(src1_i) < $signed(src2_i);
wire ge_signed = $signed(src1_i) >= $signed(src2_i);
wire lt_unsigned = src1_i < src2_i;
wire ge_unsigned = src1_i >= src2_i;
// 条件分支裁决:beq 相等、bne 不等、blt/bge 有符号比较、bltu/bgeu 无符号比较。
assign branch_taken_o = branch_i &&
((beq_i && (src1_i == src2_i)) ||
(bne_i && (src1_i != src2_i)) ||
(blt_i && lt_signed) ||
(bge_i && ge_signed) ||
(bltu_i && lt_unsigned) ||
(bgeu_i && ge_unsigned));
// 跳转目标:jalr 为 (rs1+imm) 且最低位清零;jal 为 PC+imm。
assign target_o = jalr_i ? ((src1_i + imm_i) & 32'hffff_fffe) : (pc_i + imm_i);
// EX 级存在需要裁决的控制流指令。
assign controlflow_o = valid_i && (branch_i || jal_i || jalr_i);
// 条件分支的预测方向/目标与实际不符,或 JAL/JALR 必须重定向。
assign redirect_valid_o = controlflow_o &&
(branch_i ?
((pred_taken_i != branch_taken_o) ||
(pred_taken_i && branch_taken_o &&
(pred_target_i != target_o))) : 1'b1);
// 预测了跳转但实际不跳转时,回到顺序地址继续取指。
assign redirect_pc_o = (branch_i && !branch_taken_o) ? (pc_i + 32'd4) : target_o;
endmodule
`default_nettype wire
@@ -0,0 +1,309 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// ID 级组合译码器。
//
// 与原始 Logisim 控制器一致:操作码取标准 RISC-V 的 IR[6:2] 五位字段,
// 真值表中以十六进制形式存储该字段。
// 所有输出在无匹配时保持"无操作/不写回"的默认值。
// -----------------------------------------------------------------------------
module cpu21_riscv_decoder (
input wire [31:0] ir_i,
output reg [ 4:0] rs1_idx_o,
output reg [ 4:0] rs2_idx_o,
output reg [ 4:0] rd_o,
output reg [31:0] imm_o,
output reg [ 3:0] alu_op_o,
output reg [ 2:0] wb_sel_o,
output reg [ 2:0] md_op_o,
output wire [11:0] csr_addr_o,
output reg uses_rs1_o,
output reg uses_rs2_o,
output reg reg_write_o,
output reg mem_to_reg_o,
output reg mem_write_o,
output reg mem_byte_o,
output reg alu_src_o,
output reg branch_o,
output reg beq_o,
output reg bne_o,
output reg blt_o,
output reg bge_o,
output reg bltu_o,
output reg bgeu_o,
output reg jal_o,
output reg jalr_o,
output reg ecall_o,
output reg uret_o,
output reg csr_set_o,
output reg csr_clear_o,
output reg csr_write_o
);
// CPU21 自定义操作码(取自所提供的真值表)。
localparam OP_LOAD = 5'h00;
localparam OP_R = 5'h0c;
localparam OP_I = 5'h04;
localparam OP_STORE = 5'h08;
localparam OP_JALR = 5'h19;
localparam OP_BRANCH = 5'h18;
localparam OP_JAL = 5'h1b;
localparam OP_SYS = 5'h1c;
// LUI 为扩展指令(标准 RISC-V opcode 0x37 -> IR[6:2]=0x0d)。
localparam OP_LUI = 5'h0d;
localparam ALU_SLL = 4'd0;
localparam ALU_SRA = 4'd1;
localparam ALU_SRL = 4'd2;
localparam ALU_MUL = 4'd3;
localparam ALU_DIVU = 4'd4;
localparam ALU_ADD = 4'd5;
localparam ALU_SUB = 4'd6;
localparam ALU_AND = 4'd7;
localparam ALU_OR = 4'd8;
localparam ALU_XOR = 4'd9;
localparam ALU_SLT = 4'd11;
localparam ALU_SLTU = 4'd12;
localparam ALU_MULH = 4'd13;
localparam ALU_MULHSU = 4'd14;
localparam ALU_PASSB = 4'd15; // LUI:直接输出 B 端(立即数)
// 乘除法单元(cpu21_riscv_muldiv)的操作编码,MD_NONE 表示非乘除法指令。
localparam [2:0] MD_NONE = 3'd0;
localparam [2:0] MD_MUL = 3'd1;
localparam [2:0] MD_MULH = 3'd2;
localparam [2:0] MD_MULHSU = 3'd3;
localparam [2:0] MD_MULHU = 3'd4;
localparam [2:0] MD_DIVU = 3'd5;
localparam [2:0] MD_REMU = 3'd6;
wire [4:0] opcode = ir_i[6:2];
wire [2:0] funct3 = ir_i[14:12];
wire [6:0] funct7 = ir_i[31:25];
// CSR 地址字段在译码时直接旁路输出(供 EX 级读取 CSR)。
assign csr_addr_o = ir_i[31:20];
// 默认控制信号:全部为"无操作/不写回",再由下面的 case 覆盖。
always @* begin
rs1_idx_o = ir_i[19:15];
rs2_idx_o = ir_i[24:20];
rd_o = ir_i[11:7];
imm_o = {{20{ir_i[31]}}, ir_i[31:20]};
alu_op_o = ALU_ADD;
wb_sel_o = 3'd0;
md_op_o = MD_NONE;
uses_rs1_o = 1'b0;
uses_rs2_o = 1'b0;
reg_write_o = 1'b0;
mem_to_reg_o = 1'b0;
mem_write_o = 1'b0;
mem_byte_o = 1'b0;
alu_src_o = 1'b0;
branch_o = 1'b0;
beq_o = 1'b0;
bne_o = 1'b0;
blt_o = 1'b0;
bge_o = 1'b0;
bltu_o = 1'b0;
bgeu_o = 1'b0;
jal_o = 1'b0;
jalr_o = 1'b0;
ecall_o = 1'b0;
uret_o = 1'b0;
csr_set_o = 1'b0;
csr_clear_o = 1'b0;
csr_write_o = 1'b0;
case (opcode)
// R 型运算:由 funct3/funct7 决定具体操作。
OP_R: begin
uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1;
reg_write_o = 1'b1;
// M 扩展(funct7=0000001):mul/mulh/mulhsu/mulhu,以及课程真值表里的
// divu(funct3=101,商)与 remu(funct3=111,余数)。它们都由多周期
// 乘除法单元完成,这里只给出操作类型 md_op_o;mulhu 与 remu 依旧用
// wb_sel=3 取 result2(高位乘积 / 余数)。
if (funct7 == 7'b0000001) begin
case (funct3)
3'b000: begin // mul:低 32 位
alu_op_o = ALU_MUL;
md_op_o = MD_MUL;
end
3'b001: begin // mulh:有符号高 32 位
alu_op_o = ALU_MULH;
md_op_o = MD_MULH;
end
3'b010: begin // mulhsu:有符号 x 无符号高 32 位
alu_op_o = ALU_MULHSU;
md_op_o = MD_MULHSU;
end
3'b011: begin // mulhu:无符号高 32 位
alu_op_o = ALU_MUL;
md_op_o = MD_MULHU;
wb_sel_o = 3'd3;
end
3'b101: begin // divu:无符号商
alu_op_o = ALU_DIVU;
md_op_o = MD_DIVU;
end
3'b111: begin // remu:无符号余数
alu_op_o = ALU_DIVU;
md_op_o = MD_REMU;
wb_sel_o = 3'd3;
end
default: reg_write_o = 1'b0;
endcase
end else begin
// 标准 R 型 funct3 译码;add/sub 与 sra/srl 由 funct7[5] 区分。
case (funct3)
3'b000: alu_op_o = (funct7[5] ? ALU_SUB : ALU_ADD);
3'b001: alu_op_o = ALU_SLL;
3'b010: alu_op_o = ALU_SLT;
3'b011: alu_op_o = ALU_SLTU;
3'b100: alu_op_o = ALU_XOR;
3'b101: alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL);
3'b110: alu_op_o = ALU_OR;
3'b111: alu_op_o = ALU_AND;
default: reg_write_o = 1'b0;
endcase
end
end
// I 型运算(addi/slli/slti/sltiu/xori/srai/srli/ori/andi)。
OP_I: begin
uses_rs1_o = 1'b1;
alu_src_o = 1'b1;
reg_write_o = 1'b1;
case (funct3)
3'b000: alu_op_o = ALU_ADD; // addi:立即数加
3'b001: alu_op_o = ALU_SLL; // slli:立即数逻辑左移
3'b010: alu_op_o = ALU_SLT; // slti:有符号小于置 1
3'b011: alu_op_o = ALU_SLTU; // sltiu:无符号小于置 1(立即数符号扩展)
3'b100: alu_op_o = ALU_XOR; // xori:立即数异或
3'b101:
alu_op_o = (funct7[5] ? ALU_SRA : ALU_SRL); // srai/srli:立即数算术/逻辑右移
3'b110: alu_op_o = ALU_OR; // ori:立即数或
3'b111: alu_op_o = ALU_AND; // andi:立即数与
default: reg_write_o = 1'b0;
endcase
end
// 加载高位立即数 lui:rd = imm[31:12] << 12。
// 不读寄存器;用 ALU_SRC 选立即数,ALU 直通 B 端输出。
OP_LUI: begin
reg_write_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_PASSB;
imm_o = {ir_i[31:12], 12'b0};
wb_sel_o = 3'd0;
end
// 加载指令,当前仅支持 lw(funct3=010)。
OP_LOAD: begin
if (funct3 == 3'b010) begin
uses_rs1_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_ADD;
mem_to_reg_o = 1'b1;
reg_write_o = 1'b1;
wb_sel_o = 3'd1;
end
end
// 存储指令:sw(funct3=010)与 sb(funct3=000)。
OP_STORE: begin
if ((funct3 == 3'b010) || (funct3 == 3'b000)) begin
uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_ADD;
mem_write_o = 1'b1;
mem_byte_o = (funct3 == 3'b000); // sb:字节存储
imm_o = {{20{ir_i[31]}}, ir_i[31:25], ir_i[11:7]};
end
end
// 条件分支:beq(000)/bne(001)/blt(100)/bge(101)/bltu(110)/bgeu(111)。
OP_BRANCH: begin
if ((funct3 == 3'b000) || (funct3 == 3'b001) || (funct3 == 3'b100) ||
(funct3 == 3'b101) || (funct3 == 3'b110) || (funct3 == 3'b111)) begin
uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1;
branch_o = 1'b1;
beq_o = (funct3 == 3'b000);
bne_o = (funct3 == 3'b001);
blt_o = (funct3 == 3'b100);
bge_o = (funct3 == 3'b101);
bltu_o = (funct3 == 3'b110);
bgeu_o = (funct3 == 3'b111);
// 有符号比较用 ALU_SLT、无符号比较用 ALU_SLTU,相等/不等用减法;
// 分支是否成立由 EX 级的分支裁决单元决定,这里只与真值表保持一致。
alu_op_o = (blt_o || bge_o) ? ALU_SLT : ((bltu_o || bgeu_o) ? ALU_SLTU : ALU_SUB);
imm_o = {{19{ir_i[31]}}, ir_i[31], ir_i[7], ir_i[30:25], ir_i[11:8], 1'b0};
end
end
// 无条件跳转并链接:jal(写回 PC+4)。
OP_JAL: begin
jal_o = 1'b1;
reg_write_o = 1'b1;
wb_sel_o = 3'd2;
imm_o = {{11{ir_i[31]}}, ir_i[31], ir_i[19:12], ir_i[20], ir_i[30:21], 1'b0};
end
// 寄存器间接跳转并链接:jalr。
OP_JALR: begin
if (funct3 == 3'b000) begin
jalr_o = 1'b1;
uses_rs1_o = 1'b1;
alu_src_o = 1'b1;
alu_op_o = ALU_ADD;
reg_write_o = 1'b1;
wb_sel_o = 3'd2;
end
end
// 系统指令:ecall / uret / CSR 读写。
OP_SYS: begin
// 电路用 IR[21] 区分 URET 与 ecall。
if (funct3 == 3'b000) begin
if (ir_i[21]) begin
uret_o = 1'b1;
end else begin
ecall_o = 1'b1;
// 按文档说明,ecall 读取 a7(rs17) 与 a0(rs10)。
uses_rs1_o = 1'b1;
uses_rs2_o = 1'b1;
rs1_idx_o = 5'd17;
rs2_idx_o = 5'd10;
end
end else if (funct3 == 3'b001) begin
csr_write_o = 1'b1; // CSRRW:写 CSR,并把旧值写回 rd
uses_rs1_o = 1'b1;
alu_src_o = 1'b1;
imm_o = {27'b0, ir_i[19:15]};
reg_write_o = (rd_o != 5'd0);
wb_sel_o = 3'd4;
end else if (funct3 == 3'b110) begin
csr_set_o = 1'b1; // CSRRSI:置位 CSR 中的指定位
imm_o = {27'b0, ir_i[19:15]};
reg_write_o = (rd_o != 5'd0);
wb_sel_o = 3'd4;
end else if (funct3 == 3'b111) begin
csr_clear_o = 1'b1; // CSRRCI:清除 CSR 中的指定位
imm_o = {27'b0, ir_i[19:15]};
reg_write_o = (rd_o != 5'd0);
wb_sel_o = 3'd4;
end
end
default: begin
end
endcase
end
endmodule
`default_nettype wire
@@ -0,0 +1,47 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// EX 级前递单元。
//
// 优先级:EX/MEM 级的结果最新 -> 其次 MEM/WB 级写回值 -> 最后是 ID 级
// 译码时读到的寄存器值。load 结果在 MEM 级之后才可用,因此 EX/MEM 的
// load(mem_to_reg)不参与前递,改由冒险单元插入一个气泡。
// -----------------------------------------------------------------------------
module cpu21_riscv_forward_unit (
input wire [31:0] rs1_value_i,
input wire [31:0] rs2_value_i,
input wire [ 4:0] rs1_idx_i,
input wire [ 4:0] rs2_idx_i,
// EX/MEM 级
input wire exmem_valid_i,
input wire exmem_reg_write_i,
input wire exmem_mem_to_reg_i,
input wire [ 4:0] exmem_rd_i,
input wire [31:0] exmem_value_i,
// MEM/WB 级
input wire memwb_valid_i,
input wire memwb_reg_write_i,
input wire [ 4:0] memwb_rd_i,
input wire [31:0] memwb_value_i,
// 前递后的操作数
output reg [31:0] src1_o,
output reg [31:0] src2_o
);
wire ex_mem_forward_valid = exmem_valid_i && exmem_reg_write_i &&
!exmem_mem_to_reg_i && (exmem_rd_i != 5'd0);
wire wb_forward_valid = memwb_valid_i && memwb_reg_write_i && (memwb_rd_i != 5'd0);
always @* begin
src1_o = rs1_value_i;
src2_o = rs2_value_i;
if (ex_mem_forward_valid && (exmem_rd_i == rs1_idx_i)) src1_o = exmem_value_i;
else if (wb_forward_valid && (memwb_rd_i == rs1_idx_i)) src1_o = memwb_value_i;
if (ex_mem_forward_valid && (exmem_rd_i == rs2_idx_i)) src2_o = exmem_value_i;
else if (wb_forward_valid && (memwb_rd_i == rs2_idx_i)) src2_o = memwb_value_i;
end
endmodule
`default_nettype wire
@@ -0,0 +1,46 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 冒险/流水控制单元。
//
// load-use 冒险:EX 级是 load 且 ID 级指令马上要用它的结果时,插入一个
// 气泡(停顿一拍)。其余数据相关由前递网络解决。
// 冲刷(flush_younger)优先级高于停顿:中断、URET、ecall 停机、分支
// 重定向都会丢弃年轻指令,此时不需要再插气泡。
// -----------------------------------------------------------------------------
module cpu21_riscv_hazard_unit (
input wire ifid_valid_i,
input wire idex_valid_i,
input wire idex_mem_to_reg_i,
input wire [4:0] idex_rd_i,
input wire d_uses_rs1_i,
input wire d_uses_rs2_i,
input wire [4:0] d_src1_idx_i,
input wire [4:0] d_src2_idx_i,
// 冲刷来源
input wire take_irq_i,
input wire uret_redirect_i,
input wire halt_event_i,
input wire redirect_valid_i,
input wire halted_i,
// 控制结果
output wire load_use_hazard_o,
output wire flush_younger_o,
output wire pipeline_stall_o
);
// EX 级为 load,且 ID 级的源寄存器与它的目的寄存器相同。
assign load_use_hazard_o = ifid_valid_i && idex_valid_i && idex_mem_to_reg_i &&
(idex_rd_i != 5'd0) &&
((d_uses_rs1_i && (d_src1_idx_i == idex_rd_i)) ||
(d_uses_rs2_i && (d_src2_idx_i == idex_rd_i)));
assign flush_younger_o = take_irq_i || uret_redirect_i || halt_event_i || redirect_valid_i;
// 停顿条件里不再包含 flush_younger:冲刷时 valid 位会被清 0、PC/IF-ID 也由
// 冲刷分支(优先级高于停顿)处理,同时发生停顿只会多保持一拍 payload,
// 不影响正确性。这样可以避免把“冲刷”这条长路径引到 payload 的时钟使能上。
assign pipeline_stall_o = load_use_hazard_o && !halted_i;
endmodule
`default_nettype wire
@@ -0,0 +1,188 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 中断控制器 + ustatus/uepc CSR + 嵌套返回栈。
//
// IRQ3 优先级最高,其次 IRQ2,最后 IRQ1;嵌套请求只有优先级高于当前级别
// 才会被接纳。中断入口地址由优先级选出,返回地址保存在 epc_stack 中,
// 当前上下文的活动返回 PC 放在 uepc_q(与课程讲义用 CSRRW 保存/恢复
// uepc 的流程一致)。
// -----------------------------------------------------------------------------
module cpu21_riscv_irq_ctrl #(
parameter [31:0] IRQ1_VECTOR = 32'h0000_30ac,
parameter [31:0] IRQ2_VECTOR = 32'h0000_31e4,
parameter [31:0] IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4,
// ustatus 复位值。bit0 为 MIE。课程的中断测试程序只会在中断处理
// 程序中设置 MIE,若复位后不立即置位 MIE,主程序将永远无法响应
// 第一次中断,因此这里默认打开 MIE。
parameter [31:0] USTATUS_INIT = 32'h0000_0001
) (
input wire clk,
input wire reset,
input wire [ 2:0] irq_i,
input wire halted_i,
// EX 级正在处理重定向/URET/停机,此时不允许响应中断
input wire ex_block_i,
// 中断发生时要保存的返回地址(由顶层根据流水线状态给出)
input wire [31:0] save_pc_i,
// EX 级的 URET
input wire uret_i,
// EX 级 CSR 指令(CSRRW/CSRRSI/CSRRCI)
input wire csr_valid_i,
input wire csr_set_i,
input wire csr_clear_i,
input wire csr_write_i,
input wire [11:0] csr_addr_i,
input wire [31:0] csr_imm_i,
input wire [31:0] csr_wdata_i,
// 控制结果
output wire take_irq_o,
output wire [31:0] vector_o,
output wire [31:0] return_pc_o,
output wire [ 2:0] pending_o,
output wire [ 1:0] level_o,
output wire [31:0] uepc_o,
output wire [31:0] ustatus_o
);
reg [31:0] ustatus_q;
reg [31:0] uepc_q;
reg [ 1:0] irq_current_q;
reg [ 2:0] irq_pending_q;
reg [ 2:0] irq_sync1_q;
reg [ 2:0] irq_sync2_q;
reg [ 2:0] irq_prev_q;
reg [31:0] epc_stack [0:IRQ_STACK_DEPTH-1];
reg [31:0] status_stack [0:IRQ_STACK_DEPTH-1];
reg [ 1:0] priority_stack [0:IRQ_STACK_DEPTH-1];
reg [ 2:0] irq_depth_q;
reg irq_selected_valid;
reg [ 1:0] irq_selected_level;
reg [31:0] irq_vector;
reg [ 2:0] irq_pending_d;
reg [31:0] ustatus_d;
reg [31:0] uepc_d;
integer s;
// 上升沿检测:只在 irq_i 由 0 变 1 的那一拍产生中断事件。
wire [ 2:0] irq_event = irq_sync2_q & ~irq_prev_q;
// 优先级仲裁:在当前级别允许的条件下选出优先级最高的中断源。
always @* begin
irq_selected_valid = 1'b0;
irq_selected_level = 2'd0;
if (irq_pending_q[2] && (irq_current_q < 2'd3)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd3;
end else if (irq_pending_q[1] && (irq_current_q < 2'd2)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd2;
end else if (irq_pending_q[0] && (irq_current_q < 2'd1)) begin
irq_selected_valid = 1'b1;
irq_selected_level = 2'd1;
end
end
// 依据被选中的优先级选择对应的中断入口地址。
always @* begin
case (irq_selected_level)
2'd1: irq_vector = IRQ1_VECTOR;
2'd2: irq_vector = IRQ2_VECTOR;
2'd3: irq_vector = IRQ3_VECTOR;
default: irq_vector = IRQ1_VECTOR;
endcase
end
// 响应中断的条件:有已选中中断、MIE 使能、栈未溢出、未停机,且没有
// 正在处理的重定向/URET/停机事件。
assign take_irq_o = irq_selected_valid && ustatus_q[0] &&
(irq_depth_q < IRQ_STACK_DEPTH) && !halted_i &&
!ex_block_i;
assign vector_o = irq_vector;
assign return_pc_o = uepc_q;
assign pending_o = irq_pending_q;
assign level_o = irq_current_q;
assign uepc_o = uepc_q;
assign ustatus_o = ustatus_q;
// 组合逻辑计算下一拍的挂起位与 CSR(ustatus/uepc)取值。
always @* begin
irq_pending_d = irq_pending_q | irq_event;
if (take_irq_o) begin
case (irq_selected_level)
2'd1: irq_pending_d[0] = 1'b0;
2'd2: irq_pending_d[1] = 1'b0;
2'd3: irq_pending_d[2] = 1'b0;
default: irq_pending_d = irq_pending_d;
endcase
end
ustatus_d = ustatus_q;
uepc_d = uepc_q;
if (csr_valid_i && csr_set_i) begin
if (csr_addr_i == 12'h004) ustatus_d = ustatus_q | csr_imm_i;
else if (csr_addr_i == 12'h041) uepc_d = uepc_q | csr_imm_i;
end else if (csr_valid_i && csr_clear_i) begin
if (csr_addr_i == 12'h004) ustatus_d = ustatus_q & ~csr_imm_i;
else if (csr_addr_i == 12'h041) uepc_d = uepc_q & ~csr_imm_i;
end else if (csr_valid_i && csr_write_i) begin
if (csr_addr_i == 12'h004) ustatus_d = csr_wdata_i;
else if (csr_addr_i == 12'h041) uepc_d = csr_wdata_i;
end
if (take_irq_o) ustatus_d[0] = 1'b0;
else if (uret_i && (irq_depth_q != 0)) ustatus_d = status_stack[irq_depth_q-1'b1];
if (take_irq_o) uepc_d = save_pc_i;
else if (uret_i && (irq_depth_q > 1)) uepc_d = epc_stack[irq_depth_q-2];
else if (uret_i && (irq_depth_q == 1)) uepc_d = 32'b0;
end
// 复位时清空中断状态与嵌套栈。
always @(posedge clk or posedge reset) begin
if (reset) begin
ustatus_q <= USTATUS_INIT;
uepc_q <= 32'b0;
irq_current_q <= 2'b0;
irq_pending_q <= 3'b0;
irq_sync1_q <= 3'b0;
irq_sync2_q <= 3'b0;
irq_prev_q <= 3'b0;
irq_depth_q <= 3'b0;
for (s = 0; s < IRQ_STACK_DEPTH; s = s + 1) begin
epc_stack[s] <= 32'b0;
status_stack[s] <= 32'b0;
priority_stack[s] <= 2'b0;
end
end else begin
// 中断输入打两拍同步,并锁存挂起状态与 CSR。
irq_sync1_q <= irq_i;
irq_sync2_q <= irq_sync1_q;
irq_prev_q <= irq_sync2_q;
irq_pending_q <= irq_pending_d;
ustatus_q <= ustatus_d;
uepc_q <= uepc_d;
if (take_irq_o) begin
if (irq_depth_q < IRQ_STACK_DEPTH) begin
epc_stack[irq_depth_q] <= save_pc_i;
status_stack[irq_depth_q] <= ustatus_q;
priority_stack[irq_depth_q] <= irq_current_q;
irq_depth_q <= irq_depth_q + 3'd1;
end
irq_current_q <= irq_selected_level;
end else if (uret_i) begin
if (irq_depth_q != 0) begin
irq_depth_q <= irq_depth_q - 3'd1;
irq_current_q <= priority_stack[irq_depth_q-1'b1];
end
end
end
end
endmodule
`default_nettype wire
+169
View File
@@ -0,0 +1,169 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 多周期乘除法单元(时序逻辑,32 拍迭代)
//
// 原来乘除法直接写在 ALU 里(组合的 a*b / a%b):综合后关键路径约 94 ns
// (325 级逻辑、286 个 CARRY4),100 MHz 下无法收敛,只能把板上 100 MHz 分频
// 到 6.25 MHz 再跑。这里改成移位迭代:
// * 乘法:32 拍移位加法得到 64 位乘积;有符号变体先取操作数绝对值,
// 迭代结束后按符号对 64 位乘积取负。
// * 除法:32 拍恢复余数法得到商与余数;除零按 RISC-V 约定处理
// (商为全 1、余数为被除数)。DIVU/REMU 都是无符号运算,不需要符号修正。
//
// 顶层的 EX 级会停留若干拍(往 EX/MEM 插气泡)等待 done_o 有效,然后照常写回。
// 迭代期间顶层会屏蔽中断,避免在结果未定型时提前推进流水线。
//
// op_i 编码(由 cpu21_riscv_decoder 给出,MD_NONE 表示不是乘除法指令):
// MD_MUL mul :无符号乘积低 32 位(有符号乘法的低 32 位与之相同)
// MD_MULH mulh :有符号 x 有符号,取高 32 位
// MD_MULHSU mulhsu :有符号 x 无符号,取高 32 位
// MD_MULHU mulhu :无符号 x 无符号,取高 32 位
// MD_DIVU divu :无符号商
// MD_REMU remu :无符号余数
//
// result_o / result2_o 的含义与原来的 ALU 保持一致:乘法时 result_o 为低 32 位、
// result2_o 为高 32 位;除法时 result_o 为商、result2_o 为余数。译码器用 wb_sel
// 决定写回哪一个(mulhu、remu 用 wb_sel=3 取 result2),因此顶层只需把这一对
// 结果接到原来的 EX 结果总线上,其余流水逻辑不用改。
// -----------------------------------------------------------------------------
module cpu21_riscv_muldiv (
input wire clk,
input wire reset, // 异步复位,高有效(与顶层一致)
input wire start_i, // 指令在 EX 级且是乘除法(电平,模块内部只在空闲时响应)
input wire [2:0] op_i, // 乘除法操作类型
input wire [31:0] a_i, // EX 级 src1
input wire [31:0] b_i, // EX 级 src2(ALU 的 B 端)
output wire busy_o, // 迭代中(不含结果定型那一拍)
output wire done_o, // 结果有效(单拍,此时顶层推进流水线)
output wire [31:0] result_o, // 主结果
output wire [31:0] result2_o // 第二结果(乘法高 32 位 / 除法余数)
);
localparam [2:0] MD_NONE = 3'd0;
localparam [2:0] MD_MUL = 3'd1;
localparam [2:0] MD_MULH = 3'd2;
localparam [2:0] MD_MULHSU = 3'd3;
localparam [2:0] MD_MULHU = 3'd4;
localparam [2:0] MD_DIVU = 3'd5;
localparam [2:0] MD_REMU = 3'd6;
localparam [1:0] S_IDLE = 2'd0;
localparam [1:0] S_RUN = 2'd1;
localparam [1:0] S_DONE = 2'd2;
reg [ 1:0] state_q;
reg [ 5:0] cnt_q; // 0..32:0..31 为迭代,32 为结果定型
reg [ 2:0] op_q;
reg is_div_q;
// 乘法数据通路:64 位被乘数(每拍左移)、32 位乘数(每拍右移)、64 位累加器。
reg [63:0] mcand_q;
reg [31:0] mplier_q;
reg [63:0] prod_q;
reg sign_q;
// 除法数据通路:32 位除数、32 位商/被除数移位寄存器、33 位余数。
reg [31:0] dvsr_q;
reg [31:0] quo_q;
reg [32:0] rem_q;
reg [31:0] dvd_q; // 原始被除数(除零时原样返回)
reg div_zero_q;
// 结果寄存器。
reg [31:0] res_q;
reg [31:0] res2_q;
assign busy_o = (state_q == S_RUN);
assign done_o = (state_q == S_DONE);
assign result_o = res_q;
assign result2_o = res2_q;
// 乘法操作数准备:按操作类型决定是否取绝对值,并给出结果符号。
wire a_signed = (op_i == MD_MULH) || (op_i == MD_MULHSU);
wire b_signed = (op_i == MD_MULH);
wire [31:0] a_mag = (a_signed && a_i[31]) ? (~a_i + 32'd1) : a_i;
wire [31:0] b_mag = (b_signed && b_i[31]) ? (~b_i + 32'd1) : b_i;
wire sign_in = ((op_i == MD_MULH) && (a_i[31] ^ b_i[31])) || ((op_i == MD_MULHSU) && a_i[31]);
// 除法的一步(恢复余数法):把被除数最高位移进余数,够减则减并把商位置 1。
wire [32:0] rem_shift = {rem_q[31:0], quo_q[31]};
wire rem_ge = (rem_shift >= {1'b0, dvsr_q});
wire [32:0] rem_next = rem_ge ? (rem_shift - {1'b0, dvsr_q}) : rem_shift;
wire [31:0] quo_next = {quo_q[30:0], rem_ge};
// 迭代结束时的符号修正:prod_mag 为无符号乘积,sign_q 为 1 时取负。
wire [63:0] prod_fix = sign_q ? (~prod_q + 64'd1) : prod_q;
always @(posedge clk or posedge reset) begin
if (reset) begin
state_q <= S_IDLE;
cnt_q <= 6'd0;
op_q <= MD_NONE;
is_div_q <= 1'b0;
mcand_q <= 64'd0;
mplier_q <= 32'd0;
prod_q <= 64'd0;
sign_q <= 1'b0;
dvsr_q <= 32'd0;
quo_q <= 32'd0;
rem_q <= 33'd0;
dvd_q <= 32'd0;
div_zero_q <= 1'b0;
res_q <= 32'd0;
res2_q <= 32'd0;
end else begin
case (state_q)
// 空闲:指令进入 EX 且是乘除法时装载操作数,下一拍开始迭代。
S_IDLE: begin
if (start_i) begin
state_q <= S_RUN;
cnt_q <= 6'd0;
op_q <= op_i;
is_div_q <= (op_i == MD_DIVU) || (op_i == MD_REMU);
// 乘法初始化(先取绝对值,符号留到最后修正)。
mcand_q <= {32'b0, a_mag};
mplier_q <= b_mag;
prod_q <= 64'd0;
sign_q <= sign_in;
// 除法初始化(无符号)。
dvsr_q <= b_i;
quo_q <= a_i;
rem_q <= 33'd0;
dvd_q <= a_i;
div_zero_q <= (b_i == 32'd0);
end
end
// 迭代:cnt 0..31 各走一步,32 拍后把结果定型。
S_RUN: begin
if (cnt_q == 6'd32) begin
state_q <= S_DONE;
if (is_div_q) begin
res_q <= div_zero_q ? 32'hffff_ffff : quo_q;
res2_q <= div_zero_q ? dvd_q : rem_q[31:0];
end else begin
res_q <= (op_q == MD_MULHU || op_q == MD_MUL) ? prod_fix[31:0] : prod_fix[63:32];
res2_q <= prod_fix[63:32];
end
end else begin
cnt_q <= cnt_q + 6'd1;
if (is_div_q) begin
rem_q <= rem_next;
quo_q <= quo_next;
end else begin
prod_q <= mplier_q[0] ? (prod_q + mcand_q) : prod_q;
mcand_q <= {mcand_q[62:0], 1'b0};
mplier_q <= {1'b0, mplier_q[31:1]};
end
end
end
// 结果有效一拍,顶层据此推进流水线;随后回到空闲。
default: state_q <= S_IDLE;
endcase
end
end
endmodule
`default_nettype wire
@@ -0,0 +1,76 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 性能计数器:周期数、停顿/气泡数、条件分支跳转数、无条件跳转数,
// 以及分支预测的成功/失败次数。
//
// 这些计数器只影响可观测的统计量,不参与处理器控制,便于在波形中单独
// 观察流水线效率。
// -----------------------------------------------------------------------------
module cpu21_riscv_perf_counters (
input wire clk,
input wire reset,
input wire inc_cycle_i, // 未停机时周期数 +1
input wire stall_i, // load-use 停顿或多周期乘除法停留
input wire flush_i, // 冲刷年轻指令
input wire cond_taken_i, // EX 级条件分支实际跳转
input wire cond_valid_i, // EX 级条件分支有效
input wire mispredict_i, // EX 级条件分支预测失败
input wire uncond_i, // EX 级 JAL/JALR
output wire [15:0] cycle_count_o,
output wire [15:0] stall_count_o,
output wire [15:0] bubble_count_o,
output wire [15:0] conditional_taken_count_o,
output wire [15:0] unconditional_branch_count_o,
output wire [15:0] prediction_success_count_o,
output wire [15:0] prediction_failure_count_o
);
reg [15:0] cycle_count_q;
reg [15:0] stall_count_q;
reg [15:0] bubble_count_q;
reg [15:0] conditional_taken_count_q;
reg [15:0] unconditional_branch_count_q;
reg [15:0] prediction_success_count_q;
reg [15:0] prediction_failure_count_q;
always @(posedge clk or posedge reset) begin
if (reset) begin
cycle_count_q <= 16'b0;
stall_count_q <= 16'b0;
bubble_count_q <= 16'b0;
conditional_taken_count_q <= 16'b0;
unconditional_branch_count_q <= 16'b0;
prediction_success_count_q <= 16'b0;
prediction_failure_count_q <= 16'b0;
end else begin
if (inc_cycle_i) cycle_count_q <= cycle_count_q + 16'd1;
if (stall_i) begin
stall_count_q <= stall_count_q + 16'd1;
bubble_count_q <= bubble_count_q + 16'd1;
end else if (flush_i) begin
// 一次冲刷丢弃 IF/ID 两条年轻指令,记两个气泡。
bubble_count_q <= bubble_count_q + 16'd2;
end
if (cond_taken_i) conditional_taken_count_q <= conditional_taken_count_q + 16'd1;
if (uncond_i) unconditional_branch_count_q <= unconditional_branch_count_q + 16'd1;
if (cond_valid_i) begin
if (mispredict_i) prediction_failure_count_q <= prediction_failure_count_q + 16'd1;
else prediction_success_count_q <= prediction_success_count_q + 16'd1;
end
end
end
assign cycle_count_o = cycle_count_q;
assign stall_count_o = stall_count_q;
assign bubble_count_o = bubble_count_q;
assign conditional_taken_count_o = conditional_taken_count_q;
assign unconditional_branch_count_o = unconditional_branch_count_q;
assign prediction_success_count_o = prediction_success_count_q;
assign prediction_failure_count_o = prediction_failure_count_q;
endmodule
`default_nettype wire
@@ -0,0 +1,895 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21-risc-v-4.circ -> 可综合 Verilog 实现(顶层)
//
// 处理器为五级流水线 IF/ID/EX/MEM/WB。本文件只保留:
// * 五级流水寄存器与各级之间的连线;
// * 各功能子模块的例化;
// * PC / IF-ID / ID-EX 的控制优先级(中断、URET、停机、重定向、停顿、
// 正常推进);
// * 对外输出与调试信号。
//
// 各功能单元已拆分为独立文件,便于单独仿真调试:
// cpu21_riscv_alu.v 组合 ALU(result2 用于 MUL 高位 / 余数)
// cpu21_bpb_8.v 八表项全相联分支目标缓冲
// cpu21_riscv_decoder.v ID 级组合译码器
// cpu21_riscv_regfile.v 寄存器堆(写优先旁路)
// cpu21_riscv_forward_unit.v EX 级前递网络
// cpu21_riscv_store_unit.v EX 级存储数据对齐
// cpu21_riscv_branch_unit.v EX 级控制流裁决
// cpu21_riscv_hazard_unit.v load-use 冒险与冲刷/停顿判定
// cpu21_riscv_irq_ctrl.v 中断控制、ustatus/uepc 与嵌套返回栈
// cpu21_riscv_perf_counters.v 性能计数器
// cpu21_mmio.v MMIO:LED 数据寄存器(可写、可读回)
//
// 主要特性:
// * EX 级前递(forwarding)与 load-use 冒险互锁;
// * EX 级控制流裁决与流水线重定向;
// * 八表项全相联分支目标缓冲(BPB);
// * 三路采样的中断源,带优先级与可嵌套的 URET 返回;
// * LED 显示通过 MMIO 完成:向 LED_ADDR(默认 0x0000_1000)存储即写 LED,
// 也可以从该地址读回 LED 当前值;
// * ecall 仍用于停机:a7==34 的历史“显示”调用保留为无操作,其余(如 a7=10)
// 令流水线停机并等待 go_i。
//
// 存储器接口约定:
// instr_addr_o 与 data_addr_o 均为字节地址。data_wstrb_o 是字节写掩码,
// 因此外部字宽 RAM 可用 data_addr_o[11:2] 作为字索引,再依据
// data_wstrb_o 完成字节合并。
// -----------------------------------------------------------------------------
module cpu21_riscv_redirect_int_bpb #(
parameter RESET_PC = 32'h0000_0000,
parameter IRQ1_VECTOR = 32'h0000_30ac,
parameter IRQ2_VECTOR = 32'h0000_31e4,
parameter IRQ3_VECTOR = 32'h0000_3310,
parameter IRQ_STACK_DEPTH = 4,
// MMIO:LED 数据寄存器地址(默认 0x0000_1000,位于 4 KB 数据 RAM 之外)。
parameter [31:0] LED_ADDR = 32'h0000_1000,
// ustatus 复位值。详见 cpu21_riscv_irq_ctrl 中的说明。
parameter [31:0] USTATUS_INIT = 32'h0000_0001
) (
input wire clk,
input wire reset,
input wire [31:0] instr_i,
input wire [31:0] data_rdata_i,
input wire [ 2:0] irq_i,
input wire go_i,
output wire [31:0] instr_addr_o,
output wire [31:0] data_addr_o,
output wire [31:0] data_wdata_o,
output wire [ 3:0] data_wstrb_o,
output wire data_we_o,
output wire [31:0] led_data_o,
output wire led_valid_o,
// ---- 扫雷扩展外设(由 cpu21_mmio 驱动;板级顶层接 VGA/数码管/鼠标)----
output wire [31:0] seg_data_o, // 七段数码管:8 位十六进制数
output wire seg_valid_o,
output wire fb_we_o, // 显存写端口(板级顶层的双口 RAM)
output wire [ 9:0] fb_waddr_o, // 显存字地址(0..299)
output wire [31:0] fb_wdata_o,
output wire [ 3:0] fb_wstrb_o,
input wire [31:0] fb_rdata_i, // 显存读回(组合)
output wire [31:0] disp_ctrl_o, // 显示控制器寄存器(给 VGA)
output wire [31:0] disp_cursor_o,
input wire mouse_ev_valid_i, // 鼠标事件源(板级顶层:按键去抖 / PS/2)
input wire [ 7:0] mouse_dx_i, // 单位 1/8 格,有符号
input wire [ 7:0] mouse_dy_i,
input wire [ 4:0] mouse_btn_i, // {BTNC,BTNU,BTND,BTNL,BTNR}
input wire [15:0] sw_i, // 板载拨码(软件读 INPUT 寄存器)
input wire frame_tick_i, // VGA 每帧一个脉冲
output wire mouse_irq_o, // 鼠标事件中断(板级顶层接 irq_i[0])
output wire level_irq_o, // 软件中断:关卡入口(接 irq_i[1])
output wire [31:0] if_pc_o,
output wire [31:0] id_pc_o,
output wire [31:0] ex_pc_o,
output wire [31:0] mem_pc_o,
output wire [31:0] wb_pc_o,
output wire [31:0] if_ir_o,
output wire [31:0] id_ir_o,
output wire [31:0] ex_ir_o,
output wire [31:0] mem_ir_o,
output wire [31:0] wb_ir_o,
output wire [31:0] rdin_o,
output wire [31:0] mdin_o,
output wire reg_write_o,
output wire mem_write_o,
output wire halted_o,
output wire [ 2:0] irq_pending_o,
output wire [ 1:0] irq_current_level_o,
output wire [31:0] uepc_o,
output wire [31:0] ustatus_o,
output wire bpb_predict_hit_o,
output wire bpb_predict_taken_o,
output wire bpb_mispredict_o,
output wire [15:0] cycle_count_o,
output wire [15:0] stall_count_o,
output wire [15:0] bubble_count_o,
output wire [15:0] conditional_taken_count_o,
output wire [15:0] unconditional_branch_count_o,
output wire [15:0] prediction_success_count_o,
output wire [15:0] prediction_failure_count_o
);
// 顶层只用到少量常量,完整译码表见 cpu21_riscv_decoder.v。
localparam OP_BRANCH = 5'h18;
localparam ALU_ADD = 4'd5;
// -------------------------------------------------------------------------
// 流水线寄存器
// -------------------------------------------------------------------------
// 当前取指 PC。
reg [31:0] pc_q;
// IF/ID 流水寄存器(含本条指令所携带的分支预测信息)。
reg ifid_valid_q;
reg [31:0] ifid_pc_q;
reg [31:0] ifid_ir_q;
reg ifid_pred_taken_q;
reg [31:0] ifid_pred_target_q;
// ID/EX 流水寄存器:保存译码结果与操作数。
reg idex_valid_q;
reg [31:0] idex_pc_q;
reg [31:0] idex_ir_q;
reg [31:0] idex_rs1_value_q;
reg [31:0] idex_rs2_value_q;
reg [4:0] idex_rs1_idx_q;
reg [4:0] idex_rs2_idx_q;
reg [4:0] idex_rd_q;
reg [31:0] idex_imm_q;
reg [3:0] idex_aluop_q;
reg idex_alu_src_q;
reg idex_reg_write_q;
reg idex_mem_to_reg_q;
reg idex_mem_write_q;
reg idex_mem_byte_q;
reg [2:0] idex_wb_sel_q;
reg [2:0] idex_md_op_q; // 乘除法操作类型(0=非乘除法)
reg [11:0] idex_csr_addr_q;
reg idex_branch_q;
reg idex_beq_q;
reg idex_bne_q;
reg idex_blt_q;
reg idex_bge_q;
reg idex_bltu_q;
reg idex_bgeu_q;
reg idex_jal_q;
reg idex_jalr_q;
reg idex_ecall_q;
reg idex_uret_q;
reg idex_csr_set_q;
reg idex_csr_clear_q;
reg idex_csr_write_q;
reg idex_pred_taken_q;
reg [31:0] idex_pred_target_q;
// EX/MEM 流水寄存器:保存 ALU/存储结果与写回控制。
reg exmem_valid_q;
reg [31:0] exmem_pc_q;
reg [31:0] exmem_ir_q;
reg [31:0] exmem_alu_result_q;
reg [31:0] exmem_alu_result2_q;
reg [31:0] exmem_csr_old_q;
reg [31:0] exmem_write_data_q;
reg [4:0] exmem_rd_q;
reg exmem_reg_write_q;
reg exmem_mem_to_reg_q;
reg exmem_mem_write_q;
reg [3:0] exmem_wstrb_q;
reg [2:0] exmem_wb_sel_q;
// MEM/WB 流水寄存器:保存访存数据与最终写回信息。
reg memwb_valid_q;
reg [31:0] memwb_pc_q;
reg [31:0] memwb_ir_q;
reg [31:0] memwb_alu_result_q;
reg [31:0] memwb_alu_result2_q;
reg [31:0] memwb_csr_old_q;
reg [31:0] memwb_mem_data_q;
reg [4:0] memwb_rd_q;
reg memwb_reg_write_q;
reg memwb_mem_to_reg_q;
reg [2:0] memwb_wb_sel_q;
// 停机标志(LED 寄存器已移入 cpu21_mmio.v)。
reg halted_q;
// -------------------------------------------------------------------------
// IF 级:分支预测。JAL/JALR 在 EX 级裁决,条件分支则可以直接依据 BPB
// 给出的目标地址重定向取指 PC。
// -------------------------------------------------------------------------
wire [4:0] f_opcode = instr_i[6:2];
wire [2:0] f_funct3 = instr_i[14:12];
wire f_is_branch = (f_opcode == OP_BRANCH) &&
((f_funct3 == 3'b000) || (f_funct3 == 3'b001) ||
(f_funct3 == 3'b100) || (f_funct3 == 3'b101) ||
(f_funct3 == 3'b110) || (f_funct3 == 3'b111));
wire [31:0] bpb_predict_target;
wire bpb_predict_hit;
wire bpb_predict_taken;
wire ex_branch_taken;
wire [31:0] ex_target;
wire ex_controlflow;
wire ex_redirect_valid;
wire [31:0] ex_redirect_pc;
// BPB 更新端口:在 EX 级用条件分支的实际结果训练预测器。
wire bpb_update_enable = idex_valid_q && idex_branch_q;
wire bpb_update_taken = ex_branch_taken;
wire [31:0] bpb_update_target = ex_target;
// 分支目标缓冲实例。
cpu21_bpb_8 u_bpb (
.clk (clk),
.reset (reset),
.predict_pc (pc_q),
.predict_enable (f_is_branch),
.predict_hit (bpb_predict_hit),
.predict_taken (bpb_predict_taken),
.predict_target (bpb_predict_target),
.update_enable (bpb_update_enable),
.update_controlflow(1'b1),
.update_pc (idex_pc_q),
.update_target (bpb_update_target),
.update_taken (bpb_update_taken)
);
// 下一取指 PC:预测跳转命中时使用 BPB 目标地址,否则顺序加 4。
wire [31:0] fetch_next_pc =
(f_is_branch && bpb_predict_hit && bpb_predict_taken) ?
bpb_predict_target : (pc_q + 32'd4);
// -------------------------------------------------------------------------
// ID 级:译码与寄存器堆读取
// -------------------------------------------------------------------------
wire [4:0] d_src1_idx;
wire [4:0] d_src2_idx;
wire [4:0] d_rd;
wire [31:0] d_imm;
wire [3:0] d_aluop;
wire [2:0] d_wb_sel;
wire [2:0] d_md_op;
wire [11:0] d_csr_addr;
wire d_uses_rs1;
wire d_uses_rs2;
wire d_reg_write;
wire d_mem_to_reg;
wire d_mem_write;
wire d_mem_byte;
wire d_alu_src;
wire d_branch;
wire d_beq;
wire d_bne;
wire d_blt;
wire d_bge;
wire d_bltu;
wire d_bgeu;
wire d_jal;
wire d_jalr;
wire d_ecall;
wire d_uret;
wire d_csr_set;
wire d_csr_clear;
wire d_csr_write;
cpu21_riscv_decoder u_decoder (
.ir_i (ifid_ir_q),
.rs1_idx_o (d_src1_idx),
.rs2_idx_o (d_src2_idx),
.rd_o (d_rd),
.imm_o (d_imm),
.alu_op_o (d_aluop),
.wb_sel_o (d_wb_sel),
.md_op_o (d_md_op),
.csr_addr_o (d_csr_addr),
.uses_rs1_o (d_uses_rs1),
.uses_rs2_o (d_uses_rs2),
.reg_write_o (d_reg_write),
.mem_to_reg_o(d_mem_to_reg),
.mem_write_o (d_mem_write),
.mem_byte_o (d_mem_byte),
.alu_src_o (d_alu_src),
.branch_o (d_branch),
.beq_o (d_beq),
.bne_o (d_bne),
.blt_o (d_blt),
.bge_o (d_bge),
.bltu_o (d_bltu),
.bgeu_o (d_bgeu),
.jal_o (d_jal),
.jalr_o (d_jalr),
.ecall_o (d_ecall),
.uret_o (d_uret),
.csr_set_o (d_csr_set),
.csr_clear_o (d_csr_clear),
.csr_write_o (d_csr_write)
);
// 写回值选择:wb_sel 0=ALU 结果,1=访存数据,2=PC+4(已存在 alu_result 里),
// 3=MUL 高位/余数,4=CSR 旧值。
wire [31:0] wb_value;
assign wb_value = (memwb_wb_sel_q == 3'd1) ? memwb_mem_data_q :
(memwb_wb_sel_q == 3'd3) ? memwb_alu_result2_q :
(memwb_wb_sel_q == 3'd4) ? memwb_csr_old_q :
memwb_alu_result_q;
// WB 级写回使能(同时用于寄存器堆的写端口与写优先旁路)。
wire memwb_reg_write_en = memwb_valid_q && memwb_reg_write_q;
// ID 级读寄存器:x0 恒为 0,并对同周期 WB 的结果做写优先旁路。
wire [31:0] d_rs1_value;
wire [31:0] d_rs2_value;
cpu21_riscv_regfile u_regfile (
.clk (clk),
.reset (reset),
.rs1_idx_i (d_src1_idx),
.rs2_idx_i (d_src2_idx),
.rs1_data_o(d_rs1_value),
.rs2_data_o(d_rs2_value),
.we_i (memwb_reg_write_en),
.waddr_i (memwb_rd_q),
.wdata_i (wb_value)
);
// 中断/CSR 相关连线(中断控制器实例见下方"中断 / CSR"小节)。
wire take_irq;
wire [31:0] irq_vector;
wire [31:0] ex_return_pc;
wire [2:0] irq_pending;
wire [1:0] irq_current_level;
wire [31:0] uepc_value;
wire [31:0] ustatus_value;
// -------------------------------------------------------------------------
// EX 级:前递网络、ALU 运算、存储对齐与控制流裁决
// -------------------------------------------------------------------------
// EX/MEM 级要前递的值(按 wb_sel 选取)。JAL/JALR 的链接地址(PC+4)已在
// EX 级预先算好并存入 exmem_alu_result_q,所以这里 wb_sel=2 直接取该寄存器,
// 前递路径上不再有 PC+4 加法器(100 MHz 下这是最长路径之一)。
wire [31:0] exmem_forward_value =
(exmem_wb_sel_q == 3'd3) ? exmem_alu_result2_q :
(exmem_wb_sel_q == 3'd4) ? exmem_csr_old_q :
exmem_alu_result_q;
// 前递优先级:EX/MEM 的结果最新,其次为 MEM/WB 写回值,最后才是
// 译码时读到的寄存器值。
wire [31:0] ex_src1;
wire [31:0] ex_src2;
cpu21_riscv_forward_unit u_forward_unit (
.rs1_value_i (idex_rs1_value_q),
.rs2_value_i (idex_rs2_value_q),
.rs1_idx_i (idex_rs1_idx_q),
.rs2_idx_i (idex_rs2_idx_q),
.exmem_valid_i (exmem_valid_q),
.exmem_reg_write_i (exmem_reg_write_q),
.exmem_mem_to_reg_i(exmem_mem_to_reg_q),
.exmem_rd_i (exmem_rd_q),
.exmem_value_i (exmem_forward_value),
.memwb_valid_i (memwb_valid_q),
.memwb_reg_write_i (memwb_reg_write_q),
.memwb_rd_i (memwb_rd_q),
.memwb_value_i (wb_value),
.src1_o (ex_src1),
.src2_o (ex_src2)
);
// ALU 的 B 端:立即数或 rs2。
wire [31:0] ex_alu_b = idex_alu_src_q ? idex_imm_q : ex_src2;
wire [31:0] ex_alu_result;
wire [31:0] ex_alu_result2;
// 读取 CSR 旧值(ustatus=0x004、uepc=0x041),供 CSR 指令回写 rd。
wire [31:0] ex_csr_old_value =
(idex_csr_addr_q == 12'h004) ? ustatus_value :
(idex_csr_addr_q == 12'h041) ? uepc_value : 32'b0;
// ALU 实例(只做单拍运算;乘除法见下面的多周期单元)。
cpu21_riscv_alu u_ex_alu (
.op (idex_aluop_q),
.a (ex_src1),
.b (ex_alu_b),
.result (ex_alu_result),
.result2(ex_alu_result2)
);
// -------------------------------------------------------------------------
// 多周期乘除法单元:EX 级停留若干拍等待结果,不再把 32x32 乘法/除法
// 放在组合路径上(原来这部分的组合延时约 94 ns,100 MHz 下无法收敛)。
// 指令在 EX 级期间 ex_md_stall 有效:ID/EX 保持不变、EX/MEM 插气泡、
// 取指/译码冻结;迭代结束(md_done)后照常推进。
// -------------------------------------------------------------------------
wire md_busy;
wire md_done;
wire [31:0] md_result;
wire [31:0] md_result2;
wire ex_is_muldiv = idex_valid_q && (idex_md_op_q != 3'd0);
wire ex_md_stall = ex_is_muldiv && !md_done;
cpu21_riscv_muldiv u_muldiv (
.clk (clk),
.reset (reset),
.start_i (ex_is_muldiv),
.op_i (idex_md_op_q),
.a_i (ex_src1),
.b_i (ex_alu_b),
.busy_o (md_busy),
.done_o (md_done),
.result_o (md_result),
.result2_o(md_result2)
);
// EX 级最终结果:乘除法取多周期单元的输出;JAL/JALR(wb_sel=2)的链接地址
// PC+4 在这里算好随流水寄存器传下去,避免“写回值 → 前递 → 停机判定 →
// 流水寄存器时钟使能”这条组合路径上出现 32 位加法器。
wire [31:0] ex_result =
ex_is_muldiv ? md_result :
(idex_wb_sel_q == 3'd2) ? (idex_pc_q + 32'd4) :
ex_alu_result;
wire [31:0] ex_result2 = ex_is_muldiv ? md_result2 : ex_alu_result2;
// 控制流裁决:条件分支方向/目标、JAL/JALR 目标、是否需要重定向。
cpu21_riscv_branch_unit u_branch_unit (
.valid_i (idex_valid_q),
.pc_i (idex_pc_q),
.imm_i (idex_imm_q),
.src1_i (ex_src1),
.src2_i (ex_src2),
.branch_i (idex_branch_q),
.beq_i (idex_beq_q),
.bne_i (idex_bne_q),
.blt_i (idex_blt_q),
.bge_i (idex_bge_q),
.bltu_i (idex_bltu_q),
.bgeu_i (idex_bgeu_q),
.jal_i (idex_jal_q),
.jalr_i (idex_jalr_q),
.pred_taken_i (idex_pred_taken_q),
.pred_target_i (idex_pred_target_q),
.branch_taken_o (ex_branch_taken),
.controlflow_o (ex_controlflow),
.target_o (ex_target),
.redirect_valid_o(ex_redirect_valid),
.redirect_pc_o (ex_redirect_pc)
);
// 存储数据对齐:依据字节地址低两位生成写数据与字节写掩码。
wire [31:0] ex_store_data;
wire [ 3:0] ex_store_wstrb;
cpu21_riscv_store_unit u_store_unit (
.addr_i (ex_alu_result),
.data_i (ex_src2),
.mem_write_i (idex_mem_write_q),
.mem_byte_i (idex_mem_byte_q),
.store_data_o (ex_store_data),
.store_wstrb_o(ex_store_wstrb)
);
// -------------------------------------------------------------------------
// MMIO:LED 等外设映射到数据地址空间,在 MEM 级做地址译码。
// -------------------------------------------------------------------------
wire [31:0] mmio_rdata;
wire mmio_rdata_hit;
cpu21_mmio #(
.LED_ADDR(LED_ADDR)
) u_mmio (
.clk (clk),
.reset (reset),
.mem_valid_i(exmem_valid_q),
.mem_write_i(exmem_mem_write_q),
.mem_read_i (exmem_mem_to_reg_q),
.addr_i (exmem_alu_result_q),
.wdata_i (exmem_write_data_q),
.wstrb_i (exmem_wstrb_q),
.rdata_o (mmio_rdata),
.rdata_hit_o(mmio_rdata_hit),
// LED(原样)
.led_data_o (led_data_o),
.led_valid_o(led_valid_o),
// 七段数码管
.seg_data_o (seg_data_o),
.seg_valid_o(seg_valid_o),
// 显存(本体在板级顶层)
.fb_we_o (fb_we_o),
.fb_waddr_o (fb_waddr_o),
.fb_wdata_o (fb_wdata_o),
.fb_wstrb_o (fb_wstrb_o),
.fb_rdata_i (fb_rdata_i),
// 显示控制器
.disp_ctrl_o (disp_ctrl_o),
.disp_cursor_o(disp_cursor_o),
// 鼠标事件源与中断
.mouse_ev_valid_i(mouse_ev_valid_i),
.mouse_dx_i (mouse_dx_i),
.mouse_dy_i (mouse_dy_i),
.mouse_btn_i (mouse_btn_i),
.sw_i (sw_i),
.frame_tick_i (frame_tick_i),
.mouse_irq_o (mouse_irq_o),
.level_irq_o (level_irq_o)
);
// 访存读数据:命中 MMIO 时用外设数据,否则用外部存储器数据。
wire [31:0] mem_rdata = mmio_rdata_hit ? mmio_rdata : data_rdata_i;
// ecall 约定:LED 显示已改由 MMIO 存储完成(见上方的 cpu21_mmio 例化)。
// a7==34 的历史“显示”调用保留为无操作;其余(例如 a7=10)令处理器停机,
// 等待 go_i 重新启动。
wire ex_halt_event = idex_valid_q && idex_ecall_q && (ex_src1 != 32'd34);
// -------------------------------------------------------------------------
// 中断 / CSR / 嵌套返回栈
// -------------------------------------------------------------------------
wire ex_uret_redirect = idex_valid_q && idex_uret_q;
// EX 级正在 URET,或乘除法还没算完时,不响应中断:前者与中断同时发生会丢掉
// URET 的返回地址,后者会在结果未定型时推进流水线,向 rd 写回错误值。
// 这里不再包含 ex_redirect_valid / ex_halt_event:它们本身就会让
// flush_younger 有效(流水线照常被冲刷),而中断优先于重定向时返回地址由
// irq_save_pc 给出(已正确跳转的分支取目标地址)。这样就把
// “分支判决 → 中断控制器 → flush → 流水寄存器控制”这条 100 MHz 下的
// 关键路径切断了(原来要绕经中断控制器再回到 flush)。
wire ex_block_irq = ex_uret_redirect || ex_md_stall;
// 中断发生时要保存的返回地址取"下一个架构 PC":若 EX 是一条预测正确
// 且已跳转的分支,则该 PC 就是它的目标地址;若 EX 为空,则 IF/ID 中
// 保存的就是尚未执行的最老指令。
wire [31:0] irq_save_pc = idex_valid_q ?
((idex_branch_q && ex_branch_taken) ? ex_target : (idex_pc_q + 32'd4)) :
(ifid_valid_q ? ifid_pc_q : pc_q);
cpu21_riscv_irq_ctrl #(
.IRQ1_VECTOR (IRQ1_VECTOR),
.IRQ2_VECTOR (IRQ2_VECTOR),
.IRQ3_VECTOR (IRQ3_VECTOR),
.IRQ_STACK_DEPTH(IRQ_STACK_DEPTH),
.USTATUS_INIT (USTATUS_INIT)
) u_irq_ctrl (
.clk (clk),
.reset (reset),
.irq_i (irq_i),
.halted_i (halted_q),
.ex_block_i (ex_block_irq),
.save_pc_i (irq_save_pc),
.uret_i (ex_uret_redirect),
.csr_valid_i(idex_valid_q),
.csr_set_i (idex_csr_set_q),
.csr_clear_i(idex_csr_clear_q),
.csr_write_i(idex_csr_write_q),
.csr_addr_i (idex_csr_addr_q),
.csr_imm_i (idex_imm_q),
.csr_wdata_i(ex_src1),
.take_irq_o (take_irq),
.vector_o (irq_vector),
.return_pc_o(ex_return_pc),
.pending_o (irq_pending),
.level_o (irq_current_level),
.uepc_o (uepc_value),
.ustatus_o (ustatus_value)
);
// -------------------------------------------------------------------------
// 冒险 / 停顿 / 冲刷判定
// -------------------------------------------------------------------------
wire load_use_hazard;
wire flush_younger;
wire pipeline_stall;
cpu21_riscv_hazard_unit u_hazard_unit (
.ifid_valid_i (ifid_valid_q),
.idex_valid_i (idex_valid_q),
.idex_mem_to_reg_i(idex_mem_to_reg_q),
.idex_rd_i (idex_rd_q),
.d_uses_rs1_i (d_uses_rs1),
.d_uses_rs2_i (d_uses_rs2),
.d_src1_idx_i (d_src1_idx),
.d_src2_idx_i (d_src2_idx),
.take_irq_i (take_irq),
.uret_redirect_i (ex_uret_redirect),
.halt_event_i (ex_halt_event),
.redirect_valid_i (ex_redirect_valid),
.halted_i (halted_q),
.load_use_hazard_o(load_use_hazard),
.flush_younger_o (flush_younger),
.pipeline_stall_o (pipeline_stall)
);
// -------------------------------------------------------------------------
// 对外输出与调试信号。
// -------------------------------------------------------------------------
// 存储器接口:指令地址来自取指 PC;数据地址/写数据/写掩码来自 MEM 级。
assign instr_addr_o = pc_q;
assign data_addr_o = exmem_alu_result_q;
assign data_wdata_o = exmem_write_data_q;
assign data_wstrb_o = (exmem_valid_q && exmem_mem_write_q) ? exmem_wstrb_q : 4'b0;
assign data_we_o = |data_wstrb_o;
// LED 输出由 cpu21_mmio 驱动,这里只保留流水线各级 PC/指令回显。
assign if_pc_o = pc_q;
assign id_pc_o = ifid_valid_q ? ifid_pc_q : 32'b0;
assign ex_pc_o = idex_valid_q ? idex_pc_q : 32'b0;
assign mem_pc_o = exmem_valid_q ? exmem_pc_q : 32'b0;
assign wb_pc_o = memwb_valid_q ? memwb_pc_q : 32'b0;
assign if_ir_o = instr_i;
assign id_ir_o = ifid_valid_q ? ifid_ir_q : 32'b0;
assign ex_ir_o = idex_valid_q ? idex_ir_q : 32'b0;
assign mem_ir_o = exmem_valid_q ? exmem_ir_q : 32'b0;
assign wb_ir_o = memwb_valid_q ? memwb_ir_q : 32'b0;
assign rdin_o = wb_value;
assign mdin_o = exmem_write_data_q;
assign reg_write_o = memwb_valid_q && memwb_reg_write_q;
assign mem_write_o = exmem_valid_q && exmem_mem_write_q;
assign halted_o = halted_q;
assign irq_pending_o = irq_pending;
assign irq_current_level_o = irq_current_level;
assign uepc_o = uepc_value;
assign ustatus_o = ustatus_value;
assign bpb_predict_hit_o = bpb_predict_hit;
assign bpb_predict_taken_o = bpb_predict_taken;
assign bpb_mispredict_o = ex_redirect_valid && idex_branch_q;
// -------------------------------------------------------------------------
// 状态更新。顺序遵循五级流水线:先捕获 MEM/WB、EX/MEM,再处理
// ID/EX 与 IF/ID/PC 的控制。
// -------------------------------------------------------------------------
// 复位时清空所有流水寄存器与 LED/停机标志(寄存器堆、中断状态与
// 性能计数器在各自的子模块内复位)。
always @(posedge clk or posedge reset) begin
if (reset) begin
pc_q <= RESET_PC;
ifid_valid_q <= 1'b0;
ifid_pc_q <= 32'b0;
ifid_ir_q <= 32'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
idex_valid_q <= 1'b0;
idex_pc_q <= 32'b0;
idex_ir_q <= 32'b0;
idex_rs1_value_q <= 32'b0;
idex_rs2_value_q <= 32'b0;
idex_rs1_idx_q <= 5'b0;
idex_rs2_idx_q <= 5'b0;
idex_rd_q <= 5'b0;
idex_imm_q <= 32'b0;
idex_aluop_q <= ALU_ADD;
idex_alu_src_q <= 1'b0;
idex_reg_write_q <= 1'b0;
idex_mem_to_reg_q <= 1'b0;
idex_mem_write_q <= 1'b0;
idex_mem_byte_q <= 1'b0;
idex_wb_sel_q <= 3'b0;
idex_md_op_q <= 3'b0;
idex_csr_addr_q <= 12'b0;
idex_branch_q <= 1'b0;
idex_beq_q <= 1'b0;
idex_bne_q <= 1'b0;
idex_blt_q <= 1'b0;
idex_bge_q <= 1'b0;
idex_bltu_q <= 1'b0;
idex_bgeu_q <= 1'b0;
idex_jal_q <= 1'b0;
idex_jalr_q <= 1'b0;
idex_ecall_q <= 1'b0;
idex_uret_q <= 1'b0;
idex_csr_set_q <= 1'b0;
idex_csr_clear_q <= 1'b0;
idex_csr_write_q <= 1'b0;
idex_pred_taken_q <= 1'b0;
idex_pred_target_q <= 32'b0;
exmem_valid_q <= 1'b0;
exmem_pc_q <= 32'b0;
exmem_ir_q <= 32'b0;
exmem_alu_result_q <= 32'b0;
exmem_alu_result2_q <= 32'b0;
exmem_csr_old_q <= 32'b0;
exmem_write_data_q <= 32'b0;
exmem_rd_q <= 5'b0;
exmem_reg_write_q <= 1'b0;
exmem_mem_to_reg_q <= 1'b0;
exmem_mem_write_q <= 1'b0;
exmem_wstrb_q <= 4'b0;
exmem_wb_sel_q <= 3'b0;
memwb_valid_q <= 1'b0;
memwb_pc_q <= 32'b0;
memwb_ir_q <= 32'b0;
memwb_alu_result_q <= 32'b0;
memwb_alu_result2_q <= 32'b0;
memwb_csr_old_q <= 32'b0;
memwb_mem_data_q <= 32'b0;
memwb_rd_q <= 5'b0;
memwb_reg_write_q <= 1'b0;
memwb_mem_to_reg_q <= 1'b0;
memwb_wb_sel_q <= 3'b0;
halted_q <= 1'b0;
end
else begin
if (go_i)
halted_q <= 1'b0;
if (ex_halt_event)
halted_q <= 1'b1;
// MEM/WB 流水寄存器捕获。
memwb_valid_q <= exmem_valid_q;
memwb_pc_q <= exmem_pc_q;
memwb_ir_q <= exmem_ir_q;
memwb_alu_result_q <= exmem_alu_result_q;
memwb_alu_result2_q <= exmem_alu_result2_q;
memwb_csr_old_q <= exmem_csr_old_q;
memwb_mem_data_q <= mem_rdata;
memwb_rd_q <= exmem_rd_q;
memwb_reg_write_q <= exmem_reg_write_q;
memwb_mem_to_reg_q <= exmem_mem_to_reg_q;
memwb_wb_sel_q <= exmem_wb_sel_q;
// EX/MEM 流水寄存器捕获。乘除法迭代期间(ex_md_stall)往 EX/MEM 插气泡,
// 让乘除法指令继续占用 EX,结果在 md_done 那一拍才写入 EX/MEM。
if (ex_md_stall) begin
exmem_valid_q <= 1'b0;
exmem_pc_q <= 32'b0;
exmem_ir_q <= 32'b0;
exmem_alu_result_q <= 32'b0;
exmem_alu_result2_q <= 32'b0;
exmem_csr_old_q <= 32'b0;
exmem_write_data_q <= 32'b0;
exmem_rd_q <= 5'b0;
exmem_reg_write_q <= 1'b0;
exmem_mem_to_reg_q <= 1'b0;
exmem_mem_write_q <= 1'b0;
exmem_wstrb_q <= 4'b0;
exmem_wb_sel_q <= 3'b0;
end
else begin
exmem_valid_q <= idex_valid_q;
exmem_pc_q <= idex_pc_q;
exmem_ir_q <= idex_ir_q;
exmem_alu_result_q <= ex_result;
exmem_alu_result2_q <= ex_result2;
exmem_csr_old_q <= ex_csr_old_value;
exmem_write_data_q <= ex_store_data;
exmem_rd_q <= idex_rd_q;
exmem_reg_write_q <= idex_reg_write_q;
exmem_mem_to_reg_q <= idex_mem_to_reg_q;
exmem_mem_write_q <= idex_mem_write_q;
exmem_wstrb_q <= ex_store_wstrb;
exmem_wb_sel_q <= idex_wb_sel_q;
end
// ---------------------------------------------------------------------
// ID/EX 装载。
//
// payload(PC/IR/控制位/预测位)只在 load-use 停顿、乘除法迭代、停机时
// 保持(时钟使能与“冲刷”无关),其余情况一律装载;只有 valid 位受冲刷
// 控制,冲刷时清 0。这样“EX 级组合判决(分支/中断)→ 流水寄存器时钟
// 使能”这条 100 MHz 下最长的路径就只剩下一个触发器(valid),payload
// 即使装进错误路径的数据也无害——所有使用者都以 idex_valid_q 为前提。
// ---------------------------------------------------------------------
if (!(pipeline_stall || halted_q || ex_md_stall)) begin
idex_pc_q <= ifid_pc_q;
idex_ir_q <= ifid_ir_q;
idex_rs1_value_q <= d_rs1_value;
idex_rs2_value_q <= d_rs2_value;
idex_rs1_idx_q <= d_src1_idx;
idex_rs2_idx_q <= d_src2_idx;
idex_rd_q <= d_rd;
idex_imm_q <= d_imm;
idex_aluop_q <= d_aluop;
idex_alu_src_q <= d_alu_src;
idex_reg_write_q <= d_reg_write;
idex_mem_to_reg_q <= d_mem_to_reg;
idex_mem_write_q <= d_mem_write;
idex_mem_byte_q <= d_mem_byte;
idex_wb_sel_q <= d_wb_sel;
idex_md_op_q <= d_md_op;
idex_csr_addr_q <= d_csr_addr;
idex_branch_q <= d_branch;
idex_beq_q <= d_beq;
idex_bne_q <= d_bne;
idex_blt_q <= d_blt;
idex_bge_q <= d_bge;
idex_bltu_q <= d_bltu;
idex_bgeu_q <= d_bgeu;
idex_jal_q <= d_jal;
idex_jalr_q <= d_jalr;
idex_ecall_q <= d_ecall;
idex_uret_q <= d_uret;
idex_csr_set_q <= d_csr_set;
idex_csr_clear_q <= d_csr_clear;
idex_csr_write_q <= d_csr_write;
idex_pred_taken_q <= ifid_pred_taken_q;
idex_pred_target_q <= ifid_pred_target_q;
end
if (halted_q || ex_md_stall) begin
// 停机 / 乘除法迭代:valid 保持不变(指令继续占用 EX)。
end
else if (pipeline_stall || flush_younger) begin
// load-use 停顿:load 已经进 EX/MEM,这里给 EX 装一个气泡(payload 保持
// 原值,valid=0 时无害);冲刷同理。
idex_valid_q <= 1'b0;
end
else begin
idex_valid_q <= ifid_valid_q;
end
if (take_irq) begin
pc_q <= irq_vector;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_uret_redirect) begin
pc_q <= ex_return_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_halt_event) begin
pc_q <= idex_pc_q + 32'd4;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (ex_redirect_valid) begin
pc_q <= ex_redirect_pc;
ifid_valid_q <= 1'b0;
ifid_pred_taken_q <= 1'b0;
ifid_pred_target_q <= 32'b0;
end
else if (pipeline_stall || ex_md_stall) begin
pc_q <= pc_q;
ifid_valid_q <= ifid_valid_q;
ifid_pc_q <= ifid_pc_q;
ifid_ir_q <= ifid_ir_q;
ifid_pred_taken_q <= ifid_pred_taken_q;
ifid_pred_target_q <= ifid_pred_target_q;
end
else if (halted_q) begin
pc_q <= pc_q;
ifid_valid_q <= 1'b0;
end
else begin
pc_q <= fetch_next_pc;
ifid_valid_q <= 1'b1;
ifid_pc_q <= pc_q;
ifid_ir_q <= instr_i;
ifid_pred_taken_q <= f_is_branch && bpb_predict_hit && bpb_predict_taken;
ifid_pred_target_q <= bpb_predict_target;
end
end
end
// -------------------------------------------------------------------------
// 性能计数器(只影响统计量,不参与控制)。
// -------------------------------------------------------------------------
cpu21_riscv_perf_counters u_perf_counters (
.clk (clk),
.reset (reset),
.inc_cycle_i (!halted_q),
.stall_i (pipeline_stall || ex_md_stall),
.flush_i (flush_younger),
.cond_taken_i (idex_valid_q && idex_branch_q && ex_branch_taken),
.cond_valid_i (idex_valid_q && idex_branch_q),
.mispredict_i (ex_redirect_valid),
.uncond_i (idex_valid_q && (idex_jal_q || idex_jalr_q)),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
endmodule
`default_nettype wire
@@ -0,0 +1,46 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// 寄存器堆:32 x 32 位,x0 恒为 0。
//
// 读端口为组合读,并对同周期的 WB 写回做"写优先"旁路,因此 ID 级可以
// 立刻看到正在写回的结果,无需额外的寄存器堆相关冒险处理。
// -----------------------------------------------------------------------------
module cpu21_riscv_regfile (
input wire clk,
input wire reset,
// 读端口(组合)
input wire [ 4:0] rs1_idx_i,
input wire [ 4:0] rs2_idx_i,
output wire [31:0] rs1_data_o,
output wire [31:0] rs2_data_o,
// 写端口(WB 级)
input wire we_i,
input wire [ 4:0] waddr_i,
input wire [31:0] wdata_i
);
reg [31:0] regs[0:31];
integer i;
// 写优先旁路:同周期写回且地址相同的读请求返回新值。
wire [31:0] rs1_raw = regs[rs1_idx_i];
wire [31:0] rs2_raw = regs[rs2_idx_i];
assign rs1_data_o = (rs1_idx_i == 5'd0) ? 32'b0 :
((we_i && (waddr_i == rs1_idx_i)) ? wdata_i : rs1_raw);
assign rs2_data_o = (rs2_idx_i == 5'd0) ? 32'b0 :
((we_i && (waddr_i == rs2_idx_i)) ? wdata_i : rs2_raw);
always @(posedge clk or posedge reset) begin
if (reset) begin
for (i = 0; i < 32; i = i + 1) regs[i] <= 32'b0;
end else begin
if (we_i && (waddr_i != 5'd0)) regs[waddr_i] <= wdata_i;
regs[0] <= 32'b0;
end
end
endmodule
`default_nettype wire
@@ -0,0 +1,49 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// EX 级存储数据对齐单元。
//
// 依据地址低两位生成写数据与字节写掩码:sw 输出全字 + 1111,sb 把数据的
// 低 8 位搬到对应的字节车道,并只置位对应的写掩码。
// -----------------------------------------------------------------------------
module cpu21_riscv_store_unit (
input wire [31:0] addr_i,
input wire [31:0] data_i,
input wire mem_write_i,
input wire mem_byte_i,
output reg [31:0] store_data_o,
output reg [ 3:0] store_wstrb_o
);
always @* begin
store_data_o = data_i;
store_wstrb_o = 4'b0;
if (mem_write_i) begin
if (mem_byte_i) begin
case (addr_i[1:0])
2'd0: begin
store_data_o = {24'b0, data_i[7:0]};
store_wstrb_o = 4'b0001;
end
2'd1: begin
store_data_o = {16'b0, data_i[7:0], 8'b0};
store_wstrb_o = 4'b0010;
end
2'd2: begin
store_data_o = {8'b0, data_i[7:0], 16'b0};
store_wstrb_o = 4'b0100;
end
default: begin
store_data_o = {data_i[7:0], 24'b0};
store_wstrb_o = 4'b1000;
end
endcase
end else begin
store_data_o = data_i;
store_wstrb_o = 4'b1111;
end
end
end
endmodule
`default_nettype wire
+129
View File
@@ -0,0 +1,129 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_seg7 —— Nexys4DDR 板载 8 位数码管的动态扫描驱动。
//
// 输入:seg_data_i 每 4 位一个十六进制数字(低 4 位 = 最右边那个数码管),
// an_en_i 每一位对应一个数码管的使能(1 = 显示,0 = 熄灭)。
// 输出:an_o[7:0] 位选(低有效)、seg_o[6:0] 段选 a..g(低有效)、dp_o 小数点。
//
// 扫描频率:CLK_DIV 分频后约 1 kHz 切换一位(8 位 => 每位数码管 125 Hz 刷新)。
// 板载数码管是共阳极:位选拉低选中,段选拉低点亮。
// -----------------------------------------------------------------------------
module cpu21_seg7 #(
parameter integer REFRESH_DIV = 50000 // 50 MHz / 50000 = 1 kHz
) (
input wire clk,
input wire reset,
input wire [31:0] seg_data_i, // 8 个十六进制数字
input wire [ 7:0] an_en_i, // 每位是否显示
output reg [ 7:0] an_o, // 位选,低有效
output reg [ 6:0] seg_o, // 段选 a..g,低有效
output reg dp_o // 小数点,低有效
);
// ---------------------------------------------------------------------------
// 刷新计数与当前位
// ---------------------------------------------------------------------------
reg [15:0] div_q;
reg [2:0] digit_q;
wire tick = (div_q == (REFRESH_DIV - 1));
always @(posedge clk or posedge reset) begin
if (reset) begin
div_q <= 16'd0;
digit_q <= 3'd0;
end
else if (tick) begin
div_q <= 16'd0;
digit_q <= digit_q + 3'd1;
end
else begin
div_q <= div_q + 16'd1;
end
end
// 当前要显示的十六进制数字
reg [3:0] nib;
always @* begin
case (digit_q)
3'd0:
nib = seg_data_i[3:0];
3'd1:
nib = seg_data_i[7:4];
3'd2:
nib = seg_data_i[11:8];
3'd3:
nib = seg_data_i[15:12];
3'd4:
nib = seg_data_i[19:16];
3'd5:
nib = seg_data_i[23:20];
3'd6:
nib = seg_data_i[27:24];
default:
nib = seg_data_i[31:28];
endcase
end
// ---------------------------------------------------------------------------
// 十六进制 -> 七段码(bit0=a ... bit6=g,1 表示该段点亮)
// ---------------------------------------------------------------------------
reg [6:0] seg_on;
always @* begin
case (nib)
4'h0:
seg_on = 7'b0111111;
4'h1:
seg_on = 7'b0000110;
4'h2:
seg_on = 7'b1011011;
4'h3:
seg_on = 7'b1001111;
4'h4:
seg_on = 7'b1100110;
4'h5:
seg_on = 7'b1101101;
4'h6:
seg_on = 7'b1111101;
4'h7:
seg_on = 7'b0000111;
4'h8:
seg_on = 7'b1111111;
4'h9:
seg_on = 7'b1101111;
4'hA:
seg_on = 7'b1110111;
4'hB:
seg_on = 7'b1111100;
4'hC:
seg_on = 7'b0111001;
4'hD:
seg_on = 7'b1011110;
4'hE:
seg_on = 7'b1111001;
default:
seg_on = 7'b1110001; // F
endcase
end
// ---------------------------------------------------------------------------
// 位选与段选输出(共阳极 => 低有效)
// ---------------------------------------------------------------------------
always @(posedge clk or posedge reset) begin
if (reset) begin
an_o <= 8'hFF;
seg_o <= 7'h7F;
dp_o <= 1'b1;
end
else if (tick) begin
an_o <= ~(8'b0000_0001 << digit_q) | ~an_en_i;
seg_o <= ~seg_on;
dp_o <= 1'b1; // 不用小数点
end
end
endmodule
`default_nettype wire
+242
View File
@@ -0,0 +1,242 @@
`timescale 1ns / 1ps
`default_nettype none
// -----------------------------------------------------------------------------
// cpu21_vga —— 640x480@60 的"格子"显示控制器(显存端口接板级顶层的双口 RAM)。
//
// CPU 不直接操作像素:它只通过 MMIO 往显示缓冲区里写"每格 1 字节"(字形 + 颜色),
// 本模块按像素扫描把格子展开成画面(8x8 字模放大 2 倍填满 16x16 的格子)。
// 这样一格只要 1 次字节写,刷屏开销极小。
//
// 网格:40 列 x 30 行,每格 16x16 像素 = 640x480。
//
// 时钟:本模块工作在 CPU 时钟(clk)下,用 tick(每 2 个 clk 一个使能)产生
// 25 MHz 的像素节拍。**不引入第二个时钟域**,因此显示缓冲区的读(本模块)
// 与写(MMIO,同一时钟)之间没有跨时钟域问题。
//
// 光标:由 CURSOR 寄存器的 (x,y) 决定,在对应格子上画一个白色闪动的方框
// (CTRL[0] 控制显示与否)。缓冲区字节的 bit7 在本模块中不使用(保留)。
// -----------------------------------------------------------------------------
module cpu21_vga #(
parameter integer H_VISIBLE = 640,
parameter integer H_FP = 16,
parameter integer H_SYNC = 96,
parameter integer H_BP = 48,
parameter integer V_VISIBLE = 480,
parameter integer V_FP = 10,
parameter integer V_SYNC = 2,
parameter integer V_BP = 33,
parameter integer COLS = 40,
parameter integer ROWS = 30
) (
input wire clk,
input wire reset,
input wire tick, // 25 MHz 像素使能(每 2 个 clk 一拍)
// 显示缓冲区读端口
output wire [ 9:0] fb_raddr_o,
input wire [31:0] fb_word_i,
// 控制
input wire [31:0] ctrl_i,
input wire [31:0] cursor_i,
// VGA 输出
output reg [ 3:0] vga_r,
output reg [ 3:0] vga_g,
output reg [ 3:0] vga_b,
output reg vga_hs,
output reg vga_vs,
output reg frame_tick_o
);
localparam integer H_TOTAL = H_VISIBLE + H_FP + H_SYNC + H_BP; // 800
localparam integer V_TOTAL = V_VISIBLE + V_FP + V_SYNC + V_BP; // 525
// ---------------------------------------------------------------------------
// 扫描计数
// ---------------------------------------------------------------------------
reg [9:0] hx, vy;
always @(posedge clk or posedge reset) begin
if (reset) begin
hx <= 10'd0;
vy <= 10'd0;
end
else if (tick) begin
if (hx == H_TOTAL - 1) begin
hx <= 10'd0;
vy <= (vy == V_TOTAL - 1) ? 10'd0 : (vy + 10'd1);
end
else begin
hx <= hx + 10'd1;
end
end
end
wire de = (hx < H_VISIBLE) && (vy < V_VISIBLE);
// 同步信号在 tick 上寄存输出
wire hs_n = ~((hx >= (H_VISIBLE + H_FP)) && (hx < (H_VISIBLE + H_FP + H_SYNC)));
wire vs_n = ~((vy >= (V_VISIBLE + V_FP)) && (vy < (V_VISIBLE + V_FP + V_SYNC)));
// ---------------------------------------------------------------------------
// 格子索引与缓冲区读出
// ---------------------------------------------------------------------------
wire [5:0] ccol = hx[9:4]; // 0..39
wire [4:0] crow = vy[8:4]; // 0..29
wire [10:0] cidx = (crow << 5) + (crow << 3) + ccol; // row*40 + col
assign fb_raddr_o = cidx[10:2]; // 字地址(1 字 = 4 格)
wire [1:0] lane = cidx[1:0];
wire [31:0] cell_word = fb_word_i;
wire [7:0] cell_byte = cell_word >> {lane, 3'b000};
wire [3:0] glyph = cell_byte[3:0];
wire [2:0] gcolor = cell_byte[6:4];
// 全局背景色(字模点为 0 时显示的颜色)
wire [2:0] bgcolor = ctrl_i[7:4];
// ---------------------------------------------------------------------------
// 8x8 字模(bit7 = 最左像素),放大 2 倍填满 16x16 的格子
// ---------------------------------------------------------------------------
wire [2:0] fx = hx[3:1];
wire [2:0] fy = vy[3:1];
function [7:0] font_row;
input [3:0] g;
input [2:0] r;
begin
case (g)
4'd0:
font_row = 8'h00; // 空白
4'd1:
font_row = (r == 3'd0) ? 8'h18 : (r == 3'd1) ? 8'h38 : (r == 3'd2) ? 8'h18 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h7E : 8'h00; // 1
4'd2:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h30 :
(r == 3'd6) ? 8'h7E : 8'h00; // 2
4'd3:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h1C : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 3
4'd4:
font_row = (r == 3'd0) ? 8'h0C : (r == 3'd1) ? 8'h1C : (r == 3'd2) ? 8'h3C :
(r == 3'd3) ? 8'h6C : (r == 3'd4) ? 8'h7E : (r == 3'd5) ? 8'h0C :
(r == 3'd6) ? 8'h0C : 8'h00; // 4
4'd5:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h60 : (r == 3'd2) ? 8'h7C :
(r == 3'd3) ? 8'h06 : (r == 3'd4) ? 8'h06 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 5
4'd6:
font_row = (r == 3'd0) ? 8'h1C : (r == 3'd1) ? 8'h30 : (r == 3'd2) ? 8'h60 :
(r == 3'd3) ? 8'h7C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 6
4'd7:
font_row = (r == 3'd0) ? 8'h7E : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h0C :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h18 :
(r == 3'd6) ? 8'h18 : 8'h00; // 7
4'd8:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h66 :
(r == 3'd3) ? 8'h3C : (r == 3'd4) ? 8'h66 : (r == 3'd5) ? 8'h66 :
(r == 3'd6) ? 8'h3C : 8'h00; // 8
4'd9:
font_row = (r == 3'd0) ? 8'hFF : (r == 3'd1) ? 8'h81 : (r == 3'd2) ? 8'hBD :
(r == 3'd3) ? 8'hA5 : (r == 3'd4) ? 8'hA5 : (r == 3'd5) ? 8'hBD :
(r == 3'd6) ? 8'h81 : 8'hFF; // 未揭开的方块
4'd10:
font_row = (r == 3'd0) ? 8'h40 : (r == 3'd1) ? 8'h70 : (r == 3'd2) ? 8'h78 :
(r == 3'd3) ? 8'h70 : (r == 3'd4) ? 8'h40 : (r == 3'd5) ? 8'h40 :
(r == 3'd6) ? 8'h40 : 8'h00; // 旗
4'd11:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h7E : (r == 3'd2) ? 8'hFF :
(r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : (r == 3'd5) ? 8'hFF :
(r == 3'd6) ? 8'h7E : 8'h3C; // 雷
4'd12:
font_row = (r == 3'd0) ? 8'h81 : (r == 3'd1) ? 8'h42 : (r == 3'd2) ? 8'h24 :
(r == 3'd3) ? 8'h18 : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h24 :
(r == 3'd6) ? 8'h42 : 8'h81; // 爆炸
4'd13:
font_row = (r == 3'd0) ? 8'h3C : (r == 3'd1) ? 8'h66 : (r == 3'd2) ? 8'h06 :
(r == 3'd3) ? 8'h0C : (r == 3'd4) ? 8'h18 : (r == 3'd5) ? 8'h00 :
(r == 3'd6) ? 8'h18 : 8'h00; // 问号
4'd14:
font_row = (r == 3'd3) ? 8'hFF : (r == 3'd4) ? 8'hFF : 8'h00; // 横线
4'd15:
font_row = 8'hFF; // 实心
default:
font_row = 8'h00;
endcase
end
endfunction
wire [7:0] frow = font_row(glyph, fy);
wire glyph_px = frow[3'd7 - fx]; // 1 = 画字模点
// ---------------------------------------------------------------------------
// 光标框(闪动)
// ---------------------------------------------------------------------------
reg [24:0] blink_q;
always @(posedge clk or posedge reset) begin
if (reset)
blink_q <= 25'd0;
else if (tick)
blink_q <= blink_q + 25'd1;
end
wire blink = blink_q[24];
wire cursor_here = (cursor_i[4:0] == ccol) && (cursor_i[12:8] == crow);
wire on_border = (hx[3:0] == 4'd0) || (hx[3:0] == 4'd15) ||
(vy[3:0] == 4'd0) || (vy[3:0] == 4'd15);
wire cursor_box = cursor_here && blink && ctrl_i[0] && on_border;
// ---------------------------------------------------------------------------
// 颜色(3 位调色板 -> 4:4:4 RGB)
// ---------------------------------------------------------------------------
function [11:0] pal;
input [2:0] c;
begin
case (c)
3'd0:
pal = {4'h0, 4'h0, 4'h0}; // 黑
3'd1:
pal = {4'h0, 4'h0, 4'hA}; // 深蓝
3'd2:
pal = {4'h0, 4'hA, 4'h0}; // 绿
3'd3:
pal = {4'h0, 4'hA, 4'hA}; // 青
3'd4:
pal = {4'hA, 4'h0, 4'h0}; // 红
3'd5:
pal = {4'hA, 4'h0, 4'hA}; // 洋红
3'd6:
pal = {4'hA, 4'hA, 4'h0}; // 黄
default:
pal = {4'hA, 4'hA, 4'hA}; // 浅灰
endcase
end
endfunction
wire [2:0] px_color = glyph_px ? gcolor : bgcolor;
wire [2:0] px_final = cursor_box ? 3'd7 : px_color;
wire [11:0] px_rgb = de ? pal(px_final) : 12'h000;
always @(posedge clk or posedge reset) begin
if (reset) begin
{vga_r, vga_g, vga_b} <= 12'h000;
vga_hs <= 1'b1;
vga_vs <= 1'b1;
frame_tick_o <= 1'b0;
end
else if (tick) begin
{vga_r, vga_g, vga_b} <= px_rgb;
vga_hs <= hs_n;
vga_vs <= vs_n;
frame_tick_o <= (hx == H_TOTAL - 1) && (vy == V_TOTAL - 1);
end
end
endmodule
`default_nettype wire
Tcl 一键重建
+73
View File
@@ -0,0 +1,73 @@
`timescale 1ns / 1ps
// -----------------------------------------------------------------------------
// cpu21_board_top 的仿真测试(不上板,纯 RTL 仿真)
//
// 检查内容:
// 1) 复位释放后 CPU 能从片内 ROM 取指(ROM 由 cpu21_board_rom.vh 初始化);
// 2) 程序通过 MMIO 写 LED,板载 LED 端口上能看到 16 位跑马灯序列。
//
// 默认的 board_demo 程序带约 20 ms 软件延时,仿真只需确认第一次写入;
// 若要完整验证跑马灯序列,可生成一份短延时的 ROM 并用 -d BOARD_ROM_FAST 编译:
// xvlog -d BOARD_ROM_FAST board/cpu21_board_top.v <rtl/*.v> testbench/tb_board_top.v
// -----------------------------------------------------------------------------
module tb_board_top;
reg clk100 = 1'b0;
reg rst_n = 1'b0;
reg go = 1'b0;
reg [2:0] irq_sw = 3'b000;
wire [15:0] led;
wire led_we;
always #5 clk100 = ~clk100; // 100 MHz
cpu21_board_top #(
.LED_ADDR(32'h0000_1000)
) dut (
.clk100(clk100),
.rst_n (rst_n),
.go (go),
.irq_sw(irq_sw),
.led (led),
.led_we(led_we),
// 扫雷扩展端口:本测试平台不复位测试这些功能,输入显式接 0/1,
// 避免悬空(z)传到中断控制器里影响跑马灯的比对结果。
.btnu (1'b0),
.btnd (1'b0),
.btnl (1'b0),
.btnr (1'b0),
.sw_flag (1'b0),
.ps2_clk (1'b1),
.ps2_data(1'b1),
.an (),
.seg (),
.dp (),
.vga_r (),
.vga_g (),
.vga_b (),
.vga_hs (),
.vga_vs ()
);
integer changes = 0;
reg [15:0] led_prev = 16'hxxxx;
always @(posedge clk100) begin
if (led !== led_prev) begin
changes = changes + 1;
led_prev = led;
$display("t=%t LED=%04h led_we=%b", $time, led, led_we);
end
end
initial begin
$timeformat(-9, 3, " ns", 12); // %t 按 ns 显示(默认按精度 ps 显示)
$display("NOTE: 复位后观察 LED(默认程序首次写入应为 0001)");
repeat (10) @(posedge clk100);
rst_n = 1'b1;
repeat (30000) @(posedge clk100); // 300 us(CPU 时钟为 clk100/2 = 50 MHz)
$display("FINAL: LED 变化次数=%0d 当前 LED=%04h", changes, led);
$finish;
end
endmodule
@@ -0,0 +1,279 @@
// 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。
// 示例:
// iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_cpu21_riscv_redirect_int_bpb.v
// vvp cpu21_sim
// 仿真会写出 cpu21_riscv_redirect_int_bpb.vcd,可供 GTKWave 查看。
//
// 程序镜像通过 $readmemh 从 ROM_FILE 载入 ROM 模型。$readmemh 找不到文件时
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
module tb_cpu21_riscv_redirect_int_bpb #(
parameter ROM_FILE = "cpu21_riscv_redirect_int_bpb_rom.hex"
// risc-v-benchmark_ccab.hex
// cpu21_riscv_redirect_int_bpb_rom.hex
// risc-v-branch-predict.hex
);
localparam integer ROM_WORDS = 1024;
localparam integer RAM_WORDS = 1024;
localparam integer PATH_LEN = 512;
// 哨兵值:不可能出现在 RISC-V 镜像的第一个字中。
localparam [31:0] EMPTY_ROM_WORD = 32'hFFFF_FFFF;
// 中断入口地址:DUT 例化与下面的打印条件共用。DUT 用 PC[11:2] 访问 ROM,
// 因此这三个入口位于程序镜像的第 43 / 120 / 192 个字(即每个中断处理
// 程序开头的第一条 "sw ..., 0(sp)")。
localparam [31:0] IRQ1_VECTOR = 32'h0000_30ac;
localparam [31:0] IRQ2_VECTOR = 32'h0000_31e0;
localparam [31:0] IRQ3_VECTOR = 32'h0000_3300;
reg clk;
reg reset;
reg [ 31:0] instr_i;
reg [ 31:0] data_rdata_i;
reg [ 2:0] irq_i;
reg go_i;
reg [ 31:0] rom [0:ROM_WORDS-1];
reg [ 31:0] ram [0:RAM_WORDS-1];
wire [ 31:0] instr_addr_o;
wire [ 31:0] data_addr_o;
wire [ 31:0] data_wdata_o;
wire [ 3:0] data_wstrb_o;
wire data_we_o;
wire [ 31:0] led_data_o;
wire led_valid_o;
wire [ 31:0] if_pc_o;
wire [ 31:0] id_pc_o;
wire [ 31:0] ex_pc_o;
wire [ 31:0] mem_pc_o;
wire [ 31:0] wb_pc_o;
wire [ 31:0] if_ir_o;
wire [ 31:0] id_ir_o;
wire [ 31:0] ex_ir_o;
wire [ 31:0] mem_ir_o;
wire [ 31:0] wb_ir_o;
wire [ 31:0] rdin_o;
wire [ 31:0] mdin_o;
wire reg_write_o;
wire mem_write_o;
wire halted_o;
wire [ 2:0] irq_pending_o;
wire [ 1:0] irq_current_level_o;
wire [ 31:0] uepc_o;
wire [ 31:0] ustatus_o;
wire bpb_predict_hit_o;
wire bpb_predict_taken_o;
wire bpb_mispredict_o;
wire [ 15:0] cycle_count_o;
wire [ 15:0] stall_count_o;
wire [ 15:0] bubble_count_o;
wire [ 15:0] conditional_taken_count_o;
wire [ 15:0] unconditional_branch_count_o;
wire [ 15:0] prediction_success_count_o;
wire [ 15:0] prediction_failure_count_o;
integer i;
integer tb_cycle;
reg rom_loaded;
reg [8*PATH_LEN-1:0] rom_file_path;
cpu21_riscv_redirect_int_bpb #(
.RESET_PC (32'h0000_0000),
.IRQ1_VECTOR(IRQ1_VECTOR),
.IRQ2_VECTOR(IRQ2_VECTOR),
.IRQ3_VECTOR(IRQ3_VECTOR),
// 中断测试程序只在中断处理程序内部写 MIE,因此测试平台在复位后就让
// MIE 有效(ustatus[0] = 1)。
.USTATUS_INIT(32'h0000_0001)
) dut (
.clk (clk),
.reset (reset),
.instr_i (instr_i),
.data_rdata_i (data_rdata_i),
.irq_i (irq_i),
.go_i (go_i),
.instr_addr_o (instr_addr_o),
.data_addr_o (data_addr_o),
.data_wdata_o (data_wdata_o),
.data_wstrb_o (data_wstrb_o),
.data_we_o (data_we_o),
.led_data_o (led_data_o),
.led_valid_o (led_valid_o),
.if_pc_o (if_pc_o),
.id_pc_o (id_pc_o),
.ex_pc_o (ex_pc_o),
.mem_pc_o (mem_pc_o),
.wb_pc_o (wb_pc_o),
.if_ir_o (if_ir_o),
.id_ir_o (id_ir_o),
.ex_ir_o (ex_ir_o),
.mem_ir_o (mem_ir_o),
.wb_ir_o (wb_ir_o),
.rdin_o (rdin_o),
.mdin_o (mdin_o),
.reg_write_o (reg_write_o),
.mem_write_o (mem_write_o),
.halted_o (halted_o),
.irq_pending_o (irq_pending_o),
.irq_current_level_o (irq_current_level_o),
.uepc_o (uepc_o),
.ustatus_o (ustatus_o),
.bpb_predict_hit_o (bpb_predict_hit_o),
.bpb_predict_taken_o (bpb_predict_taken_o),
.bpb_mispredict_o (bpb_mispredict_o),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
always #5 clk = ~clk;
// Logisim 的 ROM 以 PC[11:2] 作为字地址;这也让原来的三个中断向量映射到
// 相同的 ROM 表项。
always @* begin
instr_i = 32'h00000013;
if (instr_addr_o[11:2] < ROM_WORDS) instr_i = rom[instr_addr_o[11:2]];
end
// 数据存储器的异步读模型。
always @* begin
data_rdata_i = 32'b0;
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end
// 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin
if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
if (data_wstrb_o[1]) ram[data_addr_o[11:2]][15:8] <= data_wdata_o[15:8];
if (data_wstrb_o[2]) ram[data_addr_o[11:2]][23:16] <= data_wdata_o[23:16];
if (data_wstrb_o[3]) ram[data_addr_o[11:2]][31:24] <= data_wdata_o[31:24];
end
end
always @(posedge clk) begin
if (reset) begin
tb_cycle = 0;
end else begin
tb_cycle = tb_cycle + 1;
if ((tb_cycle <= 20) || data_we_o || led_valid_o ||
(irq_pending_o != 3'b000) ||
(instr_addr_o == IRQ1_VECTOR) ||
(instr_addr_o == IRQ2_VECTOR) ||
(instr_addr_o == IRQ3_VECTOR)) begin
$display(
"t=%0t cyc=%0d PC=%08h ID=%08h EX=%08h MEM=%08h WB=%08h irq=%b pend=%b uepc=%08h LEDv=%b LED=%08h memwe=%b addr=%08h wdata=%08h wstrb=%b",
$time, tb_cycle, if_pc_o, id_ir_o, ex_ir_o, mem_ir_o, wb_ir_o, irq_i, irq_pending_o,
uepc_o, led_valid_o, led_data_o, data_we_o, data_addr_o, data_wdata_o, data_wstrb_o);
end
end
end
// 尝试从 "fname" 载入 ROM 镜像。"loaded" 为 1 表示文件存在且至少包含一个
// 字。利用 rom[0] 中的哨兵值,可以避免载入失败被静默忽略。
task load_rom_image;
input [8*PATH_LEN-1:0] fname;
output loaded;
integer fh;
begin
loaded = 1'b0;
fh = $fopen(fname, "r");
if (fh != 0) begin
$fclose(fh);
rom[0] = EMPTY_ROM_WORD;
$readmemh(fname, rom);
if (rom[0] === EMPTY_ROM_WORD) begin
// 文件能打开但内容为空:恢复默认的 NOP 填充。
rom[0] = 32'h0000_0013;
end else begin
rom_file_path = fname;
loaded = 1'b1;
$display("NOTE: ROM image loaded from \"%0s\".", fname);
end
end
end
endtask
initial begin
clk = 1'b0;
reset = 1'b1;
irq_i = 3'b000;
go_i = 1'b0;
tb_cycle = 0;
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h00000013;
for (i = 0; i < RAM_WORDS; i = i + 1) ram[i] = 32'b0;
// 预置几个字,便于在波形中观察数据存储器的活动。
ram[1] = 32'h1234_5678;
ram[2] = 32'h89ab_cdef;
// 依次探测 ROM 镜像的常见位置,第一个可读的候选文件生效;下面的探测
// 失败是无害的(在找到真正的镜像之前,ROM 保持 0x00000013 填充)。
$display("NOTE: searching for ROM image \"%0s\" ...", ROM_FILE);
rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded);
if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded);
if (!rom_loaded) load_rom_image({"testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/rom/", ROM_FILE}, rom_loaded);
// 分享出去的 Vivado 工程里的镜像目录(工程目录/srcs/rom/),GUI 仿真的工作
// 目录是 <proj>.sim/sim_1/behav/xsim,所以需要向上 5 层。
if (!rom_loaded) load_rom_image({"srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded)
$display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
ROM_FILE
);
$dumpfile("cpu21_riscv_redirect_int_bpb.vcd");
$dumpvars(0, tb_cpu21_riscv_redirect_int_bpb);
#22 reset = 1'b0;
// 模拟按键式的中断脉冲,DUT 内部会同步并锁存它们。
repeat (80) @(negedge clk);
irq_i[0] = 1'b1;
@(negedge clk);
irq_i[0] = 1'b0;
repeat (180) @(negedge clk);
irq_i[1] = 1'b1;
@(negedge clk);
irq_i[1] = 1'b0;
repeat (180) @(negedge clk);
irq_i[2] = 1'b1;
@(negedge clk);
irq_i[2] = 1'b0;
repeat (5560) @(negedge clk);
$display(
"FINAL: tb_cycles=%0d dut_cycles=%0d stalls=%0d bubbles=%0d cond_taken=%0d uncond=%0d pred_ok=%0d pred_fail=%0d halted=%b",
tb_cycle, cycle_count_o, stall_count_o, bubble_count_o, conditional_taken_count_o,
unconditional_branch_count_o, prediction_success_count_o, prediction_failure_count_o,
halted_o);
$finish;
end
endmodule
+268
View File
@@ -0,0 +1,268 @@
// 仿真顶层:tb_no_intr —— 与 tb_cpu21_riscv_redirect_int_bpb.v 相同的测试框架,
// 但不注入任何中断脉冲,因此主程序可以不受打扰地连续运行。
//
// 与 RTL 文件(rtl/cpu21_*.v)一起编译,仿真相关文件都在 testbench/ 下。
// 示例:
// iverilog -g2012 -o cpu21_sim rtl/*.v testbench/tb_no_intr.v
// vvp cpu21_sim
// 仿真会写出 tb_no_intr.vcd,可供 GTKWave 查看。
//
// 程序镜像通过 $readmemh 从 ROM_FILE 载入 ROM 模型。$readmemh 找不到文件时
// 只会打印警告并保持内存原样,所以路径写错会让 ROM 看起来全是 NOP
// (0x00000013)。仿真器的工作目录并不固定(Vivado xsim 在
// <proj>.sim/sim_1/behav/xsim 下运行),因此本测试平台会依次探测若干候选
// 路径(testbench/rom/、../testbench/rom/、…,相对仓库根目录),并报告实际
// 命中的那个。显式给出的路径始终优先:
// vvp cpu21_sim +ROM_FILE=<path> (iverilog/vvp)
// xelab ... -generic_top "ROM_FILE=<path>" (Vivado xsim)
// 注意:PowerShell 会把命令行参数里的 "=" 拆开(xelab/xsim 报
// "Expected a switch but found …"),此时直接改本文件的 ROM_FILE 参数即可;
// ROM 镜像都在 testbench/rom/ 下,路径请用正斜杠(Verilog 字符串里 "\" 是转义符)。
module tb_no_intr #(
parameter ROM_FILE = "risc-v-benchmark_ccab.hex"
// risc-v-benchmark_ccab.hex
// cpu21_riscv_redirect_int_bpb_rom.hex
// risc-v-branch-predict.hex
);
localparam integer ROM_WORDS = 1024;
localparam integer RAM_WORDS = 1024;
localparam integer PATH_LEN = 512;
// 哨兵值:不可能出现在 RISC-V 镜像的第一个字中。
localparam [31:0] EMPTY_ROM_WORD = 32'hFFFF_FFFF;
// 中断入口地址:DUT 例化与下面的打印条件共用。DUT 用 PC[11:2] 访问 ROM,
// 因此这三个入口位于程序镜像的第 43 / 120 / 192 个字(即每个中断处理
// 程序开头的第一条 "sw ..., 0(sp)")。
localparam [31:0] IRQ1_VECTOR = 32'h0000_30ac;
localparam [31:0] IRQ2_VECTOR = 32'h0000_31e0;
localparam [31:0] IRQ3_VECTOR = 32'h0000_3300;
reg clk;
reg reset;
reg [ 31:0] instr_i;
reg [ 31:0] data_rdata_i;
reg [ 2:0] irq_i;
reg go_i;
reg [ 31:0] rom [0:ROM_WORDS-1];
reg [ 31:0] ram [0:RAM_WORDS-1];
wire [ 31:0] instr_addr_o;
wire [ 31:0] data_addr_o;
wire [ 31:0] data_wdata_o;
wire [ 3:0] data_wstrb_o;
wire data_we_o;
wire [ 31:0] led_data_o;
wire led_valid_o;
wire [ 31:0] if_pc_o;
wire [ 31:0] id_pc_o;
wire [ 31:0] ex_pc_o;
wire [ 31:0] mem_pc_o;
wire [ 31:0] wb_pc_o;
wire [ 31:0] if_ir_o;
wire [ 31:0] id_ir_o;
wire [ 31:0] ex_ir_o;
wire [ 31:0] mem_ir_o;
wire [ 31:0] wb_ir_o;
wire [ 31:0] rdin_o;
wire [ 31:0] mdin_o;
wire reg_write_o;
wire mem_write_o;
wire halted_o;
wire [ 2:0] irq_pending_o;
wire [ 1:0] irq_current_level_o;
wire [ 31:0] uepc_o;
wire [ 31:0] ustatus_o;
wire bpb_predict_hit_o;
wire bpb_predict_taken_o;
wire bpb_mispredict_o;
wire [ 15:0] cycle_count_o;
wire [ 15:0] stall_count_o;
wire [ 15:0] bubble_count_o;
wire [ 15:0] conditional_taken_count_o;
wire [ 15:0] unconditional_branch_count_o;
wire [ 15:0] prediction_success_count_o;
wire [ 15:0] prediction_failure_count_o;
integer i;
integer tb_cycle;
reg rom_loaded;
reg [8*PATH_LEN-1:0] rom_file_path;
cpu21_riscv_redirect_int_bpb #(
.RESET_PC (32'h0000_0000),
.IRQ1_VECTOR(IRQ1_VECTOR),
.IRQ2_VECTOR(IRQ2_VECTOR),
.IRQ3_VECTOR(IRQ3_VECTOR),
// 与中断测试平台一致,复位后即使能 MIE。本测试平台从不产生 irq_i
// 脉冲,因此该参数只有在你自行添加脉冲时才有影响。
.USTATUS_INIT(32'h0000_0001)
) dut (
.clk (clk),
.reset (reset),
.instr_i (instr_i),
.data_rdata_i (data_rdata_i),
.irq_i (irq_i),
.go_i (go_i),
.instr_addr_o (instr_addr_o),
.data_addr_o (data_addr_o),
.data_wdata_o (data_wdata_o),
.data_wstrb_o (data_wstrb_o),
.data_we_o (data_we_o),
.led_data_o (led_data_o),
.led_valid_o (led_valid_o),
.if_pc_o (if_pc_o),
.id_pc_o (id_pc_o),
.ex_pc_o (ex_pc_o),
.mem_pc_o (mem_pc_o),
.wb_pc_o (wb_pc_o),
.if_ir_o (if_ir_o),
.id_ir_o (id_ir_o),
.ex_ir_o (ex_ir_o),
.mem_ir_o (mem_ir_o),
.wb_ir_o (wb_ir_o),
.rdin_o (rdin_o),
.mdin_o (mdin_o),
.reg_write_o (reg_write_o),
.mem_write_o (mem_write_o),
.halted_o (halted_o),
.irq_pending_o (irq_pending_o),
.irq_current_level_o (irq_current_level_o),
.uepc_o (uepc_o),
.ustatus_o (ustatus_o),
.bpb_predict_hit_o (bpb_predict_hit_o),
.bpb_predict_taken_o (bpb_predict_taken_o),
.bpb_mispredict_o (bpb_mispredict_o),
.cycle_count_o (cycle_count_o),
.stall_count_o (stall_count_o),
.bubble_count_o (bubble_count_o),
.conditional_taken_count_o (conditional_taken_count_o),
.unconditional_branch_count_o(unconditional_branch_count_o),
.prediction_success_count_o (prediction_success_count_o),
.prediction_failure_count_o (prediction_failure_count_o)
);
always #5 clk = ~clk;
// Logisim 的 ROM 以 PC[11:2] 作为字地址;这也让原来的三个中断向量映射到
// 相同的 ROM 表项。
always @* begin
instr_i = 32'h00000013;
if (instr_addr_o[11:2] < ROM_WORDS) instr_i = rom[instr_addr_o[11:2]];
end
// 数据存储器的异步读模型。
always @* begin
data_rdata_i = 32'b0;
if (data_addr_o[11:2] < RAM_WORDS) data_rdata_i = ram[data_addr_o[11:2]];
end
// 带字节使能的同步写模型。MMIO 区域(LED_ADDR=0x1000 起)不在本模型的
// 译码范围(data_addr_o[11:2] < 1024)内,因此对 LED 的存储不会写进 RAM。
always @(posedge clk) begin
if (!reset && data_we_o && (data_addr_o[11:2] < RAM_WORDS)) begin
if (data_wstrb_o[0]) ram[data_addr_o[11:2]][7:0] <= data_wdata_o[7:0];
if (data_wstrb_o[1]) ram[data_addr_o[11:2]][15:8] <= data_wdata_o[15:8];
if (data_wstrb_o[2]) ram[data_addr_o[11:2]][23:16] <= data_wdata_o[23:16];
if (data_wstrb_o[3]) ram[data_addr_o[11:2]][31:24] <= data_wdata_o[31:24];
end
end
always @(posedge clk) begin
if (reset) begin
tb_cycle = 0;
end else begin
tb_cycle = tb_cycle + 1;
if ((tb_cycle <= 20) || data_we_o || led_valid_o ||
(irq_pending_o != 3'b000) ||
(instr_addr_o == IRQ1_VECTOR) ||
(instr_addr_o == IRQ2_VECTOR) ||
(instr_addr_o == IRQ3_VECTOR)) begin
$display(
"t=%0t cyc=%0d PC=%08h ID=%08h EX=%08h MEM=%08h WB=%08h irq=%b pend=%b uepc=%08h LEDv=%b LED=%08h memwe=%b addr=%08h wdata=%08h wstrb=%b",
$time, tb_cycle, if_pc_o, id_ir_o, ex_ir_o, mem_ir_o, wb_ir_o, irq_i, irq_pending_o,
uepc_o, led_valid_o, led_data_o, data_we_o, data_addr_o, data_wdata_o, data_wstrb_o);
end
end
end
// 尝试从 "fname" 载入 ROM 镜像。"loaded" 为 1 表示文件存在且至少包含一个
// 字。利用 rom[0] 中的哨兵值,可以避免载入失败被静默忽略。
task load_rom_image;
input [8*PATH_LEN-1:0] fname;
output loaded;
integer fh;
begin
loaded = 1'b0;
fh = $fopen(fname, "r");
if (fh != 0) begin
$fclose(fh);
rom[0] = EMPTY_ROM_WORD;
$readmemh(fname, rom);
if (rom[0] === EMPTY_ROM_WORD) begin
// 文件能打开但内容为空:恢复默认的 NOP 填充。
rom[0] = 32'h0000_0013;
end else begin
rom_file_path = fname;
loaded = 1'b1;
$display("NOTE: ROM image loaded from \"%0s\".", fname);
end
end
end
endtask
initial begin
clk = 1'b0;
reset = 1'b1;
irq_i = 3'b000;
go_i = 1'b0;
tb_cycle = 0;
for (i = 0; i < ROM_WORDS; i = i + 1) rom[i] = 32'h00000013;
for (i = 0; i < RAM_WORDS; i = i + 1) ram[i] = 32'b0;
// 预置几个字,便于在波形中观察数据存储器的活动。
ram[1] = 32'h1234_5678;
ram[2] = 32'h89ab_cdef;
// 依次探测 ROM 镜像的常见位置,第一个可读的候选文件生效;下面的探测
// 失败是无害的(在找到真正的镜像之前,ROM 保持 0x00000013 填充)。
$display("NOTE: searching for ROM image \"%0s\" ...", ROM_FILE);
rom_loaded = 1'b0;
if ($value$plusargs("ROM_FILE=%s", rom_file_path)) load_rom_image(rom_file_path, rom_loaded);
if (!rom_loaded) load_rom_image(ROM_FILE, rom_loaded);
if (!rom_loaded) load_rom_image({"testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../testbench/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../testbench/rom/", ROM_FILE}, rom_loaded);
// 分享出去的 Vivado 工程里的镜像目录(工程目录/srcs/rom/),GUI 仿真的工作
// 目录是 <proj>.sim/sim_1/behav/xsim,所以需要向上 5 层。
if (!rom_loaded) load_rom_image({"srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded) load_rom_image({"../../../../../srcs/rom/", ROM_FILE}, rom_loaded);
if (!rom_loaded)
$display(
"WARNING: no ROM image found for \"%0s\" - the ROM stays filled with NOPs (0x00000013).",
ROM_FILE
);
$dumpfile("tb_no_intr.vcd");
$dumpvars(0, tb_no_intr);
#22 reset = 1'b0;
// 本测试平台不注入中断脉冲:irq_i 始终为 3'b000,因此只走主程序
//(非中断)执行路径。
repeat (5560) @(negedge clk);
$display(
"FINAL: tb_cycles=%0d dut_cycles=%0d stalls=%0d bubbles=%0d cond_taken=%0d uncond=%0d pred_ok=%0d pred_fail=%0d halted=%b",
tb_cycle, cycle_count_o, stall_count_o, bubble_count_o, conditional_taken_count_o,
unconditional_branch_count_o, prediction_success_count_o, prediction_failure_count_o,
halted_o);
$finish;
end
endmodule