
没有硬件设备下进行FPGA开发,在线设计模仿平台StepFPGA,即小脚丫,可以展开图形化设计和Verilog编程及其逻辑综合、管脚分配、FPGA映射、编程文件下载、波形仿真,对于学习和提升集成电路设计,是不错的选择。近期高校教培,本人实践和互动教学了一系列项目:译码/计数器、交通灯信号控制、UART通信、RISC-V片上系统,现在沉淀下来,供参考。其中,快速构造具体项目及其纠错时,借力了人工智能AI工具--IMA-Copilot-DS与MuleRun。美中不足:小脚丫平台,图形化设计不能仿真,可以针对的FPGA芯片虽然经典但不主流。
1 四-十六译码器
1.1 方案规划

1.2 项目创建

1.3 图形化设计

1.4 仿真测试
编制仿真测试文件,进行测试,如下图所示。在线StepFPGA系统,失于维护,不能有效完成仿真,此外略去仿真及其波形展示。仿真测试文件如下文本框所示。

`timescale 1ns / 1ps
module decoder_tb;
reg 3:0 d; // 4位地址输入
wire 15:0 o; // 16位输出(低有效]
// 图形化设计的顶层模块名需与项目一致,实际为main
main uut ( .d (d), .o (o) );
initial begin
d = 4'd0; // 初始化
#100;
repeat(16) begin // 使能后遍历所有地址
#50;
d = d + 1;
end
d = 4'd0; // 禁用后验证输出全为1
#200;
$finish;
end
endmodule
1.5 综合与下载
展开一系列过程:逻辑综合à管脚分配àGPGA映射à文件下载。其中管脚分配过程如下图所示。

2 二十位计数器
2.1 方案规划

2.2 图形化设计


2.3 仿真测试
编制仿真测试文件,进行测试。在线StepFPGA系统,失于维护,不能有效完成仿真,此外略去仿真及其波形展示。仿真测试文件如下文本框所示。
`timescale 1ns / 1ps
module counter20_tb;
reg clk; reg rst_n;
wire 19:0 o;
main uut ( .clk (clk), .rst_n (rst_n), .o (o) );
initial begin
clk = 1'b0;
forever #5 clk = ~clk;
end
initial begin
rst_n = 1'b0;
#100;
rst_n = 1'b1;
// 20位计数器满量程约100万周期,仿真只观察部分
#50000;
$finish;
end
endmodule
3 简易交通灯
3.1 方案规划
实现一个"十字路口红黄绿交通灯",用两个RGB三色LED分别代表主路和支路。

|--------|--------|--------|----------|
| 状态 | 主路 | 支路 | 持续时间 |
| S1 | 🟢 绿灯 | 🔴 红灯 | 15 秒 |
| S2 | 🟡 黄灯 | 🔴 红灯 | 3 秒 |
| S3 | 🔴 红灯 | 🟢 绿灯 | 7 秒 |
| S4 | 🔴 红灯 | 🟡 黄灯 | 3 秒 |
状态转移:S1 → S2 → S3 → S4 → S1 → ...(倒计时归零触发跳转)

RGB LED编码:
|------------|
| out5:0 |
小脚丫 RGB LED 为 低电平点亮,6 位输出out5:0

|--------|--------|--------------|--------------|----------------|
| 状态 | 颜色 | 主路 R,G,B | 支路 R,G,B | out5:0 |
| S1 | 主绿支红 | 1,0,1 | 0,1,1 | 101_011 |
| S2 | 主黄支红 | 0,0,1 | 0,1,1 | 001_011 |
| S3 | 主红支绿 | 0,1,1 | 1,0,1 | 011_101 |
| S4 | 主红支黄 | 0,1,1 | 1,0,0 | 011_100 |
黄灯 = R+G 同时点亮(0,0,1 中 R=0 点红 + G=0 点绿 → 黄色混合)
3.2 项目创建

