FPGA之道(70)提高设计的综合性能(二)提高设计的自测性

前言

本文节选自《FPGA之道》。

提高设计的自测性

也许在FPGA设计的板级测试之前,我们已经做了充分的功能和时序仿真,但是仿真毕竟是仿真,它与实际情况之间还是或多或少的存在一定的差距,因此基于FPGA的整个硬件系统必须进行充分的实际上电测试才行。可是,搭建一套完整的硬件测试系统并不是一件容易的事情,即便是费了很大的力气,硬件测试系统已经搭建完毕,但是如果一运行,什么结果也没有或者行为紊乱没有规律,那么也不好定位和修改问题。因此,如果在HDL设计时,适当考虑让FPGA设计自身具有一定的自测功能,那么将会为后期的硬件级调试带来非常大的帮助。
当然了,提高设计的自测性应该遵循一个原则,那就是尽可能少的增加FPGA设计的资源负担,否则,资源的大规模膨胀会极大提升编译器的布局布线难度,降低设计的成功率,进而导致得不偿失。
下面,我们就来介绍一些常用的提高设计自测性能的方法:

增加测试管脚

最简单的增加FPGA设计自测性的方法就是增加测试管脚。即,将一些设计内部的关键信号直接通过芯片的空闲管脚输出,这样在硬件电路中就可以方便的使用万用表、示波器甚至逻辑分析仪来进行监测。不过这种方法会在原始信号上引入不确定的线延迟(FPGA内部及外部),因此在判断多个信号之间的相对位置时需要考虑这一因素。
增加测试管脚的具体做法很简单,就是在HDL中,将需要测试的信号从其产生的模块开始,一层层的输出出来,直至顶层模块,并在对应的管脚约束文件中为其添加相应的管脚分配。当然了,也需要在后续的电路板设计时对其进行硬件连接设计,在板子上放置可达的测试触点。

状态寄存器集

在进行功能仿真的时候,我们可以将模块内部任意一个寄存器或者线网的取值变化情况拿出来观察,并以此作为依据来判断问题的根源。而到了时序仿真的时候,你会发现几乎无法找不到待测模块内部的那些寄存器和线网,因为它们都被编译器优化过了,并且名字也都进行了修改,因此时序仿真时如果出现了问题,是很难定位的。最后,到了板级硬件实测的时候,你根本无法去窥探FPGA内部任何单元的取值情况,因为类似万用表、示波器这样的测试仪器最多只能达到FPGA芯片的管脚部分。
由此可见,随着理论一步步向实际靠近,我们所能利用查找问题的线索也变得越来越少,但是问题仍然可能出现,所以排查问题的工作也变得越来越艰巨。要想让问题排查变得容易,没有别的办法,只能是想办法获取更多关于系统内部的信息,而对于板级硬件实测来说,除了充分利用FPGA芯片的输出端口外也别无他法。因此,可以修改设计,将其中一些关键功能模块的一些关键信息(例如状态、标志位等)通过FPGA芯片的输出端口发送到FPGA芯片的外部,从而可以让我们得到更多的有用信息。
通常来说,FPGA内部关键的信息不止一个,因此,可以针对这些信息建立一个状态寄存器的集合,里面存储了所有我们关心的关键信息,并能够完成定时刷新,这样一来,只需要建立一个从FPGA外界对这部分寄存器的读取机制,就可以轻松获取很多FPGA芯片内部的信息。
不过有一点需要注意,切勿直接将HDL所描述的FPGA内部某一状态机的状态寄存器的内容存储到状态寄存器集中,这是因为状态机的状态定义大多是概念层级的定义,编译器很可能会对其进行重新编码(除非使能user选项),所以在HDL中是不建议直接使用状态机的状态进行非状态机相关的代码设计的。
下面给出一个关于状态寄存器集的HDL示例:

