从零实现Verilog双控开关与nvboard联调实战指南
开篇:为什么选择Verilog与nvboard组合?
第一次接触FPGA开发时,看到那些闪烁的LED和跳动的波形,总有种打开新世界大门的兴奋感。但随之而来的环境配置、语法错误和仿真问题,又常常让人望而却步。Verilog作为硬件描述语言的入门首选,配合南京大学开源的nvboard虚拟开发板,确实为初学者提供了极佳的学习路径。特别是在"一生一芯"这样的教学项目中,这种组合能快速验证数字电路设计,所见即所得。
本文将从一个真实的双控开关案例出发,不仅展示标准实现流程,更会重点剖析那些官方文档没写的细节问题。比如为什么引脚绑定脚本突然报错?Makefile变量究竟该怎么设置?仿真时界面显示正常但功能失效又是怎么回事?这些坑我都亲自踩过,现在把解决方案整理成可复用的经验。
1. 开发环境准备与项目初始化
1.1 工具链安装清单
开始前需要准备以下工具(以Ubuntu 20.04为例):
# 基础编译工具
sudo apt install build-essential git make
# Verilator仿真器(建议4.0以上版本)
sudo apt install verilator
# Python3及相关依赖
sudo apt install python3 python3-pip
注意:不同Linux发行版的包管理命令可能略有差异,遇到依赖问题时建议查阅对应发行版的文档。
1.2 nvboard环境配置
从GitHub克隆最新版nvboard仓库:
git clone https://github.com/NJU-ProjectN/nvboard.git
配置环境变量是第一个容易出错的地方。很多教程会建议在~/.bashrc中添加:
export NVBOARD_HOME=/path/to/nvboard
但实际使用时要注意两点:
- 路径必须使用绝对路径
- 在Makefile中引用时要用
${NVBOARD_HOME}而非$(NVBOARD_HOME)
验证配置是否生效:
source ~/.bashrc
echo ${NVBOARD_HOME}
2. Verilog核心模块设计
2.1 双控开关的电路原理
双控开关的本质是一个异或逻辑:当两个开关状态相同时输出0,不同时输出1。用真值表表示如下:
| 开关A | 开关B | 输出 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
2.2 Verilog实现代码
创建vsrc/on_off_switch.v文件:
module on_off_switch(
input wire a,
input wire b,
output wire f
);
// 异或逻辑实现
assign f = a ^ b;
endmodule
常见问题:
- 端口信号未声明
wire类型(虽然综合器能自动推断,但显式声明更规范) - 误用阻塞赋值
=代替连续赋值assign - 忘记写结束的分号
2.3 顶层模块封装
创建vsrc/top.v作为顶层模块:
module top(
input clk,
input rst,
input switch_a,
input switch_b,
output led_out
);
on_off_switch u_switch(
.a(switch_a),
.b(switch_b),
.f(led_out)
);
endmodule
3. nvboard引脚绑定与约束文件
3.1 约束文件编写
在constr/top.nxdc中定义引脚映射:
# 时钟信号
pin clk CLOCK 100000000
# 复位信号
pin rst BUTTON 1
# 两个物理开关
pin switch_a SWITCH 0
pin switch_b SWITCH 1
# LED输出
pin led_out LED 0
3.2 自动生成绑定代码
执行引脚绑定命令时,特别注意变量引用方式:
python3 ${NVBOARD_HOME}/scripts/auto_pin_bind.py \
${NVBOARD_HOME}/example/switch/constr/top.nxdc \
${NVBOARD_HOME}/example/switch/build/auto_bind.cpp
避坑指南:
- 路径中的
$NVBOARD_HOME要用花括号${}而非圆括号$() - 确保Python3在系统路径中
- 如果报错"No such file",检查路径是否包含空格或特殊字符
4. Makefile配置详解
4.1 基础编译配置
创建项目Makefile,关键参数说明如下:
TOPNAME = top
NXDC_FILES = constr/top.nxdc
VERILATOR = verilator
BUILD_DIR = ./build
OBJ_DIR = $(BUILD_DIR)/obj_dir
BIN = $(BUILD_DIR)/$(TOPNAME)
default: $(BIN)
$(shell mkdir -p $(BUILD_DIR))
SRC_AUTO_BIND = $(abspath $(BUILD_DIR)/auto_bind.cpp)
$(SRC_AUTO_BIND): $(NXDC_FILES)
python3 ${NVBOARD_HOME}/scripts/auto_pin_bind.py $^ $@
VSRCS = $(shell find $(abspath ./vsrc) -name "*.v")
CSRCS = $(shell find $(abspath ./csrc) -name "*.c" -or -name "*.cc" -or -name "*.cpp")
CSRCS += $(SRC_AUTO_BIND)
include ${NVBOARD_HOME}/scripts/nvboard.mk
INCFLAGS = $(addprefix -I, $(INC_PATH))
CXXFLAGS += $(INCFLAGS) -DTOP_NAME="\"V$(TOPNAME)\""
$(BIN): $(VSRCS) $(CSRCS) $(NVBOARD_ARCHIVE)
@rm -rf $(OBJ_DIR)
$(VERILATOR) $(VERILATOR_CFLAGS) \
--top-module $(TOPNAME) $^ \
$(addprefix -CFLAGS , $(CXXFLAGS)) \
--Mdir $(OBJ_DIR) --exe -o $(abspath $(BIN))
run: $(BIN)
@$^
clean:
rm -rf $(BUILD_DIR)
.PHONY: default run clean
4.2 常见编译错误处理
| 错误类型 | 可能原因 | 解决方案 |
|---|---|---|
NVBOARD_HOME未定义 | 环境变量未正确设置 | 检查~/.bashrc并source |
verilator命令未找到 | 未安装或不在PATH | apt安装并验证版本 |
undefined reference | 链接库路径错误 | 检查nvboard.mk包含路径 |
语法错误 | Verilog代码问题 | 用iverilog先做语法检查 |
5. 仿真测试与调试技巧
5.1 主循环中的关键调用
创建csrc/sim.cpp测试文件:
#include <nvboard.h>
#include "Vtop.h"
static TOP_NAME dut;
void nvboard_bind_all_pins(TOP_NAME* top);
int main() {
nvboard_bind_all_pins(&dut);
nvboard_init();
while(1) {
nvboard_update();
dut.eval(); // 这个调用绝对不能少!
}
}
血泪教训: 曾经花了三小时排查为什么开关动作没有反应,最后发现是漏掉了dut.eval()。这个函数负责更新电路状态,没有它仿真就无法进行逻辑计算。
5.2 实时调试方法
- printf调试:在C++测试代码中添加打印语句
- 波形查看:通过Verilator生成vcd波形
- 信号探针:在Verilog中使用
$display监控信号
always @(a or b) begin
$display("Time=%t: a=%b, b=%b, f=%b", $time, a, b, f);
end
6. 进阶优化与扩展思路
6.1 添加去抖动逻辑
机械开关实际使用时需要防抖处理:
module debounce (
input clk,
input button_in,
output reg button_out
);
reg [19:0] count;
reg button_sync;
always @(posedge clk) begin
button_sync <= button_in;
if (button_sync ^ button_out) begin
count <= count + 1;
if (&count) button_out <= ~button_out;
end else begin
count <= 0;
end
end
endmodule
6.2 多开关级联控制
扩展为三控开关只需修改逻辑:
assign f = a ^ b ^ c;
6.3 性能优化技巧
- 为Verilator添加
-O3优化选项 - 减少不必要的信号更新
- 使用
always_comb替代连续的assign
always_comb begin
f = a ^ b;
end
7. 项目组织与版本控制建议
推荐的项目目录结构:
project/
├── vsrc/ # Verilog源代码
│ ├── top.v
│ └── on_off_switch.v
├── csrc/ # C++测试代码
│ └── sim.cpp
├── constr/ # 约束文件
│ └── top.nxdc
├── build/ # 构建输出
├── Makefile
└── README.md
在Git版本控制中应该忽略的文件:
# .gitignore内容
build/
*.vcd
*.log
8. 真实项目中的经验分享
第一次成功点亮LED时的兴奋感至今难忘,但也遇到过几个印象深刻的问题:
- 环境变量失效:发现终端里能识别
NVBOARD_HOME但Makefile读不到,最后发现是sudo执行时环境变量不同 - 路径包含空格:项目放在"My Project"目录下导致脚本解析失败
- 版本不兼容:使用较新的Verilator 5.0时某些参数语法有变化
建议在项目README中记录以下信息:
- 使用的工具版本号
- 特殊的环境配置要求
- 已知问题的解决方法
调试时的一个小技巧:先单独验证Verilog代码功能(可以用iverilog+gtkwave),再集成到nvboard中,这样能快速定位问题是出在硬件逻辑还是接口部分。
&spm=1001.2101.3001.5002&articleId=154670021&d=1&t=3&u=bb3c8e76f49843f68fab43776fd9c67a)
351

被折叠的 条评论
为什么被折叠?



