verilog测试平台设计与verilog的synthesis
verilog测试平台的设计与verilog的synthesis
verilog模块代码在写好后,还需要进行调试和仿真,这有些类似于hspice中的测试文件,也要包括声明,激励定义,结果处理。Testbench(测试平台)是数字电路设计验证中的核心工具,用于模拟和验证硬件设计(DUT, Design Under Test)的功能正确性。其核心原理是通过仿真环境生成输入激励、监测输出响应,并与预期结果对比。以下是编写原理的详细说明:
一个典型的 Testbench 包含以下模块:
- DUT 实例化:将被测设计连接到 Testbench。
- 激励生成模块:模拟输入信号(如时钟、复位、数据等)。
- 监控模块:实时捕获 DUT 的输出信号。
- 自动检查模块:对比输出结果与预期值(Golden Model)。
DUT实例化
DUT就是design under test,DUT模块在被设计好后需要在testbench中进行实例化。DUT示例化的示例代码如下:
module counter (//模块定义
input clk, // 输入端口:时钟
input reset, // 输入端口:复位
input enable, // 输入端口:使能
output [3:0] count // 输出端口:计数值
);
module testbench;
reg tb_clk; // 测试平台生成的时钟信号(reg类型,驱动DUT输入)
reg tb_reset; // 测试平台生成的复位信号
reg tb_enable;
wire [3:0] tb_count; // 接收DUT的输出(wire类型)
endmodule
counter dut (//实例化,通常是模块端口名在前而测试平台的信号名在后
.clk(tb_clk), // DUT的clk端口 <—— 测试平台的tb_clk信号
.reset(tb_reset),
.enable(tb_enable),
.count(tb_count)
);
二者之间的映射关系具体如下所示:
激励生成
在Verilog测试平台设计中,激励生成对于测试模块编写的正确性至关重要。这里主要介绍三个部分的激励生成:
时钟与复位:基础激励,需严格同步。
数据激励:支持确定性和随机生成。
同步控制:使用 @(edge)
或 #delay
控制时序。
// 示例:生成50MHz时钟(周期20ns)
reg clk;
initial begin
clk = 0;
forever #10 clk = ~clk; // 每10ns翻转一次,使用forever来无限次的循环,#10是延迟10ns
end
//示例:复位信号的生成
reg reset;
initial begin
reset = 1; // 初始复位有效
#100 reset = 0; // 100ns后释放复位
end
//示例:确定性输入数据的激励生成
reg [7:0] data;
initial begin//开始Initial逻辑块
data = 8'h00; // 初始值
#20 data = 8'hFF;//经典的8位二进制,二位16进制
#20 data = 8'h55;
#20 data = 8'hAA;
end
这里顺带补充一下在verilog测试平台开头往往要去规定测试时间步和测试精度的,规定的具体格式如下所示,一般来说,测试时间步长是1ns,测试精度是1ps.
`timescale 1ns / 1ps // 时间单位为1ns,精度为1ps
`timescale <时间单位> / <时间精度>
监控模块
在Verilog中,监控语句用于实时跟踪信号变化、输出调试信息或验证设计行为,是测试平台(Testbench)调试和验证的关键工具。在Verilog仿真中,initial
逻辑块是测试平台(Testbench)设计的核心组成部分,用于定义仿真开始时的初始化和激励生成逻辑,重要的仿真代码都是写在initial逻辑块中的,所有initial逻辑块在仿真开始的时候立刻并行执行。以下是Verilog中常用监控语句的详细说明及其应用场景:
$display(format_string, arg1, arg2, ...); // 自动换行,dsiplay和write是直接输出相关信息的,类似于printf函数
$write(format_string, arg1, arg2, ...); // 不换行
initial begin//initial逻辑块
$display("仿真开始,时间:%t", $time); // 显示当前仿真时间
#10;
$write("信号A=%b, ", a); // 不换行输出
$display("信号B=%h", b); // 换行输出
end
monitor语句持续监控指定信号变化,当任何被监控信号发生变化时,自动触发输出。而且重要的是全局只有一个monitor语句会生效,其余的都会被最后出现的覆盖掉。
initial begin
$monitor("时间=%t, a=%b, b=%h", $time, a, b); // 监控a和b的变化,当 a 或 b 的值变化时,自动打印当前时间和信号值。
end
相关的格式化字符与其对应的含义如下:
符号 | 说明 |
---|---|
%b | 二进制格式 |
%d | 十进制格式 |
%h | 十六进制格式 |
%t | 时间格式 |
%s | 字符串格式 |
strobe语句会在当前时间步结束时输出信号值,确保获取稳定的信号状态。触发时机:时间步结束前(所有非阻塞赋值完成后)。示例代码如下:
always @(posedge clk) begin
$strobe("时钟上升沿时刻:a=%d, b=%d", a, b); // 在时钟边沿后输出稳定值
end
自动检查部分
在Verilog测试平台中,自动检查(Automatic Checking) 是验证设计正确性的核心机制。其核心目标是无需手动查看波形,通过代码自动比对DUT输出与预期结果,并实时报告错误。以下是自动检查的设计原理、实现方法和最佳实践。下面的代码中是进行直接自动检查的示例:
always @(posedge clk) begin
if (!reset && enable) begin
// 检查计数器是否递增
if (count !== expected_count) begin//假如count与expected_count的值不相同则报错
$error("错误:预期值=%h,实际值=%h", expected_count, count);//报错
$finish; // 可选:终止仿真
end
expected_count <= expected_count + 1; // 更新预期值
end
end
实践与分析:
一个简单的CPU的构造如下所示
在verilog中实现一个CPU步骤较为复杂, 但是若简单实现CPU的若干功能则较为简答, 下面的verilog代码实现了一个最为简单的CPU的设计和验证:
module SimpleCPU (
input clk,
input reset,
output [7:0] data_out
);
reg [7:0] PC, ACC, IR;
reg [7:0] ROM [0:15];//Read only memory
reg [7:0] RAM [0:15];//random access memory
parameter FETCH = 0, EXECUTE = 1;//两个状态,存与取
reg state;
// 初始化ROM(指令)
initial begin
ROM[0] = 8'b0000_0001; // LOAD 1 +地址
ROM[1] = 8'b0001_0010; // ADD 2 +地址
ROM[2] = 8'b0010_0011; // STORE 3 +地址
ROM[3] = 8'b1111_1111; // HALT
end
// 初始化RAM(数据)
initial begin
RAM[1] = 8'h05; // 地址1存5
RAM[2] = 8'h03; // 地址2存3
end
// 主状态机
always @(posedge clk or posedge reset) begin
if (reset) begin
PC <= 0;
ACC <= 0;
IR <= 0;
state <= FETCH;
// 复位时初始化RAM(可选)
for (integer i=0; i<16; i++) RAM[i] <= 8'h00;
RAM[1] <= 8'h05;
RAM[2] <= 8'h03;
end else begin
case (state)
FETCH: begin
IR <= ROM[PC];
PC <= PC + 1;
state <= EXECUTE;
end
EXECUTE: begin
case (IR[7:4])
4'b0000: ACC <= RAM[IR[3:0]]; // LOAD
4'b0001: ACC <= ACC + RAM[IR[3:0]]; // ADD
4'b0010: RAM[IR[3:0]] <= ACC; // STORE
4'b1111: state <= EXECUTE; // HALT:冻结状态机,进入循环执行状态
default: ;
endcase
state <= FETCH;
end
endcase
end
end
assign data_out = ACC;//输出信号
endmodule
//SimpleCpu.v
module SimpleCPU_tb;
// 信号定义
reg clk;
reg reset;
wire [7:0] data_out;
// 实例化CPU
SimpleCPU uut (
.clk(clk),
.reset(reset),
.data_out(data_out)
);
// 生成时钟(周期=10ns)
initial begin
clk = 1;
forever #5 clk = ~clk;//这里的代码是永远每隔五ns翻转一次时钟信号
end
// 测试流程
initial begin
reset = 1; // 复位
#10 reset = 0;//10ns后reset键设为0
// 观察执行过程
#80; // 等待8个时钟周期(LOAD -> ADD -> STORE -> HALT),每二个时钟周期会执行一次命令
// 打印最终结果
$display("ACC = %h", data_out);//打印输出ACC累加器寄存器的值
$display("RAM[3] = %h", uut.RAM[3]);//打印输出RAM固定位置的值
$stop;
end
endmodule
//应该保存为SimpleCpu_tb.v
仿真测试结果如下:
CPU的基础理论其实是比较简单的,实现的无非是存,取,运算三大指令。
Synthesis综合初探
在Verilog中,综合(synthesis)是将Verilog代码转换成硬件电路的过程。这个过程是由电子设计自动化(EDA)工具完成的,通常称为综合工具。ASIC库(Application-Specific Integrated Circuit Library)是一组预先设计好的、可重用的硬件模块和元件的集合,它们用于在ASIC设计中实现特定的功能。这些库通常由半导体制造商或第三方EDA(电子设计自动化)工具提供商提供,并且与特定的工艺技术紧密相关。
以下是Verilog代码的综合过程的主要步骤:
- 设计输入
综合过程的第一步是提供设计输入,即编写好的Verilog代码。这个代码应该遵循一些编写规则,以确保可综合性,例如避免使用不可综合的语法(如initial块、$display等)。
- 解析和预处理
综合工具首先解析Verilog代码,检查语法错误,并将代码分解成更小的单元,如模块、实例、端口、信号等。预处理步骤还会处理宏定义和文件包含指令。
- 高层次综合(High-Level Synthesis, HLS)
在这个阶段,综合工具将Verilog代码中的行为描述(如always块)转换成更高层次的架构描述。这个过程可能包括算法优化、循环展开、资源共享等。
- 逻辑优化
综合工具会对设计进行逻辑优化,以减少资源使用和提高性能。这可能包括:
- 删除未使用的信号和模块。
- 合并逻辑门。
- 简化逻辑表达式。
- 优化时序路径。
- 映射到库
综合工具会将Verilog代码中的逻辑元素映射到FPGA或ASIC库中的具体单元,如查找表(LUTs)、触发器(FFs)、乘法器、RAM等。
- 约束和时序分析
在这个阶段,综合工具会考虑设计者提供的时序约束(如时钟周期、建立时间和保持时间等),并尝试满足这些约束。如果无法满足,工具会报告时序违规。
- 生成网表
综合过程的最终输出是一个网表(netlist),这是一个包含所有逻辑单元和它们之间连接关系的描述。网表是电路的图形表示,通常以特定格式(如EDIF、VHDL等)保存。
- 后续流程
综合完成后,如果是ASIC设计,网表将用于后续的布局和布线(layout and routing)流程;如果是FPGA设计,网表将输入到FPGA的布局和布线工具中,最终生成配置FPGA的比特流文件。
等。
- 约束和时序分析
在这个阶段,综合工具会考虑设计者提供的时序约束(如时钟周期、建立时间和保持时间等),并尝试满足这些约束。如果无法满足,工具会报告时序违规。
- 生成网表
综合过程的最终输出是一个网表(netlist),这是一个包含所有逻辑单元和它们之间连接关系的描述。网表是电路的图形表示,通常以特定格式(如EDIF、VHDL等)保存。
- 后续流程
综合完成后,如果是ASIC设计,网表将用于后续的布局和布线(layout and routing)流程;如果是FPGA设计,网表将输入到FPGA的布局和布线工具中,最终生成配置FPGA的比特流文件。
比较常用的synthesis工具就有vivado综合IDE,其是赛力斯公司旗下FPGA开发板 的配套设计工具,我在大二的时候经常使用其烧录比特流并验证。