这一篇在干嘛?

你平时写的 always 块、assign 语句,最终都要落到 FPGA 芯片里真实的硬件上。而芯片里有些硬件——全局时钟网络、双沿采样触发器、块存储、硬件乘法器——靠普通写法很难(或没法保证)用上,这时就需要”点名”例化芯片厂商提供的现成模块,也就是原语(Primitive)。这篇笔记把安路 EAGLE 系列原语库文档里的 23 个原语按类别梳理一遍,重点讲每个类别最常用的几个:端口、参数、例化代码和坑。

原语是什么?和普通 Verilog 什么关系

打个比方:FPGA 是一盒乐高,你平时写 Verilog 相当于”我要一个能实现与或非逻辑的东西”,具体怎么拼由综合工具决定;而原语是盒子里预装好的成品组件——时钟树、乘法器、FIFO 控制器这些专用电路,你直接说”给我用这个”,名字和引脚都对上号即可。

两者的关系可以这么理解:

  • 普通 Verilog:描述”行为”,综合器帮你映射到芯片资源。大多数逻辑这样写就够了。
  • 原语例化:描述”我要用哪个具体硬件单元”,例化写法和调用普通模块一模一样——模块名 #(.参数(...)) 实例名 (.端口(...));

那什么时候才需要手搓原语?典型场景:

  1. 时钟与高扇出信号:时钟要进专用全局时钟网络(BUFG),复位、时钟使能这类扇出极大的信号也建议走全局缓冲;
  2. DDR 类接口:引脚上数据是双沿采样的,必须用 IO 里专用的 IDDR/ODDR 单元;
  3. 精确延时:输入/输出管脚上的纳秒级延时调整,普通逻辑做不了;
  4. 硬核资源:块 RAM、硬件乘法器、除法器,想确保用上硬核而不是被综合成逻辑门,就显式例化;
  5. 芯片特殊功能:多重启动、读取芯片 64 位 DNA、配置时钟频率等,只有原语能碰。

原语不可移植

原语名是跟着厂商和器件系列走的。本篇所有原语都以 EG_LOGIC_ / EG_PHY_ 开头,是安路 EAGLE 系列专属命名,搬到 Xilinx 或 Intel 的芯片上编译必报错。反过来,别人的代码里出现 Xilinx 原语(如 BUFG、IDDR 原语名不同),移植到安路器件时也要对照本系列文档改名。

能用 IP 生成器就别手敲

文档里每个原语都有”使用方式”表,标明是否支持原语例化IP Generator(图形界面配置生成)两种途径。像 BRAM、FIFO 这类参数一大堆的原语,用 IP Generator 点选配置更不容易出错;只有 IP Generator 不支持的原语(如 BUFGMUX、MBOOT、MULT)才必须手写例化。

全家福:23 个原语速查总表

先上总表建立全局观,后面按类别精讲。名称一律以文档为准:

类别原语名用途一句话
时钟与布线EG_LOGIC_BUF芯片内部低延时布线资源
时钟与布线EG_LOGIC_BUFG高扇出全局时钟缓冲,延时抖动小
时钟与布线EG_LOGIC_BUFIOIO 组内的时钟分频器,每组两个
时钟与布线EG_LOGIC_BUFGMUX全局时钟二选一选择器
IO 类EG_LOGIC_IDELAY输入管脚延时控制(0~31 挡)
IO 类EG_LOGIC_ODELAY输出管脚延时控制(4 挡)
IO 类EG_LOGIC_IDDRIO 双沿采样数据 → 内部单沿数据
IO 类EG_LOGIC_ODDR内部单沿数据 → IO 双沿输出
IO 类EG_LOGIC_IDDRx2IO 上 1:4 串转并(更高速)
IO 类EG_LOGIC_ODDRx2IO 内 4:1 并转串(更高速)
IO 类EG_LOGIC_ODDRx2I4:1 并转串,省一个 pclk 时钟
存储类EG_LOGIC_FIFO硬核 FIFO 控制器,支持同步/异步/混合端口
存储类EG_LOGIC_RAMFIFO软核 FIFO 控制器,多深度指示与 Showahead 模式
存储类EG_LOGIC_DRAM分布式 RAM,可配单口/单双口
存储类EG_LOGIC_DRAM16X416×4 分布式 RAM 基本单元
存储类EG_LOGIC_BRAMBRAM9K/BRAM32K 块存储资源
运算类EG_LOGIC_MULT18×18 硬件乘法器
运算类EG_LOGIC_CLOCKED_DIV内部除法器(start/done 握手)
配置与特殊EG_LOGIC_MBOOT多重启动控制模块
配置与特殊EG_LOGIC_DNA64 位芯片 DNA 信息读取接口
配置与特殊EG_LOGIC_CCLK配置时钟频率控制
底层逻辑单元EG_PHY_LSLICE底层 SLICE:2 个增强 LUT5 + 2 寄存器 + 4 级进位链
底层逻辑单元EG_PHY_MSLICE底层 SLICE:2 个 LUT4 + 2 寄存器 + 2 级进位链

