Quick Start:快速判断你该学哪个
- 步骤 1:打开主流招聘平台(如Boss直聘、猎聘、牛客网),搜索“FPGA实习生”、“数字IC实习生”、“芯片验证实习生”等关键词,筛选2026年发布的最新岗位。
- 步骤 2:统计岗位JD中“Verilog”、“SystemVerilog”、“SV”的出现频次。建议至少统计50个岗位,记录两者单独出现和同时出现的比例。
- 步骤 3:关注“职责描述”而非“任职要求”。很多JD在“任职要求”里笼统写“熟悉Verilog或SystemVerilog”,但“职责描述”里会明确写“使用SystemVerilog搭建UVM验证环境”或“使用Verilog完成RTL设计”。
- 步骤 4:区分岗位方向:
• 设计岗(RTL Design):几乎100%要求Verilog,部分要求SystemVerilog用于testbench。
• 验证岗(Verification):几乎100%要求SystemVerilog + UVM,Verilog仅作为基础。
• 综合/实现岗(Synthesis/Implementation):主要用Verilog,偶尔涉及SystemVerilog的interface。 - 步骤 5:查看企业类型:
• 外企/大型IC公司(如Intel、AMD、NVIDIA、Synopsys、Cadence):实习生岗位几乎全部要求SystemVerilog,因为验证占工作量的60%-70%。
• 国内中小型FPGA公司/初创(如紫光同创、安路科技、高云半导体):更偏向Verilog,因为团队小、设计为主,验证流程不完善。
• 研究所/军工:几乎只用Verilog,SystemVerilog支持有限(工具链老旧)。 - 步骤 6:结论速查:
• 如果你目标是大厂/外企/IC验证岗:优先学SystemVerilog + UVM。
• 如果你目标是FPGA设计/中小公司/研究所:优先学Verilog,SystemVerilog作为加分项。
• 最稳妥策略:先精通Verilog(3-6个月),再学SystemVerilog(1-2个月)用于验证,同时掌握UVM框架。 - 验收点:完成上述统计后,你应该能写出一个100字以内的个人学习路线图,并解释为什么。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| EDA工具 | Vivado 2024.2 / Quartus Prime Pro 24.3 | Vivado 2023.x / Quartus 22.x(但SystemVerilog支持可能受限) |
| 仿真器 | QuestaSim 2024.3 / VCS 2024.06 / Xsim | Modelsim SE-64 2024.0(免费版功能受限) |
| 器件/板卡 | Xilinx Artix-7 / Kintex-7 / Zynq-7000 系列开发板 | Altera Cyclone V / Intel Agilex 7 |
| 时钟/复位 | 板载50MHz有源晶振,全局异步复位(低有效) | 100MHz晶振,同步复位(不推荐) |
| 接口依赖 | UART-USB桥(CP2102/FT232)用于调试 | JTAG调试器(Digilent HS2/Xilinx Platform Cable) |
| 约束文件 | XDC(Vivado)或SDC(Quartus),至少包含时钟周期、输入输出延迟、false_path | 无约束跑综合(仅用于功能验证,不可上板) |
| 操作系统 | Windows 11 / Ubuntu 22.04 LTS | CentOS 7(已停止维护,不推荐) |
| 编程语言基础 | 至少掌握C语言基础(指针、结构体、循环) | Python也可,但C更贴近硬件思维 |
注意:以上版本号均为2026年5月可获取的典型配置,具体以各厂商官网最新发布为准。如果你使用的工具版本较老(如Vivado 2018.3),SystemVerilog的某些语法(如interface、assertion)可能不支持或行为不同。
目标与验收标准
- 功能点:
• 能用Verilog独立完成一个中等复杂度的RTL模块(如SPI Master、UART、FIFO)。
• 能用SystemVerilog搭建一个带随机约束的testbench,并完成覆盖率收集。
• 理解UVM的基本框架(uvm_component、uvm_sequence、uvm_driver)。 - 性能指标:
• 设计模块在目标器件上Fmax ≥ 150MHz(典型值,以实际约束为准)。
• 验证覆盖率(代码覆盖率 + 功能覆盖率)≥ 90%。 - 资源占用:
• 设计模块LUT ≤ 500,FF ≤ 400(以Artix-7为参考)。 - 验收方式:
• 仿真通过:所有testcase pass,无时序违例。
• 上板验证:LED闪烁/串口打印“Hello FPGA”等可观测现象。
• 面试中能解释:为什么用Verilog写设计、为什么用SystemVerilog写验证。
实施步骤
阶段一:工程结构搭建
- 1. 创建顶层目录:建议使用标准结构:
project/
├── rtl/ # 所有Verilog/SystemVerilog设计文件
├── sim/ # 仿真文件(testbench、波形、脚本)
├── constr/ # 约束文件(XDC/SDC)
├── ip/ # IP核(如PLL、FIFO Generator)
├── scripts/ # Tcl脚本(自动编译、运行)
├── docs/ # 设计文档
└── output/ # 综合/实现结果(bit、rpt) - 2. 创建Makefile或Tcl脚本:实现一键编译+仿真+综合。至少包含:
•make compile:调用Vivado/QuestaSim编译所有RTL和TB。
•make sim:运行仿真并导出波形。
•make synth:运行综合并生成资源报告。
•make clean:清理中间文件。 - 3. 编写一个简单的“Hello World”模块:用Verilog写一个分频器,用SystemVerilog写一个testbench,验证分频输出。这一步确保工具链和环境正确。
- 常见坑与排查:
• 坑1:Windows路径含中文或空格导致Vivado编译失败。→ 解决方案:路径全英文,无空格。
• 坑2:QuestaSim默认不支持SystemVerilog,需在编译时加-sv选项。→ 解决方案:在do文件中写vlog -sv *.sv。
阶段二:关键模块实现(以SPI Master为例)
Verilog设计代码(spi_master.v):
module spi_master #(
parameter CLK_DIV = 4, // 时钟分频系数,产生SCLK = CLK/CLK_DIV
parameter DATA_WIDTH = 8
)(
input wire clk,
input wire rst_n,
input wire start,
input wire [DATA_WIDTH-1:0] tx_data,
output reg [DATA_WIDTH-1:0] rx_data,
output reg sclk,
output reg mosi,
input wire miso,
output reg cs_n,
output reg busy
);
// 状态机定义
typedef enum reg [1:0] {
IDLE = 2'b00,
TRANSFER = 2'b01,
DONE = 2'b10
} state_t;
state_t state, next_state;
reg [DATA_WIDTH-1:0] shift_reg;
reg [3:0] bit_cnt;
reg [7:0] clk_cnt;
reg sclk_en;
// 状态机第一段:状态更新
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 状态机第二段:下一状态逻辑
always_comb begin
next_state = state;
case (state)
IDLE: begin
if (start)
next_state = TRANSFER;
end
TRANSFER: begin
if (bit_cnt == DATA_WIDTH-1 && clk_cnt == CLK_DIV-1)
next_state = DONE;
end
DONE: begin
next_state = IDLE;
end
endcase
end
// 状态机第三段:输出逻辑
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cs_n <= 1'b1;
sclk <= 1'b0;
mosi <= 1'b0;
busy <= 1'b0;
rx_data <= '0;
shift_reg <= '0;
bit_cnt <= '0;
clk_cnt <= '0;
sclk_en <= 1'b0;
end else begin
case (state)
IDLE: begin
cs_n <= 1'b1;
busy <= 1'b0;
sclk_en <= 1'b0;
if (start) begin
cs_n <= 1'b0;
busy <= 1'b1;
shift_reg <= tx_data;
bit_cnt <= 0;
clk_cnt <= 0;
sclk_en <= 1'b1;
end
end
TRANSFER: begin
if (clk_cnt == CLK_DIV/2 - 1) begin
sclk <= ~sclk;
if (sclk) begin // SCLK下降沿采样MISO
shift_reg <= {shift_reg[DATA_WIDTH-2:0], miso};
end else begin // SCLK上升沿更新MOSI
mosi <= shift_reg[DATA_WIDTH-1];
end
end
if (clk_cnt == CLK_DIV - 1) begin
clk_cnt <= 0;
bit_cnt <= bit_cnt + 1;
end else begin
clk_cnt <= clk_cnt + 1;
end
end
DONE: begin
cs_n <= 1'b1;
busy <= 1'b0;
rx_data <= shift_reg;
sclk_en <= 1'b0;
end
endcase
end
end
endmodule逐行说明(Verilog设计)
- 第1-2行:模块声明,使用parameter定义两个可配置参数:CLK_DIV(时钟分频系数,决定SCLK频率)和DATA_WIDTH(数据位宽)。这是Verilog-2001的ANSI风格端口声明,推荐用于新设计。
- 第3-11行:端口列表。clk和rst_n是全局信号;start、tx_data是输入;rx_data、sclk、mosi、cs_n、busy是输出。注意所有输出都声明为reg类型,因为需要在always块中赋值。
- 第14-18行:使用
typedef enum定义状态机状态。这是SystemVerilog语法,但在Vivado/Quartus中Verilog也支持(需文件后缀为.sv)。如果用纯Verilog,应使用parameter定义状态值。 - 第20-24行:内部寄存器声明。shift_reg是移位寄存器,bit_cnt是位计数器,clk_cnt是时钟分频计数器,sclk_en是SCLK使能信号。
- 第27-31行:状态机第一段——时序逻辑更新当前状态。注意使用
always_ff(SystemVerilog语法)或always @(posedge clk or negedge rst_n)(Verilog语法)。这里混合使用了SV语法,如果工具不支持,需改为纯Verilog写法。 - 第34-43行:状态机第二段——组合逻辑计算下一状态。使用
always_comb(SV语法)或always @(*)(Verilog语法)。注意在TRANSFER状态下,当位计数达到最后一位且时钟计数达到分频周期时,跳转到DONE。 - 第46-82行:状态机第三段——时序逻辑输出。在IDLE状态,检测到start信号后拉低cs_n(片选有效),加载tx_data到shift_reg,使能sclk_en。在TRANSFER状态,通过clk_cnt产生分频时钟sclk,在sclk上升沿更新MOSI,下降沿采样MISO。注意这里用sclk的边沿作为条件,实际综合时可能产生时钟门控问题,更稳健的做法是用clk_cnt的中间值产生sclk使能信号,而不是直接生成sclk。
- 第84-90行:DONE状态:拉高cs_n,清空busy,将shift_reg赋值给rx_data,关闭sclk_en。
SystemVerilog Testbench代码(spi_master_tb.sv):
`timescale 1ns/1ps
module spi_master_tb;
// 参数
parameter CLK_PERIOD = 20; // 50MHz时钟周期20ns
parameter CLK_DIV = 4;
parameter DATA_WIDTH = 8;
// 接口信号
logic clk;
logic rst_n;
logic start;
logic [DATA_WIDTH-1:0] tx_data;
logic [DATA_WIDTH-1:0] rx_data;
logic sclk;
logic mosi;
logic miso;
logic cs_n;
logic busy;
// 待测模块实例化
spi_master #(
.CLK_DIV(CLK_DIV),
.DATA_WIDTH(DATA_WIDTH)
) dut (
.clk(clk),
.rst_n(rst_n),
.start(start),
.tx_data(tx_data),
.rx_data(rx_data),
.sclk(sclk),
.mosi(mosi),
.miso(miso),
.cs_n(cs_n),
.busy(busy)
);
// 时钟生成
initial begin
clk = 0;
forever #(CLK_PERIOD/2) clk = ~clk;
end
// 复位和测试激励
initial begin
// 初始化
rst_n = 0;
start = 0;
tx_data = 8'hA5;
miso = 0;
#100;
rst_n = 1;
#100;
// 发送数据
@(posedge clk);
start = 1;
tx_data = 8'hA5;
@(posedge clk);
start = 0;
// 等待传输完成
wait(!busy);
#50;
// 检查结果
if (rx_data == 8'hA5)
$display("Test PASS: rx_data = %h", rx_data);
else
$display("Test FAIL: rx_data = %h, expected A5", rx_data);
#200;
$finish;
end
// 模拟MISO数据(回环模式)
always_ff @(posedge sclk or negedge cs_n) begin
if (!cs_n)
miso <= mosi; // 简单回环
else
miso <= 0;
end
// 波形导出
initial begin
$dumpfile("spi_master_tb.vcd");
$dumpvars(0, spi_master_tb);
end
endmodule逐行说明(SystemVerilog Testbench)
- 第1行:
`timescale 1ns/1ps定义时间单位和精度。1ns表示#1代表1ns,1ps表示精度到1ps。这是仿真必需,否则工具默认可能为1s。 - 第3行:模块声明。Testbench不需要端口,因为它是顶层。
- 第6-8行:参数定义,与DUT保持一致。CLK_PERIOD=20ns对应50MHz时钟。
- 第11-21行:使用
logic类型声明所有信号。SystemVerilog中logic可以替代wire和reg,简化代码。注意miso是输入到DUT,但在TB中由我们驱动,所以声明为logic。 - 第24-40行:DUT实例化。使用
#(.PARAM_NAME(VALUE))语法传递参数,这是SystemVerilog推荐的命名参数传递方式,比Verilog的位置参数更安全。 - 第43-46行:时钟生成。使用
initial块和forever循环产生周期为CLK_PERIOD的时钟。注意#(CLK_PERIOD/2)是延迟表达式,需要CLK_PERIOD是常数或参数。 - 第49-70行:测试激励。先复位(rst_n=0)保持100ns,然后释放复位。在时钟上升沿拉高start并设置tx_data=8'hA5,下一个时钟沿拉低start。然后等待busy信号变低(表示传输完成),检查rx_data是否等于0xA5。注意
wait(!busy)是SV的阻塞等待语句,Verilog中需用@(negedge busy)替代。 - 第73-78行:模拟MISO数据。这里使用简单回环模式:将mosi直接赋值给miso。实际应用中,miso可能来自外部SPI从设备,这里只是为了验证DUT的发送和接收功能。
- 第81-84行:波形导出。使用
$dumpfile和$dumpvars系统任务,生成VCD文件供GTKWave或Vivado查看。如果使用QuestaSim,可以用vcd file和vcd add命令。
阶段三:时序与约束
- 1. 创建主时钟约束:在XDC文件中写入
create_clock -period 20.000 -name sys_clk [get_ports clk],对应50MHz输入时钟。 - 2. 生成时钟约束:如果使用PLL或MMCM生成内部时钟,Vivado会自动识别,但建议手动约束生成时钟的相位和抖动。
- 3. 输入输出延迟约束:对于SPI接口,需要约束mosi、sclk、cs_n相对于时钟的输出延迟,以及miso的输入延迟。典型值:
set_output_delay -clock [get_clocks sclk] -max 5 [get_ports {mosi sclk cs_n}]。 - 4. 异步复位约束:使用
set_false_path -from [get_ports rst_n] -to [get_regs *]将复位路径设为false path,避免时序分析报告大量违例。 - 常见坑与排查:
• 坑1:忘记约束生成时钟,导致时序分析报告不准确。→ 解决方案:综合后打开“Report Timing Summary”,检查是否有unconstrained paths。
• 坑2:SPI的sclk是内部生成的时钟,如果直接作为时钟信号使用,Vivado会报“clock gating”警告。→ 解决方案:将sclk作为普通数据信号处理,用使能信号代替时钟边沿。
阶段四:验证与上板
- 1. 仿真验证:运行
vsim -c -do "run -all"(QuestaSim)或xsim --runall(Vivado)。检查波形:cs_n在传输期间为低,sclk有8个周期,mosi在sclk上升沿更新,miso在下降沿采样。 - 2. 综合与实现:运行
synth_design -top spi_master和place_design、route_design。检查资源报告:LUT、FF、IO是否在预算内。 - 3. 生成比特流并下载:使用
write_bitstream生成.bit文件,通过Vivado Hardware Manager或openFPGALoader下载到开发板。 - 4. 上板验证:将SPI Master的输出连接到SPI Flash或DAC芯片(如AD5601),观察输出波形或用逻辑分析仪抓取。如果没有外部设备,可以将mosi和miso短接(回环),验证发送和接收数据一致。
- 常见坑与排查:
• 坑1:上板后无输出。→ 检查:时钟是否正常(用示波器测clk pin)、复位是否释放(rst_n是否高电平)、电源是否稳定。
• 坑2:仿真通过但上板失败。→ 检查:约束是否完整、是否有未初始化的寄存器、是否有组合逻辑环路。