3.3 编码设计
新建两个编程文件:devide.v和traffic.v,编码如下文本框所示。设置顶层文件为traffic.v。
devide.v:
cpp
module divide(
input wire clk,
input wire rst_n,
output wire clkout
);
parameter WIDTH = 3; parameter N = 5;
reg [WIDTH-1:0] cnt_p, cnt_n;
reg clk_p, clk_n;
assign clkout = (N == 1) ? clk : (N[0]) ? (clk_p & clk_n) : clk_p;
// 上升沿计数
always @(posedge clk) begin
if (!rst_n) cnt_p <= 0;
else if (cnt_p == N - 1) cnt_p <= 0;
else cnt_p <= cnt_p + 1;
end
// 下降沿计数
always @(negedge clk) begin
if (!rst_n) cnt_n <= 0;
else if (cnt_n == N - 1) cnt_n <= 0;
else cnt_n <= cnt_n + 1;
end
// 上升沿分频输出
always @(posedge clk) begin
if (!rst_n) clk_p <= 0;
else if (cnt_p < (N >> 1)) clk_p <= 0;
else clk_p <= 1;
end
// 下降沿分频输出
always @(negedge clk) begin
if (!rst_n) clk_n <= 0;
else if (cnt_n < (N >> 1)) clk_n <= 0;
else clk_n <= 1;
end
endmodule
traffic.v:
cpp
module traffic(
input wire clk, // 12MHz 系统时钟
input wire rst_n, // 复位(低有效)
output reg [5:0] out // {主R,主G,主B, 支R,支G,支B}
);
// 状态编码
parameter S1 = 2'b00, // 主绿支红
S2 = 2'b01, // 主黄支红
S3 = 2'b10, // 主红支绿
S4 = 2'b11; // 主红支黄
// 各状态持续时间(秒)
parameter time_s1 = 8'd15,
time_s2 = 8'd3,
time_s3 = 8'd7,
time_s4 = 8'd3;
// 各状态 LED 编码(低电平点亮)
parameter led_s1 = 6'b101_011, // 主绿支红
led_s2 = 6'b001_011, // 主黄支红
led_s3 = 6'b011_101, // 主红支绿
led_s4 = 6'b011_100; // 主红支黄
reg [7:0] timecont; // 倒计时计数器
reg [1:0] cur_state, next_state; // 现态、次态
wire clk1h; // 1Hz 时钟
// 例化分频器:12MHz → 1Hz
divide #(.WIDTH(32), .N(12000000)) CLK1H (
.clk(clk),
.rst_n(rst_n),
.clkout(clk1h)
);
// ============================================
// 第一段:同步时序 --- 状态寄存(现态 ← 次态)
// ============================================
always @(posedge clk1h or negedge rst_n) begin
if (!rst_n)
cur_state <= S1;
else
cur_state <= next_state;
end
// ============================================
// 第二段:组合逻辑 --- 状态跳转条件
// ============================================
always @(*) begin
case (cur_state)
S1: next_state = (timecont == 1) ? S2 : S1;
S2: next_state = (timecont == 1) ? S3 : S2;
S3: next_state = (timecont == 1) ? S4 : S3;
S4: next_state = (timecont == 1) ? S1 : S4;
default: next_state = S1;
endcase
end
// ============================================
// 第三段:同步时序 --- 输出动作 + 倒计时
// ============================================
always @(posedge clk1h or negedge rst_n) begin
if (!rst_n) begin
out <= led_s1;
timecont <= time_s1;
end else begin
case (next_state)
S1: begin
out <= led_s1;
if (timecont == 1)
timecont <= time_s1;
else
timecont <= timecont - 1;
end
S2: begin
out <= led_s2;
if (timecont == 1)
timecont <= time_s2;
else
timecont <= timecont - 1;
end
S3: begin
out <= led_s3;
if (timecont == 1)
timecont <= time_s3;
else
timecont <= timecont - 1;
end
S4: begin
out <= led_s4;
if (timecont == 1)
timecont <= time_s4;
else
timecont <= timecont - 1;
end
default: begin
out <= led_s1;
timecont <= time_s1;
end
endcase
end
end
endmodule
3.4 仿真测试
编制仿真测试文件,进行测试,运行波形如下图所示。仿真测试文件如下文本框所示。由于12MHz分频到1Hz 需要12000000个时钟周期,仿真非常慢。仿真前临时修改 traffic.v 中的分频参数:
// 实际下载到板卡时用:
divide #(.WIDTH(32), .N(12000000)) CLK1H (...)
// 仿真时临时改为(仿真完记得改回来):
divide #(.WIDTH(5), .N(10)) CLK1H (...)
这样1Hz变成约1.2MHz,只需10个时钟周期就翻转一次,仿真几微秒就能看到完整的状态切换。

