1.概述
仿真测试平台文件(Testbench)是可以用来验证所设计的硬件模型正确性的 VHDL模型,它为所测试的元件提供了激励信号,可以以波形的方式显示仿真结果或把测试结果存储到文件中。这里所说的激励信号可以直接集成在测试平台文件中,也可以从外部文件中加载。
一般而言,编写 Testbench 进行测试主要有下面四个步骤
- (1)实例化需要测试的设计(DUT,Design Under Test);
- (2)产生模拟激励(波形);
- (3)将产生的激励加入到被测试模块并观察其输出响应;
- (4)将输出响应与期望进行比较,从而判断设计的正确性。
其中,输出响应可以以波形方式显示或存储测试结果到文件中。
2.Testbench程序基本结构
通常 Testbench 的基本结构包括库的调用、程序包的调用、空实体、结构体描述。在结构体描述中,一般包含有被测试元件的声明、局部信号声明、被测试元件例化、激励信号的产生,如图所示。与一般的 VHDL 程序不同的是,Testbench 里面的实体为空。
2.1 被测试元件的声明方式
先说一个被测实体vote7
,代码如下:
library ieee;
use ieee.std_logic_1164.all;
entity vote7 is
port(
vt : in std_logic_vector(6 downto 0);
result : out std_logic
);
end entity vote7;
architecture rtl of vote7 is
begin
process(vt)
variable sum : integer range 0 to 7;
begin
sum := 0;
for i in 0 to 6 loop
if vt(i) = '1' then
sum := sum + 1;
if sum > 4 then
result <= '1';
else
result <= '0';
end if;
end if;
end loop;
end process;
end architecture;
2.1.1 组件实例化
组件实例化是一种传统且常用的方法,特别适用于较早版本的VHDL(如VHDL-93)。该方法需要在测试平台中先声明一个组件,然后在架构中进行实例化。
步骤
-
组件声明:在测试平台的架构声明部分(通常在
architecture
关键词之后)声明DUT的组件。 -
实例化:在架构的主体部分使用
component
实例化DUT,并进行端口映射。
示例代码
假设有一个被测实体vote7,其声明如下:
library ieee;
use ieee.std_logic_1164.all;
entity tb_vote7 is
end entity tb_vote7;
architecture Behavioral of tb_vote7 is
-- 信号声明
……
-- 组件声明
component vote7
port(
vt : in std_logic_vector(6 downto 0);
result : out std_logic
);
end component;
begin
-- DUT实例化
DUT: vote7
port map (
vt => vt,
result => result
);
-- 激励过程
……
end architecture Behavioral;
2.1.2 直接实体实例化
直接实体实例化(也称为架构实例化)是VHDL-2002及更高版本中引入的一种更简洁的实例化方式。它不需要提前声明组件,直接引用实体和架构即可。
优点
- 简洁:无需组件声明,减少了代码冗余。
- 灵活:可以直接指定要使用的实体和架构。
使用与前述相同的vote7实体,测试平台采用直接实体实例化的方法如下:
library ieee;
use ieee.std_logic_1164.all;
entity tb_vote7 is
end entity tb_vote7;
architecture Behavioral of tb_vote7 is
-- 信号声明
……
begin
-- DUT直接实例化
DUT: entity work.vote7(rtl)
port map (
vt => vt,
result => result
);
-- 激励过程
……
end architecture Behavioral;
代码解析
- DUT实例化:使用
entity work.vote7(rtl)
指定实体vote7
和其架构rtl
,然后进行端口映射。 - 无需组件声明:省略了组件的预先声明,代码更加简洁。
3.激励信号的产生
激励信号产生的方式一般有两种,一种是以一定的离散时间间隔产生激励信号,另一种是基于实体的状态产生激励信号。需要注意的是,在 Testbench 程序中一定要对所有的激励信号赋初始值。下面通过实例,讲述激励信号的产生方法。
3.1 时钟信号的产生
时钟信号属于周期性出现的信号,是同步设计中最重要的信号之一。如图所示,时钟信号分为两类,即占空比为50%的对称时钟信号与占空比不是 50%的非对称时钟信号。
Testbench 中产生时钟信号方式有两种,
- 一种是使用并行的信号赋值语句;
- 一种是使用process进程。
下面分别通过两个例子来说明如何用这两种方法来产生所需的时钟信号。
【例】用并行信号赋值语句产生如图所示的 clk1
、clk2
、clk3
信号。
观察上图,我们发现 clk1
为对称时钟信号,其初始值可以在信号定义时赋值;clk2
和 clk3
为非对称时钟信号,其起始值可以在语句中赋值。这两种信号的产生方式有所不同,相对而言对称时钟信号的产生相对简单一些。
并行信号赋值语句的实现如下:
signal clk1:std_logic := '0';
signal clk2:std_logic;
signal clk3:std_logic;
……
clk1 <= not clk1 after clk_period/2;
clk2 <= '0' after clk_period/4 when clk2 = '1' else
'1' after 3*clk_period/4 when clk2 = '0' else
'1';
clk3 <= '0' after clk_period/4 when clk3 = '1' else
'1' after 3*clk_period/4 when clk3 = '0' else
'0';
……
【例】使用 process 进程产生如图所示的clk1
、clk2
信号。
观察上图,可以发现 clk1
为对称时钟信号,clk2
为非对称时钟信号,但这两种信号用 process
进程实现的方法基本一致。
process
进程实现如下:
signal clk1:std_logic;
signal clk2:std_logic;
……
clk1_gen:process
constant clk_period :time := 40ns;--常量只在该进程中起作用
begin
clk1 <= '1';
wait for clk_period/2;
clk1