-- VHDL example
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity stateRegArray is
port (
	extClk : in  std_logic;
	addr : in  std_logic_vector(1 downto 0);
	stateSel : out std_logic_vector(7 downto 0);

	mod0En : in  std_logic;
	mod0StateData : in std_logic_vector(7 downto 0);
	mod1En : in  std_logic;
	mod1StateData : in std_logic_vector(7 downto 0);
	mod2En : in  std_logic;
	mod2StateData : in std_logic_vector(7 downto 0);
	mod3En : in  std_logic;
	mod3StateData : in std_logic_vector(7 downto 0)
);
end stateRegArray;

architecture Behavioral of stateRegArray is
	type arrayType is array(3 downto 0) of std_logic_vector(7 downto 0);
	signal stateArray : arrayType;
begin
	process(extClk)
	begin
		if(extClk'event and extClk = '1')then
			stateSel <= stateArray(conv_integer(addr));
	
			if(mod0En = '1')then
				stateArray(0) <= mod0StateData;
			end if;
			if(mod1En = '1')then
				stateArray(1) <= mod1StateData;
			end if;
			if(mod2En = '1')then
				stateArray(2) <= mod2StateData;
			end if;
			if(mod3En = '1')then
				stateArray(3) <= mod3StateData;
			end if;
		end if;
	end process;
end Behavioral;

entity mod0 is
port (
	inerClk : in  std_logic;
	mod0En : out  std_logic;
	mod0StateData : out std_logic_vector(7 downto 0)
);
end mod0;

architecture Behavioral of mod0 is		
	signal counter : std_logic_vector(3 downto 0);
	signal importantSignal : std_logic_vector(7 downto 0);
begin
	mod0En <= '1' when counter > 5 and counter < 10 else '0';
	process(inerClk)
	begin
		if(inerClk'event and inerClk = '1')then
			counter <= counter + 1;
			if(counter = 0)then
				mod0StateData <= importantSignal;
			end if;
		end if;
	end process;

	-- importantSignal are generate below
	-- ......

end Behavioral;

-- mod1, mod2, mod3 would define following
-- ......
// Verilog example
	module stateRegArray(
	input extClk,
	input [1:0] addr,
	output reg [7:0] stateSel,

	input mod0En,
	input [7:0] mod0StateData,
	input mod1En,
	input [7:0] mod1StateData,	
	input mod2En,
	input [7:0] mod2StateData,
	input mod3En,
	input [7:0] mod3StateData
	);
	
	reg [7:0] stateArray[3:0];
	
	always@(posedge extClk)
	begin
		stateSel <= stateArray[addr];
		
		if(mod0En == 1'b1)
		begin
			stateArray[0] <= mod0StateData;
		end
		if(mod1En == 1'b1)
		begin
			stateArray[1] <= mod1StateData;
		end
		if(mod2En == 1'b1)
		begin
			stateArray[2] <= mod2StateData;
		end
		if(mod3En == 1'b1)
		begin
			stateArray[3] <= mod3StateData;
		end
	end
	endmodule
	
	module mod0(
	input inerClk,
	output mod0En,
	output reg [7:0] mod0StateData
	);
	reg [3:0] counter;
	reg [7:0] importantSignal;
	
	assign mod0En = ((counter > 4'd5) && (counter < 4'd10))? 1'b1 : 1'b0;
	
	always@(posedge inerClk)
	begin
		counter <= counter + 1'b1;
		if(counter == 4'b0)
		begin
			mod0StateData <= importantSignal;
		end
	end	
	
	// importantSignal are generate below
	// ......
	
	endmodule

	// mod1, mod2, mod3 would define following
	// ......

从上述示例可以看出,为了解决跨时钟域问题,mod0模块中的数据mod0StateData稳定16个内部时钟周期,但mod0En确仅在mod0StateData稳定的中间4个内部时钟周期有效。这样一来,只要保证4个内部时钟周期大于1个外部时钟周期,那么mod0的内部状态数据就必然可以正确被stateRegArray采样。这便是【本篇->编程思路->时钟及时钟域->跨时钟域问题->握手法】小节中介绍的思路。

虚拟示波器

如果说状态寄存器集的方法类似于用万用表去测量电路电压的方式,即它仅适合观察一些变化缓慢或不需要连续记录的信息,那么对于一些变化非常快或需要连续记录的信息,自然连线到采用类似示波器的形式。没错,本章节就来介绍利用虚拟示波器技术提高设计自测性的方法。

ChipScope&SignalTap

在FPGA的集成开发环境中,一般都集成了类似的测试工具,例如Xilinx公司的ChipScope系列调试工具和Altera公司的SignalTap系列的调试工具。这些工具能够较为方便的帮助我们完成硬件级的在线调试,其使用方法一般也比较简单,通过调用相应功能的IP核并完成信号观测配置,重新编译工程,完成好硬件连接,最后即可使用该在线调试工具完成信号的触发、采集和显示等等。
当然了,要想使用这些在线调试工具,也是需要付出一定代价的。首先,这需要在我们的FPGA设计中添加相应的逻辑功能,因此需要耗费一定的资源。其次,这些工具一般都是通过JTAG接口来完成数据的回传,因此采集能力会受一定的限制。第三,如果希望在成品产品中使用这一测试工具,必须设计专门的硬件接口。最后,这些工具不一定都是免费的,而且你的测试环境离不开安装完成这样工具的一台电脑。

自己编写VirtualScope

仿照ChipScope以及SignalTap的在线调试思路,我们也可以自定义设计出更适合自己的虚拟示波器——VirtualScope。相比于ChipScope、SignalTap这类现成的调试工具,自己设计的VirtualScope具有以下优点:
首先,VirtualScope可以利用系统现成的高速硬件数据接口来进行在线调试,无需引入额外硬件接口或者连线。
其次,VirtualScope可以利用系统现成的显示设备来显示采集到的FPGA内部数据,无需引入额外的显示设备。
第三,VirtualScope作为FPGA项目的一部分集成于整个设计之中,因此可以比较方便的在调试模式和正常工作模式之间进行切换。
最后,“自己动手、丰衣足食”,在FPGA设计中加入自己编写的VirtualScope模块是完全开源和免费的,并且实现起来也不复杂。
下面,就给出一个VirtualScope模块的HDL示例:

-- VHDL example
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity VirtualScope is
port (
	extClk : in  std_logic;
	extRst : in  std_logic;
	sampleFlag : in  std_logic;
	paraChangeEn : in  std_logic;
	source : in std_logic_vector(1 downto 0);
	sampleNum : in std_logic_vector(9 downto 0);
	waveEn : out  std_logic;
	waveform : out  std_logic_vector(7 downto 0);
	
	innerClk : in  std_logic;
	innerRst : in  std_logic;
	mod0En : in  std_logic;
	mod0Data : in std_logic_vector(7 downto 0);
	mod1En : in  std_logic;
	mod1Data : in std_logic_vector(7 downto 0);
	mod2En : in  std_logic;
	mod2Data : in std_logic_vector(7 downto 0);
	mod3En : in  std_logic;
	mod3Data : in std_logic_vector(7 downto 0)
);
end VirtualScope;

architecture Behavioral of VirtualScope is

component AsynFifo
port (
rst: in std_logic;
wr_clk: in std_logic;
rd_clk: in std_logic;
din: in std_logic_vector(7 downto 0);
wr_en: in std_logic;
rd_en: in std_logic;
dout: out std_logic_vector(7 downto 0);
full: out std_logic;
empty: out std_logic);
end component;

signal rd_en, wr_en : std_logic;
signal empty : std_logic;
signal din : std_logic_vector(7 downto 0);
signal sourceLock : std_logic_vector(1 downto 0);
signal sampleNumLock : std_logic_vector(9 downto 0);
signal sampleFlagLock : std_logic;
signal startSample : std_logic;
signal modXEn : std_logic;
type stateType is (s_idle, s_sample);
signal state, nextState : stateType;
signal counter, nextCounter : std_logic_vector(9 downto 0);

begin

-- fifo instatiation, fifo depth must be bigger than sampleNum
m0 : AsynFifo
	port map (
		rst => extRst,
		wr_clk => innerClk,
		rd_clk => extClk,
		din => din,
		wr_en => wr_en,
		rd_en => rd_en,
		dout => waveform,
		empty => empty);
-- read data out of fifo and send to ext
-- may need doing something to make sure the ext sample the data right
-- ......
rd_en <= not empty;
process(extClk)
begin
	if(extClk'event and extClk = '1')then
		waveEn <= rd_en;
	end if;
end process;

-- parameter passed from ext to inner
-- handshake must be introduced for safety
process(innerClk)
begin
	if(innerClk'event and innerClk = '1')then
		if(paraChangeEn = '1')then
			sourceLock <= source;
			sampleNumLock <= sampleNum;
		end if;
		
		sampleFlagLock <= sampleFlag;
	end if;
end process;

-- source select for sample
process(sourceLock, mod0En, mod1En, mod2En, mod3En, mod0Data, mod1Data, mod2Data, mod3Data)
begin
	case (sourceLock) is 
	when "00" =>
		modXEn <= mod0En;
		din <= mod0Data;
	when "01" =>
		modXEn <= mod1En;
		din <= mod1Data;
	when "10" =>
		modXEn <= mod2En;
		din <= mod2Data;
	when "11" =>
		modXEn <= mod3En;
		din <= mod3Data;
	when others =>
		modXEn <= mod0En;
		din <= mod0Data;
	end case;
end process;

-- sample data and store into fifo
startSample <= sampleFlagLock xor sampleFlag;

process(innerClk)
begin
	if(innerClk'event and innerClk = '1')then
		if(innerRst = '1')then
			state <= s_idle;
			counter <= (others => '0');
		else
			state <= nextState;
			counter <= nextCounter;
		end if;
	end if;
end process;

process(state, startSample, counter, modXEn, sampleNum)
begin
	wr_en <= '0';
	
	case (state) is 
	-- wait sample flag
  		when s_idle =>         
		nextCounter <= (others => '0');
		if(startSample = '1')then
			nextState <= s_sample;
		else
			nextState <= s_idle;
		end if;
		
	-- sampling		
  		when s_sample =>
		if(modXEn = '1')then
			nextCounter <= counter + 1;
		else
			nextCounter <= counter;
		end if;
		
		wr_en <= modXEn;
		
		if(nextCounter >= sampleNum)then
			nextState <= s_idle;
		else
			nextState <= s_sample;
		end if;
		
	when others =>
		nextState <= s_idle;
	end case;
end process;

end Behavioral;
// Verilog example
module VirtualScope(
input extClk,
input extRst,
input sampleFlag,
input paraChangeEn,
input [1:0] source,
input [9:0] sampleNum,
output reg waveEn,
output [7:0] waveform,

input innerClk,
input innerRst,
input mod0En,
input [7:0] mod0Data,
input mod1En,
input [7:0] mod1Data,
input mod2En,
input [7:0] mod2Data,
input mod3En,
input [7:0] mod3Data
);

wire rd_en;
reg [7:0] din;
reg wr_en;
wire empty;
reg [1:0] sourceLock;
reg [9:0] sampleNumLock;
reg sampleFlagLock;
wire startSample;
reg modXEn;
parameter s_idle = 0, s_sample = 1;
reg state, nextState;
reg [9:0] counter, nextCounter;

// fifo instatiation, fifo depth must be bigger than sampleNum  
AsynFifo m0(
	.rst(extRst),
	.wr_clk(innerClk),
	.rd_clk(extClk),
	.din(din), // Bus [7 : 0] 
	.wr_en(wr_en),
	.rd_en(rd_en),
	.dout(waveform), // Bus [7 : 0] 
	.empty(empty));

// read data out of fifo and send to ext
// may need doing something to make sure the ext sample the data right
// ......
assign rd_en = ~ empty;
always@(posedge extClk)
begin
	waveEn <= rd_en;
end

// parameter passed from ext to inner
// handshake must be introduced for safety
always@(posedge innerClk)
begin 
	if(paraChangeEn == 1'b1)
	begin
		sourceLock <= source;
		sampleNumLock <= sampleNum;
	end
	
	sampleFlagLock <= sampleFlag;
end	

// source select for sample
always@(sourceLock, mod0En, mod1En, mod2En, mod3En, mod0Data, mod1Data, mod2Data, mod3Data)
begin
	case (sourceLock)
	2'b00: 
	begin
		modXEn = mod0En;
		din = mod0Data;
	end
	2'b01: 
	begin
		modXEn = mod0En;
		din = mod1Data;
	end
	2'b10: 
	begin
		modXEn = mod0En;
		din = mod2Data;
	end
	2'b11: 
	begin
		modXEn = mod0En;
		din = mod3Data;
	end
	default:
	begin
		modXEn = mod0En;
		din = mod0Data;
	end
	endcase
end

// sample data and store into fifo
assign startSample = sampleFlagLock ^ sampleFlag;

always@(posedge innerClk)
begin
	if(innerRst == 1'b1)
	begin
		state <= s_idle;
		counter <= 10'b0;
	end
	else
	begin
		state <= nextState;
		counter <= nextCounter;
	end
end

always@(state, startSample, counter, modXEn, sampleNum)
begin
	wr_en = 1'b0;
	
	case (state)
	// wait sample flag
	s_idle: 
	begin
		nextCounter = 10'b0;
		if(startSample == 1'b1)
		begin
			nextState = s_sample;
		end
		else
		begin
			nextState = s_idle;
		end
	end
	
	// sampling
	s_sample: 
	begin			
		if(modXEn == 1'b1)
		begin
			nextCounter = counter + 1'b1;
		end
		else
		begin
			nextCounter = counter;
		end
		
		wr_en = modXEn;
		
		if(nextCounter >= sampleNum)
		begin
			nextState = s_idle;
		end
		else
		begin
			nextState = s_sample;
		end
	end
			
	default: 
	begin
		nextState = s_idle;
	end
	endcase
end

endmodule

从上述示例可以看出,为了解决跨时钟域问题,本例主要采用了【本篇->编程思路->时钟及时钟域->跨时钟域问题->异步FIFO法】小节中介绍的方法。当然了,对于采样源选择信号和采样点数信号的稳定可靠更新,仍需采用【本篇->编程思路->时钟及时钟域->跨时钟域问题->握手法】小节中介绍的思路来解决。

扫描二维码关注公众号,回复: 10195805 查看本文章

编写激励发生测试模块

上述介绍的方法是适用于任何情况的一种通用的自测方法,它们的目的均是窥探FPGA芯片的内部情况,但对于一些专有的应用,仅仅能够观察到FPGA芯片内部的情况还远远不够,为了提高设计的自测性,还需要FPGA设计自身能够产生一些特定的激励。
举个例子,如果要做一个视频采集之类的项目,在硬件级的测试时,肯定需要有实实在在的视频信息输入,才能够对应得到实实在在的视频显示信号。可是为了产生视频显示信号,往往需要额外的设备和硬件电路接入,比较麻烦。此时,若能够提前在FPGA设计中嵌入一个简单的视频信号产生模块,那么就可以在没有任何外界激励源的情况下,迅速的利用视频信号产生模块发出的测试信号来初步的测试一下整个设计工作的情况,例如,彩条测试模块、雪花点测试模块都是视频应用项目中常见的提高设计自测性的内嵌激励模块,这样一来,只需要将视频信号采集源选择为这些模块的输出,便可以在显示设备中看到对应的图像,并可通过图像的质量来判断整个设计是否通过了初步的验证。
编写激励发生测试模块的方法比较适用于以处理数据包为核心业务的FPGA设计,当然了,该方法除了能提高设计的自测性以外,还能够起到应对输入异常的作用,例如我们在看电视时,当视频信号特别差,我们就可以直接切换到彩条、蓝屏或者雪花点测试模块,而不用把非常难看、晃眼的图像效果呈现给客户。

发布了806 篇原创文章 · 获赞 1541 · 访问量 151万+

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/105021230