3. 认识 .lpf 文件:基本结构、语法规则与实战要点

好,咱们今天来聊聊 Lattice 的 .lpf 文件。说实话,我刚从 Xilinx 转到 Lattice 平台那会儿,第一眼看到 .lpf 文件,心里是有点懵的。它跟 Xilinx 的 .ucf 或者 Vivado 的 .xdc 长得不太一样,语法也自成一派。但用顺手了之后,你会发现它其实很简洁、很直接。

.lpf 的全称是 Lattice Preference File,说白了就是给 FPGA 的引脚分配和物理约束用的。它不像 .sdc 那样主要管时序,.lpf 更关心的是「哪个信号焊到了哪个引脚上」、「这个引脚的电气特性是什么」、「这个时钟走哪条全局时钟网络」。嗯,咱们一个一个来看。

3.1 .lpf 文件的基本结构

一个典型的 .lpf 文件,结构其实挺清晰的。我习惯把它分成三大块:

  • 引脚分配区:把顶层模块的端口名映射到芯片的物理引脚上
  • 电气特性区:设置 I/O 标准、驱动能力、上下拉等
  • 物理约束区:指定时钟网络、PLL 位置、差分对等高级约束

举个例子,一个最简单的 .lpf 文件长这样:

# 引脚分配
LOCATE COMP "clk" SITE "P3";
LOCATE COMP "rst_n" SITE "P5";
LOCATE COMP "led[0]" SITE "P10";
LOCATE COMP "led[1]" SITE "P11";

# 电气特性
IOBUF COMP "clk" IO_TYPE=LVCMOS33;
IOBUF COMP "rst_n" IO_TYPE=LVCMOS33 PULLMODE=UP;
IOBUF COMP "led[*]" IO_TYPE=LVCMOS33 DRIVE=8;

# 时钟约束
FREQUENCY NET "clk" 50 MHz;

你看,每条语句都以分号结尾,大小写不敏感,但引号里的名字必须跟顶层模块的端口名完全一致。我在项目中遇到过好几次因为大小写不匹配导致编译报错的情况,后来就养成了习惯——所有名字都用小写,省心。

3.2 语法规则:你必须知道的几个要点

.lpf 的语法其实不复杂,但有几个坑,我当年都踩过。咱们一条条说。

3.2.1 引脚分配:LOCATE 语句

这是最常用的语句。格式是:

LOCATE COMP "端口名" SITE "引脚编号";

注意,引脚编号要用双引号括起来,比如 "P3"、"A12"、"R5" 这种。Lattice 的引脚命名规则跟芯片封装有关,你可以在数据手册或者 Lattice Diamond 的 Floor Planner 里查到。

我的小技巧:我习惯在写代码之前,先把原理图里的引脚分配整理成一个 Excel 表格,然后用脚本批量生成 .lpf 语句。这样既快又不容易出错。尤其是那些几十个引脚的并行总线,手写太容易漏了。

3.2.2 电气特性:IOBUF 语句

这个用来设置引脚的电气参数。格式是:

IOBUF COMP "端口名" IO_TYPE=LVCMOS33 DRIVE=8 SLEWRATE=FAST;

常用的参数有:

参数 说明 常用值
IO_TYPE I/O 标准 LVCMOS33, LVCMOS25, LVDS, SSTL 等
DRIVE 驱动能力 (mA) 4, 8, 12, 16 (取决于 IO_TYPE)
SLEWRATE 压摆率 SLOW, FAST
PULLMODE 上下拉 UP, DOWN, NONE
注意:我曾经在一个项目中,把 3.3V 的 LVCMOS 信号接到了 1.8V 的 bank 上,结果芯片工作异常,查了两天才发现是 IO_TYPE 写错了。所以,一定要确认每个 bank 的供电电压,再选对应的 IO_TYPE。

3.2.3 时钟约束:FREQUENCY 语句

这个语句用来告诉工具,某个时钟网络的频率是多少。格式是:

FREQUENCY NET "时钟名" 100 MHz;

注意,这里用的是 NET 而不是 COMP。因为时钟信号在布线之后会变成一条网络,而不是一个端口。嗯,这个细节容易搞混。

3.3 注释方式:三种方法,随你选

.lpf 支持三种注释方式,跟 C 语言有点像:

  • 单行注释:用 # 开头,整行都是注释
  • 行内注释:在语句后面加 //,后面的内容被注释掉
  • 块注释:用 /* ... */ 包裹多行注释

举个例子:

# 这是单行注释
LOCATE COMP "clk" SITE "P3";  // 这是行内注释

/*
  这是块注释
  可以写多行
  比如说明这个引脚为什么这么分配
*/

我个人习惯用 # 做单行注释,因为一眼就能看出来。块注释我一般用来写版本历史或者修改记录。

3.4 .lpf 与 .sdc 的区别:各司其职

很多刚接触 Lattice 的朋友会问:.lpf 和 .sdc 到底有什么区别?能不能混着用?

我的回答是:它们分工不同,但需要配合使用

咱们用一张图来理解:

.lpf 与 .sdc 的分工 .lpf 文件 物理约束 • 引脚分配 (LOCATE) • I/O 电气特性 (IOBUF) • 时钟网络分配 • 差分对定义 • PLL 物理位置 • 引脚分组 (GRP) → 告诉工具「信号焊在哪里」 .sdc 文件 时序约束 • 时钟周期定义 • 输入延迟 (set_input_delay) • 输出延迟 (set_output_delay) • 伪路径 (set_false_path) • 多周期路径 • 时钟分组 → 告诉工具「信号跑多快」 两者缺一不可,共同完成 FPGA 设计的约束工作

说白了,.lpf 管的是「物理世界」——你的信号到底接在哪个引脚上,这个引脚用什么电平标准。而 .sdc 管的是「时间世界」——你的时钟频率是多少,输入输出信号跟时钟的时序关系是什么。

它们之间有一个交集:时钟约束。在 .lpf 里,你可以用 FREQUENCY 语句指定时钟频率,但这个约束只用于物理综合时的时钟网络分配。真正的时序分析,还是要靠 .sdc 里的 create_clock 来定义。

我的建议:把 .lpf 当作引脚和物理约束的「唯一来源」,把 .sdc 当作时序约束的「唯一来源」。不要在两个文件里重复定义同样的东西,否则容易冲突。我见过有人把时钟频率同时在 .lpf 和 .sdc 里写了不同的值,结果综合结果跟预期完全不一样,查了半天才发现是这里的问题。

3.5 实战中的一些经验

最后,分享几个我在实际项目中积累的小经验:

  • 版本管理:.lpf 文件一定要纳入版本控制。我习惯在文件头部写一段注释,记录修改日期、修改人和修改原因。
  • 批量操作:如果有很多引脚需要分配,可以用 LOCATE COMP "data[*]" SITE "P[*]" 这种通配符写法,但要注意引脚编号的对应关系。
  • 差分信号:对于 LVDS 等差分信号,只需要分配正极 (P) 的引脚,负极 (N) 会自动分配。但如果你需要手动指定,可以用 LOCATE COMP "rx_p" SITE "A1"; LOCATE COMP "rx_n" SITE "A2"; 这样写。
  • 未使用引脚:建议在 .lpf 里显式地处理未使用引脚,比如设置成弱上拉或者三态,避免悬空导致功耗问题。

嗯,关于 .lpf 文件的基本内容,差不多就这些了。它不像 .sdc 那么复杂,但却是 FPGA 设计里不可或缺的一环。你想想看,如果引脚分配错了,时序约束写得再好,板子也跑不起来,对吧?


公众号:蓝海资料掘金营,微信deep3321