子模块别乱例化

文档特别注明:gray2bingray_countshowahead_buffer 是 RAMFIFO 的内部子模块,不建议用户直接例化。

时钟类:BUFG 与 BUFGMUX

BUFG:把信号送上全局时钟网络

BUFG 是高扇出的全局时钟缓冲器,延时和抖动都小。除了时钟,复位信号、时钟使能这类要”撒满全片”的高扇出网络也应该走它。端口极简:一个进 i、一个出 o,没有参数。

EG_LOGIC_BUFG BUFG_inst(
    .o(o),
    .i(I)
);

BUFGMUX:两路时钟二选一

BUFGMUX 是全局时钟缓冲选择器:选择信号 s 为低时输出 i0,为高时输出 i1。典型的用处是”跑慢时钟省电、跑快时钟干活”的场景下切换系统时钟。

EG_LOGIC_BUFGMUX 结构示意:两路时钟输入 i0/i1 经选择信号 s 控制的多路器输出

它有三个参数:INIT_OUT 设置输出初始化值(默认 “0”);PRESELECT_I0 / PRESELECT_I1 设置上电后预先选择哪一路(默认选 i0)。

EG_LOGIC_BUFGMUX #(
    .INIT_OUT ("0"),
    .PRESELECT_I0 ("TRUE"),
    .PRESELECT_I1 ("FALSE"))
BUFGMUX_inst(
    .o(o),
    .i0(i0),
    .i1(i1),
    .s(s)
);

BUFIO:IO 组里的分频器

BUFIO 是 IO 上的时钟分频器,每个 I/O 组有两个。注意它的三路输出性质不同:clko 输出与输入同频,但只能供 IO 使用clkdiv1clkdivx(x=1,2,4 分频)才能供全局时钟网使用。参数有 DIV(分频系数 2 或 4)、GSR(全局复位,默认 DISABLE)、STOPCLK(时钟关闭,默认 DISABLE)。

BUFIO 的输出别接错

clko 只能在 IO 局部用,想把它接进全局时钟网络是行不通的;要进全局网就接 clkdiv1 / clkdivx

IO 类:DDR 采样与管脚延时

先搞懂 DDR 单元在干嘛

引脚上的高速接口(比如摄像头、高速 ADC)常用双沿采样:一个时钟的上升沿和下降沿各传一位数据。而 FPGA 内部逻辑几乎都是单沿工作,所以需要专门的硬件做”翻译”——进芯片用 IDDR(1 位双沿 → 2 位单沿 q0/q1),出芯片用 ODDR(2 位单沿 d0/d1 → 1 位双沿输出)。

IDDR 的参数:ASYNCRST(默认 “ENABLE”,异步复位使能)和 PIPEMODE(“PIPED”/“NONE”,控制 q0q1 的数据对齐模式,默认 “PIPED”)。

EG_LOGIC_IDDR #(
    .ASYNCRST("ENABLE"),
    .PIPEMODE("PIPED"))
IDDR_inst(
    .q1(q1),
    .q0(q0),
    .clk(clk),
    .d(d),
    .rst(rst)
);

ODDR 同样只有一个 ASYNCRST 参数(默认 “ENABLE”):

