前言
本文节选自《FPGA之道》。
原语的使用
什么是原语
原语,英文名称primitive,是FPGA软件集成开发环境所提供的一系列底层逻辑功能单元。由于是底层逻辑功能单元,所以它们往往跟目标FPGA芯片以及芯片厂商紧密相关,因此不同厂商、不同器件的原语往往不能通用。当编译器对我们的HDL代码进行编译时,其中间环节的一些输出往往就是由原语组成的逻辑网表。因此,原语往往是不参与综合过程的,而使用原语描述的逻辑往往也不会被综合工具所优化。例如,Xilinx公司的ISE软件集成开发环境中的unisims库中定义了所有用于综合的原语,而simprims库中则定义了所有用于实现的原语。需要注意的是,如果我们去ISE安装目录下的verilog\src\unisims或verilog\src\simprims文件夹下去看这些原语的代码,可以发现其实这些并不是真正的原语,而是在原语的基础上又封装了一层,不过人们常常将它们也泛称为原语。
例如,如下为unisims库中一个二输入与门的定义:
`timescale 1 ps / 1 ps
module AND2 (O, I0, I1);
output O;
input I0, I1;
and A1 (O, I0, I1);
endmodule
至于其中的and是不是真正的原语,则需要找进一步的定义。
而simprims库由于涉及到了实现,因此在建模的时候就需要加入时序信息,例如,如下为simprims库中一个二输入与门的定义:
`timescale 1 ps/1 ps
module X_AND2 (O, I0, I1);
parameter LOC = "UNPLACED";
output O;
input I0, I1;
and (O, I0, I1);
specify
(I0 => O) = (0:0:0, 0:0:0);
(I1 => O) = (0:0:0, 0:0:0);
specparam PATHPULSE$ = 0;
endspecify
endmodule
可见,这里面用到了我们在【本篇->编程语法->Verilog基本语法->Verilog的并行语句->Verilog模块说明语句】小节中介绍的specify语法,来在原语的基础上,对该功能模块进行时序建模,这应该也是为什么库里面不提供直接的原语调用的原因之一。不过上述定义中的时间延迟信息均为0,这是由于不同系列器件或不同等级器件中的延迟不相等,所以这些参数会在编译的时候根据具体目标器件进行替换。
再多观察一些库中的基本逻辑单元,可以发现其中一些是带有真正的原语定义的,例如simprims库下的一个两输入查找表的定义:
`timescale 1 ps/1 ps
module X_LUT2 (O, ADR0, ADR1);
parameter INIT = 4'h0;
parameter LOC = "UNPLACED";
output O;
input ADR0, ADR1;
wire out, a0, a1;
buf b0 (a0, ADR0);
buf b1 (a1, ADR1);
x_lut2_mux4 (O, INIT[3], INIT[2], INIT[1], INIT[0], a1, a0);
specify
(ADR0 => O) = (0:0:0, 0:0:0);
(ADR1 => O) = (0:0:0, 0:0:0);
specparam PATHPULSE$ = 0;
endspecify
endmodule
primitive x_lut2_mux4 (o, d3, d2, d1, d0, s1, s0);
output o;
input d3, d2, d1, d0;
input s1, s0;
table
// d3 d2 d1 d0 s1 s0 : o;
? ? ? 1 0 0 : 1;
? ? ? 0 0 0 : 0;
? ? 1 ? 0 1 : 1;
? ? 0 ? 0 1 : 0;
? 1 ? ? 1 0 : 1;
? 0 ? ? 1 0 : 0;
1 ? ? ? 1 1 : 1;
0 ? ? ? 1 1 : 0;
? ? 0 0 0 x : 0;
? ? 1 1 0 x : 1;
0 0 ? ? 1 x : 0;
1 1 ? ? 1 x : 1;
? 0 ? 0 x 0 : 0;
? 1 ? 1 x 0 : 1;
0 ? 0 ? x 1 : 0;
1 ? 1 ? x 1 : 1;
0 0 0 0 x x : 0;
1 1 1 1 x x : 1;
endtable
endprimitive
上述定义中,primitive关键字定义的就是真正的原语,我们将在后续的【UDP简介】小节中具体介绍primitive的语法。
需要使用原语的情况
一般来说,在进行HDL代码编写时,不需要直接或间接的进行原语调用,因为随着FPGA设计规模的越来越庞杂,人脑应该集中于抽象层次较高的工作中去,而将这些具体实现细节交给编译器来完成。不过有些时候,原语或者库中底层模块的调用还是十分必要的。例如,在Xilinx的ISE工具中,可以通过edit->Language Templates菜单调出语法模板,在其中的VHDL或Verilog子菜单下,都可以看到一个Device Primitive Instantiation子项,里面列举出了所有我们在Xilinx FPGA平台上开发项目时可能会用到的原语例化方式。举例简介如下:
时钟相关原语
如果时钟信号不是由FPGA芯片的专用时钟pin(或pad)引入FPGA的,那么它通常就需要在FPGA内部被显式的连接到时钟树资源上,否则,直接使用这种不经过时钟树的时钟信号,会给FPGA设计的时序带来非常麻烦的问题,进而导致逻辑行为失败。
可是HDL代码仅仅描述功能,无法向编译器表达“希望将某一时钟信号连接到时钟树资源”这样的一层意思,那么此时,就需要使用类似BUFG这样的库里提供的底层模块来进行指示。例如:
-- VHDL example
singal innerclk, gclk : std_logic;
onToGlobalClockTree: BUFG
port map (
I => innerclk, -- in std_logic
O => gclk); -- out std_logic
process(gclk)
begin
……
end process;
// Verilog example
wire innerclk, gclk;
BUFG onToGlobalClockTree(.I (innerclk),
.O (gclk));
always@(posedge gclk)
begin
……
end
那么,像上例这样,通过显式调用BUFG这样一个库中的底层模块,就可以告诉编译器,我们希望将innerclk信号引入全局时钟树,而其经过全局时钟树后的名字就改为gclk。后续HDL代码便可以放心的基于gclk编写逻辑。需要说明的是,直接从全局时钟pin(或pad)引入的时钟信号,对于xilinx的FPGA芯片来说,实际上是通过IBUFG+BUFG这样的组合直接连接到全局时钟树上的,只不过此时我们不需要显式例化这两个原语。
类似的时钟相关原语还有BUFR、BUFIO等,它们分别对应区域时钟树和IO时钟树资源,这里就不再赘述。
差分输入、输出原语
FPGA的接口具有单端和差分两种形式,同样,HDL代码只能描述功能,无法表达“某两个pin(或pad)脚互为一个差分对”这样的一层意思。那么此时,就需要使用类似IBUFDS、IBUFGDS、OBUFDS这样的库里提供的底层模块或原语来进行指示。例如:
-- VHDL example
clklvds : IBUFGDS
generic map (
DIFF_TERM => TRUE,
IBUF_DELAY_VALUE => "0",
IOSTANDARD => "DEFAULT")
port map (
I => LVDSClk_p,
IB => LVDSClk_n,
O => sclk);
onToGlobalClockTree: BUFG
port map (
I => sclk, -- in std_logic
O => gclk); -- out std_logic
// Verilog example
IBUFGDS clklvds(.I (LVDSClk_p),
.