FPGA之道(44)HDL中的隐患写法

前言

本文节选自《FPGA之道》,让我们和作者一起来看下编写FPGA时需要遵循的一些规则,避免FPGA编写的隐患。

有隐患的混写逻辑

目前来说,几乎99%以上的FPGA设计都属于时序逻辑,所以FPGA设计几乎都由组合逻辑和纯时序逻辑两大部分共同组成。当我们面对一个比较庞杂的项目时,或者急于解决一个棘手的问题时,我们可能并不能将时序逻辑的代码写得非常清晰,因此组合逻辑与纯时序逻辑混合在一起的情况在我们的代码中总是时有发生。有时候,这些混写是无伤大雅的,例如上一小节中的例子;但另一些时候,这些混写会造成一些隐患甚至错误,必须杜绝。
请注意!!!本小节所讨论的各种代码情况都是不建议采用的,目的是起到警示作用,而不是指导作用,切忌切忌!!!定力差者请自觉跳过本章节!!!

VHDL中应该禁止的写法

在时序process中使用variable

process中的语句都是串行执行的,但是由于signal的赋值是具有延迟的,因此在时序process中,signal的赋值语句其实无所谓先后,事实上时序逻辑的赋值本身就是并发执行的,也不该有先后。就好比我们在【纯时序逻辑描述方法】小节中描述的移位寄存器示例,无论怎么打乱赋值顺序,寄存器之间的传递关系都不会改变。可是variable的赋值是立即实现的,因此具有variable赋值语句的时序process,其语句的先后顺序就很重要。例如,如下几段代码的输出结果肯定是不同的:

process(clk) -- example 1
	variable tmp : std_logic;