EG_LOGIC_ODDR #(
    .ASYNCRST("ENABLE"))
ODDR_inst(
    .q(q),
    .d1(d1),
    .d0(d0),
    .clk(clk),
    .rst(rst)
);

IDDRx2 / ODDRx2:更高速的 1:4 与 4:1

EG_LOGIC_IDDRx2 是 IO 上的 1:4 串转并单元,功能与 IDDR 相同但支持更高的 IO 速度:串行输入 d 变成 4 位并行输出 q0~q3。它需要两个时钟:sclk 是 1/2 数据率的时钟,pclksclk 的 1/2(即 1/4 数据率)。

反方向,EG_LOGIC_ODDRx24:1 并转串:4 位并行数据 d0~d3 压成 1 位串行输出 q,同样用 pclk(1/2 sclk)和 sclk(1/2 数据率)双时钟。

EG_LOGIC_ODDRx2 #(
    .ASYNCRST("ENABLE"))
ODDRx2_inst(
    .q(q),
    .pclk(pclk),
    .sclk(sclk),
    .d3(d3),
    .d2(d2),
    .d1(d1),
    .d0(d0),
    .rst(rst)
);

EG_LOGIC_ODDRx2I 是 ODDRx2 的省时钟版本:直接用内部 sclk 的 2 分频充当 pclk,省掉一个时钟输入;代价是数据输出比 ODDRx2 模式晚一个 sclk 时钟周期

时钟关系别配错

用 x2 系列原语时记牢:sclk = 1/2 数据率,pclk = 1/2 sclk。pclk 接成了 sclk 同频,输出数据就会错位。用 ODDRx2I 时则根本没有 pclk 端口,别照着 ODDRx2 的模板连。

IDELAY / ODELAY:纳秒级延时微调

IDELAY 是输入延时控制单元,参数 INDEL 取 0~31,初始化延时 0.2ns,最大延时 3.8ns;ODELAY 是输出延时单元,参数 OUTDEL 只有 4 挡可调。两者端口都只有 io 出。

新版软件可以不例化

使用 4.2.266 及以后版本的软件,IDELAY/ODELAY 都不用再手动调用,直接在 IO CONSTRAINT 图形界面里设置延时长度即可,更省事。

存储类:FIFO 与 RAM

硬核 FIFO:EG_LOGIC_FIFO

这是 EAGLE 的 FIFO 控制器模块,支持同步/异步以及混合端口(读写位宽不同)的工作模式。核心端口:写侧 clkw/we/di/csw,读侧 clkr/re/do/csr/ore,外加四个状态标志——empty_flag(读空,与 clkr 同步)、aempty_flag(几乎空,可设阈值)、full_flag(写满,与 clkw 同步)、afull_flag(几乎满,可设阈值)。

EG_LOGIC_FIFO 端口示意:左侧写端口与读控制,右侧数据输出与空满标志

参数不少,抓住这几组:DATA_WIDTH_W/RDATA_DEPTH_W/R 定位宽和深度(深度可选 512~8192);E/AE/AF/F 四个值分别设定空、快空、快满、满标志的深度阈值;RESETMODE(“ASYNC”/“SYNC”)与 ASYNC_RESET_RELEASE 控制复位行为;ENDIAN 控制混合端口模式下大小端对齐;REGMODE_W/R 可选输出加一级寄存器(“OUTREG”)。

EG_LOGIC_FIFO #(
    .DATA_WIDTH_W(9),
    .DATA_DEPTH_W(1024),
    .MODE("FIFO8K"),
    .REGMODE_W("NOREG"),
    .REGMODE_R("NOREG"),
    .GSR("ENABLE"),
    .RESETMODE("ASYNC"),
    .ASYNC_RESET_RELEASE("SYNC"))
FIFO_inst(
    .rst(rst),
    .di(di),
    .clkw(clkw),
    .we(we),
    .csw(csw),
    .do(do),
    .clkr(clkr),
    .re(re),
    .csr(csr),
    .ore(ore),
    .empty_flag(empty_flag),
    .aempty_flag(aempty_flag),
    .full_flag(full_flag),
    .afull_flag(afull_flag)
);