cpp
`timescale 1ns / 1ps
module traffic_tb;
reg clk, rst_n;
wire [5:0] out;
// 例化交通灯(仿真用极小分频值,加速仿真)
traffic uut ( .clk(clk), .rst_n(rst_n), .out(out) );
// 生成 12MHz 时钟(周期 ≈ 83.3ns)
initial begin
clk = 0;
forever #42 clk = ~clk;
end
// 复位与仿真流程
initial begin
rst_n = 0;
#200;
rst_n = 1;
// 运行足够长时间
// 实际 12MHz/12000000 = 1Hz,完整一轮需 28 秒
// 仿真中 28s × 12000000 ≈ 3.36×10^8 个时钟周期
// 仿真时间很长,建议缩小分频值
#500000;
$display("=== Traffic light simulation complete ===");
$finish;
end
endmodule
4 UART通信监视
4.1 方案规划

4.2 编码设计
新建五个编程文件:
1 波特率节拍baud.v :
cpp
module baud #(
parameter BPS_PARA = 1250 // 12MHz / 9600 = 1250
)(
input clk, // 系统时钟 12MHz
input rst_n, // 复位(低有效)
input bps_en, // 接收使能
output reg bps_clk // 采样节拍脉冲(每位中点产生1个脉冲)
);
reg [12:0] cnt;
// 计数器:0 ~ BPS_PARA-1 循环
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 13'd0;
else if ((cnt >= BPS_PARA - 1) || (!bps_en))
cnt <= 13'd0;
else
cnt <= cnt + 1'b1;
end
// 在计数中点产生采样脉冲(此时数据最稳定)
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
bps_clk <= 1'b0;
else if (cnt == (BPS_PARA >> 1)) // 中点
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;
end
endmodule
2 接收uart_rx.v :
cpp
module uart_rx #(
parameter BPS_PARA = 1250
)(
input clk,
input rst_n,
input uart_rx, // 串口接收线(CP2102 TXD → FPGA RX)
output reg [7:0] rx_data_out, // 接收到的8位数据
output reg rx_data_valid // 数据有效脉冲(1个时钟周期宽)
);
// ====== 亚稳态消除:打三拍 + 下降沿检测 ======
reg uart_rx0, uart_rx1, uart_rx2;
wire neg_uart_rx = uart_rx2 & ~uart_rx1; // 起始位下降沿
always @(posedge clk) begin
uart_rx0 <= uart_rx;
uart_rx1 <= uart_rx0;
uart_rx2 <= uart_rx1;
end
// ====== 接收使能控制 ======
reg bps_en;
reg [3:0] num; // 0~9: 0=等待, 1~8=数据位, 9=完成
reg [7:0] rx_data;
wire bps_clk;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
bps_en <= 1'b0;
else if (neg_uart_rx && (!bps_en)) // 检测到起始位且未在接收
bps_en <= 1'b1;
else if (num == 4'd9) // 接收完成
bps_en <= 1'b0;
end
// ====== 数据采样:在每个波特率中点采样 ======
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
num <= 4'd0;
rx_data <= 8'd0;
end else if (bps_en) begin
if (bps_clk) begin
num <= num + 1'b1;
if (num >= 1 && num <= 8)
rx_data[num-1] <= uart_rx1; // 采样当前位
end else if (num == 4'd9) begin
num <= 4'd0;
end
end else begin
num <= 4'd0;
end
end
// ====== 数据输出 ======
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_data_out <= 8'd0;
rx_data_valid <= 1'b0;
end else if (num == 4'd9) begin
rx_data_out <= rx_data;
rx_data_valid <= 1'b1;
end else begin
rx_data_valid <= 1'b0;
end
end
// 例化波特率节拍模块
baud #(BPS_PARA) u_baud(
.clk (clk),
.rst_n (rst_n),
.bps_en (bps_en),
.bps_clk(bps_clk)
);
endmodule
3 数据解码decoder.v :
cpp
module decoder(
input clk,
input rst_n,
input [7:0] rx_data_out,
input rx_data_valid,
output reg [31:0] seg_data, // 8位数码管数据(每4bit一个BCD)
output reg [7:0] data_en // 每位数码管使能
);
// ====== ASCII 模式(串口助手选 ASCII 发送)======
// PC 发送字符 '0'~'9',ASCII 码为 48~57,减去 48 得到数值
wire [3:0] bcd_val = rx_data_out[3:0]; // 取低4位作为BCD
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
seg_data <= 32'd0;
data_en <= 8'd0;
end else if (rx_data_valid) begin
// ASCII 模式:减48得到数值,左移推入
if (rx_data_out >= 8'd48 && rx_data_out <= 8'd57) begin
seg_data <= {seg_data[27:0], rx_data_out[3:0]};
data_en <= {data_en[6:0], 1'b1};
end
// Hex 模式:直接取数值,每2个hex占1个数码管位
// 如需 Hex 模式,取消下方注释,注释上方 ASCII 逻辑
// seg_data <= {seg_data[23:0], rx_data_out};
// data_en <= {data_en[5:0], 2'b11};
end
end
endmodule
4 数码管扫描sesgmentscan.v :
cpp
module segmentscan(
input clk,
input rst_n,
input [31:0] seg_data,
input [7:0] data_en,
output reg [7:0] seg_sel, // 位选(高有效,哪一位亮)
output reg [7:0] seg_db // 段选(共阳极,低电平点亮段)
);
// ====== 扫描计数器(约 1kHz 刷新)======
reg [15:0] cnt;
reg [2:0] scan_idx; // 0~7,当前扫描第几位
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
cnt <= 16'd0;
else
cnt <= cnt + 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
scan_idx <= 3'd0;
else if (cnt == 16'd0)
scan_idx <= scan_idx + 1'b1;
end
// ====== 位选信号 ======
always @(*) begin
seg_sel = 8'd0;
if (data_en[scan_idx])
seg_sel[scan_idx] = 1'b1;
end
// ====== 当前位 BCD 值 ======
reg [3:0] bcd;
always @(*) begin
case (scan_idx)
3'd0: bcd = seg_data[3:0];
3'd1: bcd = seg_data[7:4];
3'd2: bcd = seg_data[11:8];
3'd3: bcd = seg_data[15:12];
3'd4: bcd = seg_data[19:16];
3'd5: bcd = seg_data[23:20];
3'd6: bcd = seg_data[27:24];
3'd7: bcd = seg_data[31:28];
default: bcd = 4'd0;
endcase
end
// ====== BCD → 七段码(共阳极,低电平点亮)======
always @(*) begin
case (bcd)
4'd0: seg_db = 8'hC0; // 0
4'd1: seg_db = 8'hF9; // 1
4'd2: seg_db = 8'hA4; // 2
4'd3: seg_db = 8'hB0; // 3
4'd4: seg_db = 8'h99; // 4
4'd5: seg_db = 8'h92; // 5
4'd6: seg_db = 8'h82; // 6
4'd7: seg_db = 8'hF8; // 7
4'd8: seg_db = 8'h80; // 8
4'd9: seg_db = 8'h90; // 9
4'hA: seg_db = 8'h88; // A
4'hB: seg_db = 8'h83; // B
4'hC: seg_db = 8'hC6; // C
4'hD: seg_db = 8'hA1; // D
4'hE: seg_db = 8'h86; // E
4'hF: seg_db = 8'h8E; // F
default: seg_db = 8'hFF; // 熄灭
endcase
end
endmodule
5 顶层显示display_ctl.v :
cpp
module display_ctl(
input clk, // 12MHz 系统时钟
input rst_n, // 复位(低有效)
input uart_rx, // 串口接收线
output [7:0] seg_sel, // 数码管位选
output [7:0] seg_db // 数码管段选
);
wire [7:0] rx_data_out;
wire rx_data_valid;
wire [31:0] seg_data;
wire [7:0] data_en;
// 例化 UART 接收模块
uart_rx #(.BPS_PARA(1250)) u_rx( // 9600 baud
.clk (clk),
.rst_n (rst_n),
.uart_rx (uart_rx),
.rx_data_out (rx_data_out),
.rx_data_valid(rx_data_valid)
);
// 例化数据解码模块
decoder u_dec(
.clk (clk),
.rst_n (rst_n),
.rx_data_out (rx_data_out),
.rx_data_valid(rx_data_valid),
.seg_data (seg_data),
.data_en (data_en)
);
// 例化数码管扫描模块
segmentscan u_seg(
.clk (clk),
.rst_n (rst_n),
.seg_data(seg_data),
.data_en (data_en),
.seg_sel (seg_sel),
.seg_db (seg_db)
);
endmodule
4.3 仿真测试
编制仿真测试文件,进行测试,运行波形如下图所示。仿真测试文件如下文本框所示。

cpp
`timescale 1ns / 1ps
module uart_monitor_tb;
reg clk, rst_n;
reg uart_rx;
wire [7:0] seg_sel;
wire [7:0] seg_db;
// 例化顶层
display_ctl uut(
.clk (clk),
.rst_n (rst_n),
.uart_rx(uart_rx),
.seg_sel(seg_sel),
.seg_db (seg_db)
);
// 12MHz 时钟生成
initial begin
clk = 0;
forever #42 clk = ~clk; // 周期 ≈ 84ns → ~12MHz
end
// ====== UART 发送任务 ======
// 模拟 PC 串口发送一个字节:起始位(0) + 8位数据(低位在前) + 停止位(1)
task uart_send_byte;
input [7:0] data;
integer i;
begin
// 起始位
uart_rx = 0;
#104166; // 1/9600 ≈ 104166ns
// 数据位(低位在前)
for (i = 0; i < 8; i = i + 1) begin
uart_rx = data[i];
#104166;
end
// 停止位
uart_rx = 1;
#104166;
end
endtask
// ====== 主仿真流程 ======
initial begin
// 初始化
rst_n = 0;
uart_rx = 1; // 空闲状态为高
#500;
rst_n = 1;
#1000;
// 发送字符 '5' (ASCII 0x35 = 53)
$display("[%0t] Sending '5' (0x35)...", $time);
uart_send_byte(8'h35);
#500000;
// 发送字符 '8' (ASCII 0x38 = 56)
$display("[%0t] Sending '8' (0x38)...", $time);
uart_send_byte(8'h38);
#500000;
// 发送字符 '2' (ASCII 0x32 = 50)
$display("[%0t] Sending '2' (0x32)...", $time);
uart_send_byte(8'h32);
#500000;
$display("[%0t] === UART monitor simulation complete ===", $time);
$finish;
end
endmodule
5 RISC-V_SoC
5.1 方案规划

|--------|--------------------------|
| 参数 | 规格 |
| ISA | RV32I 子集(8 条指令) |
| 数据宽度 | 32 位 |
| 寄存器 | x0~x31(x0 恒为 0) |
| 指令ROM | 256 × 32 bit (1 KB) |
| 数据RAM | 256 × 32 bit (1 KB) |
| GPIO | 8 位输出(映射 LED) |
| 地址映射 | RAM: 0x0000,GPIO: 0x4000 |
| 主时钟 | 12 MHz(STEPFPGA 板载) |
5.2 编码设计
新建编程文件:risc_soc.v
cpp
`timescale 1ns / 1ps // 包含`timescale 1ns/1ps声明,仿真时不会出现 vsim-3009 警告
// =========================================================
// RISC-V SoC 顶层模块
// ISA: RV32I 子集 | ROM: 1KB | RAM: 1KB | GPIO: 8-bit
// =========================================================
module risc_soc (
input clk, // 12 MHz
input rst_n, // 按键复位 active-low
output [7:0] gpio_out // 8-bit GPIO → LED
);
// ---- CPU 信号 ----
wire [31:0] instr_addr, instr_data;
wire [31:0] data_addr, data_wdata, data_rdata;
wire data_we;
// ---- 总线解码 ----
wire sel_ram = ~data_addr[10]; // 0x0000~0x03FF
wire sel_gpio = data_addr[10]; // 0x0400+
wire [31:0] ram_rdata;
wire [31:0] gpio_rdata = {24'd0, gpio_out};
assign data_rdata = sel_gpio ? gpio_rdata : ram_rdata;
// ---- 实例化 CPU ----
rv32i_cpu u_cpu (
.clk (clk),
.rst_n (rst_n),
.instr_addr (instr_addr),
.instr_data (instr_data),
.data_addr (data_addr),
.data_wdata (data_wdata),
.data_rdata (data_rdata),
.data_we (data_we)
);
// ---- 指令 ROM ----
instr_rom u_rom ( .addr (instr_addr[9:2]), .data (instr_data) );
// ---- 数据 RAM ----
data_ram u_ram ( .clk (clk), .we (data_we & sel_ram),
.addr (data_addr[9:2]), .din (data_wdata), .dout (ram_rdata) );
// ---- GPIO 寄存器 ----
gpio_reg u_gpio ( .clk (clk), .rst_n (rst_n), .we (data_we & sel_gpio),
.din (data_wdata[7:0]), .gpio_out (gpio_out) );
endmodule
// =========================================================
// RV32I CPU 核心(单周期实现)
// =========================================================
module rv32i_cpu (
input clk,
input rst_n,
output reg [31:0] instr_addr, // PC → ROM
input [31:0] instr_data, // ROM → 指令
output [31:0] data_addr, // 数据地址
output [31:0] data_wdata, // 写数据
input [31:0] data_rdata, // 读数据
output data_we // 写使能
);
// ---- 指令解码字段 ----
wire [6:0] opcode = instr_data[6:0];
wire [4:0] rd = instr_data[11:7];
wire [2:0] funct3 = instr_data[14:12];
wire [4:0] rs1 = instr_data[19:15];
wire [4:0] rs2 = instr_data[24:20];
wire [6:0] funct7 = instr_data[31:25];
// ---- 立即数生成 ----
wire [31:0] imm_i = {{20{instr_data[31]}}, instr_data[31:20]};
wire [31:0] imm_s = {{20{instr_data[31]}}, instr_data[31:25], instr_data[11:7]};
wire [31:0] imm_b = {{19{instr_data[31]}}, instr_data[31], instr_data[7],
instr_data[30:25], instr_data[11:8], 1'b0};
// ---- opcode 类型判断 ----
wire is_rtype = (opcode == 7'b0110011); // ADD/SUB/AND/OR
wire is_itype = (opcode == 7'b0010011); // ADDI
wire is_load = (opcode == 7'b0000011); // LW
wire is_store = (opcode == 7'b0100011); // SW
wire is_branch= (opcode == 7'b1100011); // BEQ
// ---- 寄存器堆 ----
reg [31:0] regfile [0:31];
wire [31:0] rs1_data = (rs1 == 5'd0) ? 32'd0 : regfile[rs1];
wire [31:0] rs2_data = (rs2 == 5'd0) ? 32'd0 : regfile[rs2];
// ---- ALU ----
reg [31:0] alu_result;
always @(*) begin
alu_result = 32'd0;
if (is_rtype) begin
case ({funct7, funct3})
10'b0000000_000: alu_result = rs1_data + rs2_data; // ADD
10'b0100000_000: alu_result = rs1_data - rs2_data; // SUB
10'b0000000_111: alu_result = rs1_data & rs2_data; // AND
10'b0000000_110: alu_result = rs1_data | rs2_data; // OR
default: alu_result = 32'd0;
endcase
end else if (is_itype) begin
alu_result = rs1_data + imm_i; // ADDI
end else if (is_load || is_store) begin
alu_result = rs1_data + (is_store ? imm_s : imm_i); // 地址计算
end
end
// ---- 数据通路输出 ----
assign data_addr = alu_result;
assign data_wdata = rs2_data;
assign data_we = is_store;
// ---- 写回 + PC 更新 ----
wire branch_taken = is_branch & (rs1_data == rs2_data);
wire [31:0] rd_wdata = is_load ? data_rdata : alu_result;
wire rd_we = (is_rtype || is_itype || is_load) & (rd != 5'd0);
integer i;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
instr_addr <= 32'd0;
for (i = 0; i < 32; i = i + 1)
regfile[i] <= 32'd0;
end else begin
// 寄存器写回
if (rd_we)
regfile[rd] <= rd_wdata;
// PC 更新
if (branch_taken)
instr_addr <= instr_addr + imm_b;
else
instr_addr <= instr_addr + 32'd4;
end
end
endmodule
// =========================================================
// 指令 ROM(预装测试程序)
// =========================================================
module instr_rom (
input [7:0] addr,
output reg [31:0] data
);
always @(*) begin
case (addr)
// 地址 机器码 汇编 注释
8'd0: data = 32'h00100093; // addi x1, x0, 1 x1=1
8'd1: data = 32'h00200113; // addi x2, x0, 2 x2=2
8'd2: data = 32'h00300193; // addi x3, x0, 3 x3=3
8'd3: data = 32'h00208233; // add x4, x1, x2 x4=3
8'd4: data = 32'h003202B3; // add x5, x4, x3 x5=6
8'd5: data = 32'h40128333; // sub x6, x5, x1 x6=5
8'd6: data = 32'h0032F3B3; // and x7, x5, x3 x7=2
8'd7: data = 32'h0012E433; // or x8, x5, x1 x8=7
8'd8: data = 32'h00502023; // sw x5, 0(x0) RAM[0]=6
8'd9: data = 32'h00002483; // lw x9, 0(x0) x9=6
8'd10: data = 32'h40000513; // addi x10, x0, 0x400 x10=0x400
8'd11: data = 32'h00552023; // sw x5, 0(x10) GPIO=6
8'd12: data = 32'h00000063; // beq x0, x0, 0 死循环
default: data = 32'h00000013; // NOP
endcase
end
endmodule
// =========================================================
// 数据 RAM (256 x 32)
// =========================================================
module data_ram (
input clk,
input we,
input [7:0] addr,
input [31:0] din,
output [31:0] dout
);
reg [31:0] mem [0:255];
assign dout = mem[addr];
always @(posedge clk) begin
if (we)
mem[addr] <= din;
end
endmodule
// =========================================================
// GPIO 输出寄存器 (8-bit)
// =========================================================
module gpio_reg (
input clk,
input rst_n,
input we,
input [7:0] din,
output reg [7:0] gpio_out
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
gpio_out <= 8'd0;
else if (we)
gpio_out <= din;
end
endmodule
5.3 仿真测试
编制仿真测试文件,进行测试,运行波形如下图所示。仿真测试文件如下文本框所示。


cpp
`timescale 1ns / 1ps
module risc_soc_tb;
// ---- 信号 ----
reg clk;
reg rst_n;
wire [7:0] gpio_out;
// ---- 实例化 SoC ----
risc_soc uut ( .clk (clk), .rst_n (rst_n), .gpio_out (gpio_out) );
// ---- 50 MHz 时钟 (周期 20ns) ----
initial clk = 0;
always #10 clk = ~clk;
// ---- 寄存器名称用于显示 ----
wire [31:0] x1 = uut.u_cpu.regfile[1];
wire [31:0] x2 = uut.u_cpu.regfile[2];
wire [31:0] x3 = uut.u_cpu.regfile[3];
wire [31:0] x4 = uut.u_cpu.regfile[4];
wire [31:0] x5 = uut.u_cpu.regfile[5];
wire [31:0] x6 = uut.u_cpu.regfile[6];
wire [31:0] x7 = uut.u_cpu.regfile[7];
wire [31:0] x8 = uut.u_cpu.regfile[8];
wire [31:0] x9 = uut.u_cpu.regfile[9];
wire [31:0] x10 = uut.u_cpu.regfile[10];
// ---- PC 和指令追踪 ----
wire [31:0] pc = uut.u_cpu.instr_addr;
wire [31:0] instr = uut.u_rom.data;
// ---- 每周期打印执行跟踪 ----
always @(posedge clk) begin
if (rst_n) begin
$display("[%0t] PC=%h INSTR=%h | x1=%0d x2=%0d x3=%0d x4=%0d x5=%0d x6=%0d x7=%0d x8=%0d x9=%0d x10=%h | GPIO=%b",
$time, pc, instr,
x1, x2, x3, x4, x5, x6, x7, x8, x9, x10,
gpio_out);
end
end
// ---- GPIO 变化检测 ----
always @(gpio_out) begin
$display("*** GPIO changed to: %0d (0x%h) at time %0t ***",
gpio_out, gpio_out, $time);
end
// ---- 测试流程 ----
initial begin
$display("======================================");
$display(" RISC-V SoC Simulation Start");
$display("======================================");
// 复位
rst_n = 0;
#50;
rst_n = 1;
// 运行 20 个时钟周期(足够执行全部 13 条指令)
#400;
$display("======================================");
$display(" Final Register State:");
$display(" x1=%0d x2=%0d x3=%0d", x1, x2, x3);
$display(" x4=%0d x5=%0d x6=%0d", x4, x5, x6);
$display(" x7=%0d x8=%0d x9=%0d", x7, x8, x9);
$display(" x10=0x%h", x10);
$display(" GPIO = %0d (binary: %b)", gpio_out, gpio_out);
$display(" RAM[0] = %0d", uut.u_ram.mem[0]);
$display("======================================");
// 验证结果
if (x5 == 32'd6 && gpio_out == 8'd6)
$display(">>> TEST PASSED <<<");
else
$display(">>> TEST FAILED <<< Expected x5=6, GPIO=6");
$finish;
end
endmodule