begin
	if (clk'event and clk = '1') then
		tmp := a and b;
		tmp := not tmp;
		m <= tmp; -- the same as: m <= not (a and b);
		n <= tmp; -- the same as: n <= not (a and b);
	end if;
end process;

process(clk) -- example 2
	variable tmp : std_logic;
begin
	if (clk'event and clk = '1') then
		tmp := a and b;
		m <= tmp; -- the same as: m <= a and b;
		tmp := not tmp;
		n <= tmp; -- the same as: n <= not (a and b);
	end if;
end process;

process(clk) -- example 3
	variable tmp : std_logic;
begin
	if (clk'event and clk = '1') then
		tmp := a and b;
		n <= tmp; -- the same as: n <= a and b;
		tmp := not tmp;
		m <= tmp; -- the same as: m <= not (a and b);
	end if;
end process;

process(clk) -- example 4
	variable tmp : std_logic;
begin
	if (clk'event and clk = '1') then
		tmp := a and b;
		m <= tmp; -- the same as: m <= a and b;
		n <= tmp; -- the same as: n <= a and b;
		tmp := not tmp;
	end if;
end process;

通过example1~4可以看出,原本书写顺序无关的时序process,因为两条variable赋值语句的插入开始变得敏感代码书写顺序了。试想,如果一个具有100行代码的时序process中有variable赋值的存在,那么将会对后期维护和修改带来多么大的不便!而variable的可怕之处还不仅如此,上例中,tmp相关运算都被综合为组合逻辑,tmp被对应到FPGA中实际的硬件连线,可是如果再对上例的语句顺序进行一些调整,tmp甚至还会被综合成为触发器,例如:

process(clk) -- example 5
	variable tmp : std_logic;
begin
	if (clk'event and clk = '1') then
		m <= tmp; -- the same as: m <= pre clock cycle (not (a and b));
		n <= tmp; -- the same as: n <= pre clock cycle (not (a and b));
		tmp := a and b; -- synthesis as wire
		tmp := not tmp; -- synthesis as flip-flop
	end if;
end process;

当然了,利用variable也可以写成纯时序逻辑,例如【纯时序逻辑描述方法】小节中描述的移位寄存器示例也可以利用variable描述如下,只不过语句顺序不能随便颠倒:

process(clk) -- example 6
	variable r0, r1 : std_logic;
begin
	if (clk'event and clk = '1') then
		dOut <= r1;
		r1 := r0; -- synthesis as flip-flop
		r0 := dIn; -- synthesis as flip-flop
	end if;
end process;

经过以上的介绍,我们基本上可以得出一个结论,那就是variable的存在不但没有拓展时序process的功能,反而由于其过分的灵活性极大的增加了代码的编写难度,为时序process带来了无穷的隐患。

在组合process中使用variable

在组合process中使用variable,由于process中不会调用’event等方法,因此也不会综合出flip-flop,例如下面的代码:

process(a,b)
variable tmp : std_logic;
begin
	m <= tmp; -- the same as: m <= a and b;
	tmp := a and b; -- synthesis as wire
end process;	

尽管tmp的赋值位于process的末尾,从语法上分析m的值并不由当前a和b的值决定,而是由最近一次变化前的a、b决定,但是综合出来的结果仍是纯组合逻辑。因此在组合process中即使想刻意的用variable来写一些时序逻辑,也是不能成功的。但是其隐患就在于,仿真器是完全按照语法去理解HDL代码的,这样一来,使用了variable的组合process很可能会出现前仿真与后仿真结果不一致的问题,这将给我们得调试工作带来巨大的麻烦。

综上所述,尽管VHDL语言一向以严谨著称,但是variable的存在的确是其语法中的一大败笔,因此在这里郑重向大家建议——珍爱生命,远离variable!

鲁莽的process糅合

鲁莽的process糅合,指的就是生生的将两个毫不相关的组合process和时序process揉在一起。例如,以下两个process都是非常纯净的组合和时序

process:
	process(b)
	begin
		n <= not b;
	end
	
	process(clk)
	begin
		if (clk'event and clk = '1') then
			m <= a;
		end if;
	end process;
	可是如果你非要写成这样:
	process(clk, b)
	begin
		n <= not b;
		
		if (clk'event and clk = '1') then
			m <= a;
		end if;
	end process;

也许编译器能够正确的综合,但是代码的可读性却差了很多,猛的看上去,还以为描述的是一个具有异步复位的时序逻辑电路呢。

Verilog中应该禁止的写法

在时序always中使用阻塞赋值

在时序always中,推荐全部使用非阻塞赋值方式,不过你要是非要强行在其中使用阻塞赋值方式,编译器也不会报错,但是其相当于引入了不太恰当的组合逻辑,会导致一些隐患。
针对时序always中的单条代码来说,无论是阻塞赋值还是非阻塞赋值,等号左端的变量都会被综合为触发器。例如如下代码中m和n的结果是完全一样的:

	always@(posedge clk) // example 1
begin
		n = a & b;
		m <= a & b;
end

不过按照语法来说,always中的语句都是串行执行的,由于非阻塞赋值不是立即生效的,因此在书写标准的时序always中,非阻塞赋值语句其实无所谓先后,事实上时序逻辑的赋值本身就是并发执行的,也不该有先后。就好比我们在【纯时序逻辑描述方法】小节中描述的移位寄存器示例,无论怎么打乱赋值顺序,寄存器之间的传递关系都不会改变。可是阻塞赋值符号是立即实现的,因此具有阻塞赋值语句的时序always,其语句的先后顺序就显得很重要。例如,如下两段代码综合后的结果肯定是不同的:

	always@(posedge clk) // example 2 
begin
	n = a & b; // the same as : n <= a & b;
		m <= n; // the same as : m <= a & b;
end

always@(posedge clk) // example 3 
begin
		m <= n; // the same as : m <= pre clock cycle n;
		n = a & b; // the same as : n <= a & b;
end

在example 2中,触发器n的输出应该在此次时钟上升沿后就等于a&b,由于n使用了阻塞赋值,因此从语法上来理解,触发器m的输出应该在此次时钟上升沿后就等于n的新值,所以也就等于a&b,此时m、n其实是一样的,可以合并为1个触发器;不过对于example 3来说,由于对触发器m赋值时n的新值还没有得到,因此从语法上来分析,m就等于n的旧值,此时m、n之间就是一个移位寄存器的关系。
由此可见,在时序always中使用阻塞赋值,除了影响代码的可读性外,最大的隐患就是很容易会造成最终实现的电路结构上产生本质的变化。

在组合always中使用非阻塞赋值

在组合always中,推荐全部使用阻塞赋值方式,注意好语句顺序,代码的综合和仿真一般都不会有什么问题。不过你要是非要强行在其中使用非阻塞赋值方式,编译器也不会报错。由于组合always敏感量表中不可能出现边沿敏感事件,所以非阻塞赋值也不会综合出寄存器来,故编译结果仍是纯组合逻辑。因此在组合always中即使想刻意的用非阻塞赋值方式来写一些时序逻辑,也是不能成功的。但是其隐患就在于,仿真器是完全按照语法去理解HDL代码的,这样一来,使用了非阻塞赋值方式的组合always很可能会出现前仿真与后仿真结果不一致的问题,这将给我们得调试工作带来巨大的麻烦。例如:

reg t;
always@(a)
begin
		t <= a;
		o = t;  // the same as : o = pre stable level of a;
end

可见,上述非阻塞赋值方式虽不会对最终的设计功能造成影响,但确是却会带来前仿真错误和一些阅读上的疑惑,因此建议不要这样使用。

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

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/104332959
HDL