软核版 EG_LOGIC_RAMFIFO 是 TD 软件集成的 FIFO 控制器,比硬核版多了实时深度指示rdusedw 可读个数 / wrusedw 已写个数)和 Showahead 模式SHOWAHEAD 置 1 时数据先摆到输出口、读使能来了就算读完,省一拍)。参数 DATA_WIDTH 范围 1432,ADDR_WIDTH 范围 215。

DRAM 与 BRAM:小存储用分布式,大存储用块

  • EG_LOGIC_DRAM:分布式 RAM 例化模块,可配置为单口、单双口 RAM。参数含读写各自的位宽/地址宽度/深度,以及 INIT_FILE 初始化文件。
  • EG_LOGIC_DRAM16X4:分布式 RAM 的基本单元(16 深 × 4 位宽),支持 INIT_D0~D3 四组初始化数据。要自己拼大容量时以它为砖头。
EG_LOGIC_DRAM16X4 #(
    .INIT_D0(16'h0000),
    .INIT_D1(16'h0000),
    .INIT_D2(16'h0000),
    .INIT_D3(16'h0000))
DRAM16X4_inst(
    .di(di),
    .waddr(waddr),
    .we(we),
    .wclk(wclk),
    .raddr(raddr),
    .do(do)
);
  • EG_LOGIC_BRAM:由 BRAM9K 或 BRAM32K 搭建的块存储,是三种里面容量最大的。关键参数 MODE 决定工作形态:DP(真双口)、SP(单口)、PDPW(伪双口)、FIFO。其他常用参数:WRITEMODE_A/B(“NORMAL”/“READBEFOREWRITE”/“WRITETHROUGH”,写数据时读出口的行为)、REGMODE_A/B(输出是否加寄存器)、INIT_FILE(初始化文件路径)、IMPLEMENT(选 “9K”、“9K(FAST)” 或 “32K” 物理单元)。

EG_LOGIC_BRAM 端口示意:A、B 两个对称端口,各含数据、地址、使能与时钟

BRAM 的 A/B 两口端口完全对称:dia/addra/cea/ocea/clka/wea/rsta/bea 对应 A 口写数据、写地址、片选、输出使能、时钟、写使能、复位、字节使能,B 口 dib~dob 同理;doa/dob 是各自口的读出数据。端口太多,例化建议直接走 IP Generator。

存储选型经验

几十字节到几百 bit 的小缓冲用 DRAM(省块存储);跨时钟域的数据缓存用 FIFO;帧缓存、大表项用 BRAM。别拿 BRAM 存几个数——块存储是稀缺资源。

运算类:硬件乘法器与除法器

MULT:18×18 硬件乘法器

EG_LOGIC_MULT 是芯片里的 18×18 硬件乘法器,输出 36 位。它自带三级流水寄存器:INPUTREGAINPUTREGB(A/B 口输入寄存器)和 OUTPUTREG(乘积输出寄存器),默认都是 “ENABLE”,相当于帮你免费打了两拍,时序好跑得多。

EG_LOGIC_MULT 端口示意:乘数 a/b 进入,控制与复位信号并行,输出乘积 p

注意它的复位是低有效且 A/B/输出三路独立:rstanrstbnrstpdn;片选 cea/ceb/cepd 也是三路独立、高有效。两个关键参数:INPUTFORMAT 选 “SIGNED”(默认)或 “UNSIGNED”;IMPLEMENT 选 “AUTO”/“DSP”/“GATE”——想强制走 DSP 硬核就填 “DSP”,“GATE” 则是用逻辑门搭(一般不选)。

EG_LOGIC_MULT #(
    .INPUT_WIDTH_A(18),
    .INPUT_WIDTH_B(18),
    .OUTPUT_WIDTH(36),
    .INPUTFORMAT("SIGNED"),
    .INPUTREGA("ENABLE"),
    .INPUTREGB("ENABLE"),
    .OUTPUTREG("ENABLE"),
    .SRMODE("ASYNC"),
    .IMPLEMENT("AUTO"))
MULT_inst(
    .p(p),
    .a(a),
    .b(b),
    .cea(cea),
    .ceb(ceb),
    .cepd(cepd),
    .clk(clk),
    .rstan(rstan),
    .rstbn(rstbn),
    .rstpdn(rstpdn)
);

乘法器复位是低有效

rstan/rstbn/rstpdn 三个复位都是低有效(名字里的 n 就是取反的意思),和高有效的 cea/ceb/cepd 片选极性正好相反,接反了乘法器会一直处于复位状态输出不动。

CLOCKED_DIV:要握手的除法器

EG_LOGIC_CLOCKED_DIV 是内部除法器,与乘法器最大的不同是它多周期计算:拉高 start 启动,等 done 拉高才能取结果 quotient(商)和 remain(余数)。参数 NUMER_WIDTHDENOM_WIDTH 默认都是 16。

EG_LOGIC_CLOCKED_DIV 端口示意:start 启动计算,done 指示完成,输出商与余数

EG_LOGIC_CLOCKED_DIV #(
    .NUMER_WIDTH(16),
    .DENOM_WIDTH(16))
CLOCKED_DIV_inst(
    .clk(clk),
    .rst(rst),
    .start(start),
    .numer(numer),
    .denom(denom),
    .quotient(quotient),
    .remain(remain),
    .done(done)
);

除法别写在 always 里

直接在组合逻辑里写 / 运算符,综合出来又大又慢;用这个原语配合 start/done 握手状态机,才是除法的正确打开方式。使用时注意文档端口表中 numer 为除数、denom 为被除数,与日常”被除数在前”的习惯相反,接反了商和余数就全错了。

配置与底层:MBOOT、DNA、CCLK 和 SLICE

MBOOT:多重启动

EG_LOGIC_MBOOT 是多重启动控制模块,让 FPGA 固件损坏后能从备用镜像启动。启动地址有两种来源:参数 ADDR_SOURCE_SEL 选 “STATIC”(用参数 STATIC_ADDR 指定的静态地址,默认 8’b0)或 “DYNAMIC”(用端口 dynamic_addr 输入的 8 位地址)。这 8 位地址是 SPI Flash 地址的高 8 位。重启靠端口 rebootn(低有效)。

EG_LOGIC_MBOOT #(
    .ADDR_SOURCE_SEL("STATIC"),
    .STATIC_ADDR(8'b00000000))
MBOOT_inst (
    .rebootn(reboot_n),
    .dynamic_addr(addr)
);

DNA:芯片的身份证

EG_LOGIC_DNA 用于读取芯片唯一的 64 位 DNA 信息(可做防抄板、设备序列号)。移位读取式接口:拉高 shift_en,在 clk 节拍下从 dout 一位移出,LSB 优先din 是用户数据输入。

CCLK:配置时钟

EG_LOGIC_CCLK 控制配置时钟频率,参数 FREQ 只能填 “4.5”、“6.5”、“10.0”、“30.0” 四个字符串(单位 MHz,默认 “4.5”),端口只有使能 en 和输出 cclk

LSLICE / MSLICE:看得见但平时别碰

EG_PHY_LSLICE(2 个增强 LUT5 + 2 寄存器 + 4 级进位链)和 EG_PHY_MSLICE(2 个 LUT4 + 2 寄存器 + 2 级进位链)是芯片最底层的逻辑单元,参数多到眼花(LUT 初始化值、各种 MUX 选择、DFFMODE 等),文档里给了配置成 LUT5/LUT6/LUT4 的例化示例。日常写逻辑时这些全由综合器自动管理,竞赛和一般开发中不需要手动例化,知道它们的存在即可。

最佳实践清单

  • 时钟和全局信号上 BUFG,双沿接口用 IDDR/ODDR,x2 系列记得核对 pclk 与 sclk 的分频关系
  • 参数繁多的原语(BRAM、FIFO)优先用 IP Generator 生成,再对照文档检查端口
  • 复位极性逐个确认:MULT 是低有效,FIFO/IDDR/ODDR 的 rst 是高有效,MBOOT 的 rebootn 是低有效
  • 例化后跑一遍综合,对照文档端口表核对每个端口位宽,别信编译器帮你猜