这一篇在干嘛?
这是 PH1P 原语库两连的第 1 篇,覆盖 UG1310 的逻辑族(PH1P_LOGIC_*)里最常用的部分:缓冲、运算、RAM、乘法器和 FIFO。阅读姿势是”按功能族查”——遇到高扇出信号想插缓冲、想手搓一块 RAM 或 FIFO 时回来翻。第 2 篇讲 IO、时钟与 DSP 原语。
先搞懂:原语是什么、什么时候用
原语就是 FPGA 内部硬件资源在 HDL 世界里的”零件编号”。你平时写的 always @(posedge clk) 会被综合器翻译成 SLICE 里的 LUT + DFF,assign a = b; 可能被翻译成一根走线——这些”翻译”综合器做得很成熟,大多数时候你不需要手动干预。但有三类场景综合器会失手或不够聪明:
- 物理资源有专属走线:全局时钟缓冲 BUFG、IO 延迟 IDELAY,这些资源综合器不一定知道你想用,需要显式例化;
- 时序要求苛刻:低延时的 DRAM、带 Showahead 模式的 FIFO,综合器从你的 RTL 推断出来的结构可能多一级寄存器;
- 器件专属功能:读芯片 DNA、多启动 MBOOT、ECC 校验,这些功能根本没有对应的 RTL 写法,只能例化原语。
UG1310 覆盖 PH1P100、PH1P35、PH1P50 三个系列的器件,手册开头有一张器件支持总表——动手前先查你的型号支不支持目标原语。比如 MBOOT 只在 PH1P100 上有,MBOOT_V2 只在 PH1P35/50 上有,搞反了编译直接报错。
原语的使用方式有两种:原语例化(直接在 Verilog 里实例化模块)和 IP Generator(在 TD 软件里图形化配置生成)。每个原语的手册条目都会标明支持哪些方式。经验法则:有 IP 的优先用 IP(参数有图形界面、不易写错),只有原语的才手写例化。
全局缓冲:BUF 与 BUFG

这一族只有两个端口 i 和 o,但作用完全不同:
| 原语 | 定位 | 典型用途 | IP Generator |
|---|---|---|---|
| PH1P_LOGIC_BUF | 低延时布线资源 | 关键路径上强制插一级缓冲 | 不支持 |
| PH1P_LOGIC_BUFG | 高扇出全局时钟缓冲 | 时钟、复位、时钟使能等高扇出网络 | 支持 |
BUFG 的手册描述是”延时和抖动小”,这是时钟网络的命根子。FPGA 内部有专用的全局时钟树,只有通过 BUFG 才能挂上去。如果你的设计里某时钟是从普通逻辑生成的(比如分频出来的时钟),综合器有时不会自动给它配 BUFG,时钟偏斜会大到难以收敛——这时手动例化一级 BUFG 就能解决:
PH1P_LOGIC_BUFG u_PH1P_LOGIC_BUFG (
.i (i),
.o (o)
);常见坑:BUFG 是稀缺资源,不是越多越好
全局时钟网络数量有限(每个时钟区域就那么几条),把普通数据信号也挂到 BUFG 上会挤占时钟资源,布局布线时反而报”BUFG 不够用”。只给时钟、全局复位、全局使能这类真正高扇出的信号用。
运算单元:除法器、移位器和 SLICE
SEQ_DIV:会算除法的”外设”

Verilog 里的 / 运算符综合出来又慢又占资源,因为除法本质是多周期迭代运算。PH1P_LOGIC_SEQ_DIV 把它做成了带握手协议的专用模块:给 start 脉冲,等 done 拉高,取走 quotient(商)和 remain(余数)。
| 端口 | 方向 | 位宽 | 描述 |
|---|---|---|---|
| clk | input | 1 | 同步时钟输入 |
| rst | input | 1 | 复位信号,高有效 |
| start | input | 1 | 计算开始 |
| numer | input | NUMER_WIDTH | 除数 |
| denom | input | DENOM_WIDTH | 被除数 |
| quotient | output | NUMER_WIDTH | 商 |
| remain | output | DENOM_WIDTH | 余数 |
| done | output | 1 | 计算完成标志,高有效 |
PH1P_LOGIC_SEQ_DIV #(
.NUMER_WIDTH(16),
.DENOM_WIDTH(16)
) u_PH1P_LOGIC_SEQ_DIV (
.clk(clk), .rst(rst), .start(start),
.numer(numer), .denom(denom),
.quotient(quotient), .remain(remain), .done(done)
);注意手册表格里 numer 标注的是”除数”、denom 是”被除数”,与直觉相反(英文 numerator 本意是被除数)——以中文描述为准,写代码前用一个具体数字(如 100/7)仿真验证一遍商和余数。
SHIFTER:动态深度的移位寄存器

比 data <= {data[DATA_WIDTH-2:0], din} 强的地方在于深度可调:SHIFT_TYPE 配成 “DYNAMIC” 时,depth 端口实时控制移位深度。深度的换算公式是:
| 移位寄存器位宽(bit) | 最大深度 | 深度属性值(depth) | 实际深度(shift_depth) |
|---|---|---|---|
| 16 | 33 | 0x00 | 2 |
| 16 | 33 | 0x1F | 33 |
| — | — | 公式 | shift_depth = depth[4:0] + 2 |
| 8 | 65 | 0x3F | 65 |
| — | — | 公式 | shift_depth = depth[5:0] + 2 |
SHIFT_TYPE=“FIXED” 模式下 depth 端口无效,深度由 DATA_DEPTH 参数静态决定。
PH1P_LOGIC_SHIFTER #(
.DATA_WIDTH (16),
.DEPTH_WIDTH (2),
.SHIFT_TYPE ("FIXED")
) u_PH1P_LOGIC_SHIFTER (
.clk(clk), .en(en),
.datain(datain),
.depth ({2{1'b0}}),
.dataout(dataout)
);SLICE:最底层的”积木”
SLICE 是 PH1P 逻辑阵列的基本单元:内部包含两个非独立的双 LUT5(可拼成 LUT6)和两个独立的 DFF,外加进位链。

平时综合器自动分配,但理解它的参数表对你看懂时序报告、做底层优化有帮助。关键端口分组:
- 组合输入:
ia/ib/ic/id/ie/imf(LUT 输入),ima/imb(组合/时序复用); - 时序控制:
clk、cea/ceb(时钟使能)、ssr(同步置位/复位)、asr(异步置位/复位); - 输出:
oqa/oqb(时序输出)、ofa/ofb(组合输出); - 进位链:
fci进位输入、fco进位输出,做加法器时逐级串联。
参数表里最有意思的是 LUTMODE(“LUT6” / “DLUT5”)和一堆 *_MUX 选择参数(CLKMUX、SSRMUX、ASRMUX 等可选 “0/1/SIG/INV”,即接地、接高、接信号、取反)。例化 SLICE 配 LUT6 输出的写法:
PH1P_LOGIC_SLICE #(
.REGB_SD ("LUT6"),
.LUTMODE ("LUT6"),
.CLKMUX ("SIG"),
.CEBMUX ("SIG"),
.SSRMUX ("SIG"),
.ASRMUX ("SIG"),
.OFB_SD ("LUTX"),
.OQBMUX ("QB")
) u_PH1P_LOGIC_SLICE (
.imf(imf), .ie(ie), .id(id), .ic(ic), .ib(ib), .ia(ia),
.ceb(1'b1), .clk(clk), .ssr(1'b0), .asr(1'b0),
.oqb(oqb), .ofb(ofb)
);常见坑:SLICE 手写例化性价比极低
SLICE 支持 IP Generator=否、参数几十个,除非你在做极特殊的底层优化(比如手工映射进位链),否则让综合器自己排布就行。理解它的意义在于看懂 Chip Viewer 里的资源占用。
分布式 RAM:DRAM

DRAM(Distributed RAM)用 LUT 阵列实现存储,特点是读写都是组合路径、延时极低,适合小容量(几十~几千 bit)、对延迟敏感的场景,比如系数表、查找表、小型状态缓存。端口分写侧和读侧两组:
| 端口 | 方向 | 位宽 | 描述 |
|---|---|---|---|
| di | input | DATA_WIDTH_W | 写数据 |
| waddr | input | ADDR_WIDTH_W | 写地址 |
| wclk | input | 1 | 写时钟 |
| we | input | 1 | 写使能,高有效 |
| raddr | input | ADDR_WIDTH_R | 读地址 |
| rdo | output | DATA_WIDTH_R | 读数据 |
| rclk | input | 1 | 读时钟 |
| rrst | input | 1 | 读复位,可配置同步/异步 |
| rce | input | 1 | 读使能,高有效 |
| rdoq | output | DATA_WIDTH_R | 读数据(带寄存器) |
关键参数:READREG 选 “ENABLE” 时数据从 rdoq 输出(多一级寄存器,时序好但延迟大);RESETMODE 选 “SYNC”/“ASYNC”;INIT_FILE 可指定初始化文件、FILL_ALL 可把未初始化空间填成固定图案(如 “0101”)。
PH1P_LOGIC_DRAM #(
.DATA_WIDTH_W (9), .ADDR_WIDTH_W (10), .DATA_DEPTH_W (1024),
.DATA_WIDTH_R (9), .ADDR_WIDTH_R (10), .DATA_DEPTH_R (1024),
.INIT_FILE ("NONE"), .FILL_ALL ("NONE"),
.READREG ("DISABLE"), .RESETMODE ("SYNC")
) u_PH1P_LOGIC_DRAM (
.di(di), .waddr(waddr), .we(we), .wclk(wclk),
.raddr(raddr), .rclk(rclk), .rrst(rrst), .rce(rce),
.rdo(rdo), .rdoq(rdoq)
);选型口诀:容量小、要快 → DRAM;容量大(KB 级以上)→ ERAM。
块存储:ERAM20K

ERAM 是芯片里真实的 20Kbit 存储硬核,一个 ERAM20K 支持四种工作模式(MODE 参数):
| MODE | 含义 |
|---|---|
| ”SP” | 单口 RAM |
| ”DP” | 双口 RAM(A/B 两个口,默认) |
| “PDPW” | 伪双口(A 口只写、B 口只读) |
| “FIFO” | 当 FIFO 硬核用 |
双口模式下 A、B 各有一整套端口:dia/addra/cea/ocea/clka/wea/rsta/bea 与 dib/.../beb 对称,bea/beb 是字节使能(BYTE_ENABLE 参数配 0/8/10)。此外还带 ECC 校验端口(ecc_sbiterr/ecc_dbiterr 及注错端口)。
写模式(WRITEMODE_A/B)有三个选项,处理”写的时候读同地址”的行为:
- “NORMAL”:写时读出的是旧数据或不定(默认);
- “READBEFOREWRITE”:先输出旧数据再写入;
- “WRITETHROUGH”:写的同时新数据直接透传到读出口。
PH1P_LOGIC_ERAM #(
.DATA_WIDTH_A(16), .DATA_WIDTH_B(16),
.ADDR_WIDTH_A(11), .ADDR_WIDTH_B(11),
.DATA_DEPTH_A(2048), .DATA_DEPTH_B(2048),
.MODE("PDPW"),
.REGMODE_A("NOREG"), .REGMODE_B("NOREG"),
.WRITEMODE_A("NORMAL"), .WRITEMODE_B("NORMAL"),
.IMPLEMENT("20K"),
.ECC_ENCODE("DISABLE"), .ECC_DECODE("DISABLE"),
.CLKMODE("ASYNC"), .SSROVERCE("DISABLE"),
.OREGSET_A("SET"), .OREGSET_B("SET"),
.RESETMODE_A("ASYNC"), .RESETMODE_B("ASYNC"),
.ASYNC_RESET_RELEASE_A("SYNC"), .ASYNC_RESET_RELEASE_B("SYNC"),
.INIT_FILE("NONE"), .FILL_ALL("NONE")
) u_PH1P_LOGIC_ERAM (
.dia(dia), .dib(dib), .addra(addra), .addrb(addrb),
.cea(cea), .ceb(ceb), .ocea(ocea), .oceb(oceb),
.clka(clka), .clkb(clkb),
.wea(wea), .web(web), .bea(bea), .beb(beb),
.rsta(rsta), .rstb(rstb),
.ecc_sbiterr(ecc_sbiterr), .ecc_dbiterr(ecc_dbiterr),
.ecc_sbiterrinj(ecc_sbiterrinj), .ecc_dbiterrinj(ecc_dbiterrinj),
.doa(doa), .dob(dob)
);常见坑:输出不带寄存器时不能用同步复位
手册特别注明:REGMODE 选 “NOREG”(输出不带寄存器)时,RESETMODE_A/B 只能选 “ASYNC”;选 “OUTREG” 后同步/异步均可。配错了工具会报参数错误。
硬件乘法器:18x18 MULT

PH1P_LOGIC_MULT 是 18x18 的硬件乘法器,A/B 输入各 18bit,输出 36bit。它与 DSP 大模块的区别:MULT 是轻量版(只能做纯乘法),DSP(见第 2 篇)是带预加器、三输入加法器、逻辑单元的完整运算链。
端口分三组寄存器,各自有独立的片选和复位:A 口(cea/rstan)、B 口(ceb/rstbn)、乘积输出(cepd/rstpdn)。注意复位都是低有效(名字里的 n)。
参数里的门道:
INPUTFORMAT:“SIGNED”/“UNSIGNED”,决定乘法语义;INPUTREGA/INPUTREGB/OUTPUTREG:三级流水各配 “ENABLE”/“DISABLE”,全开是 3 级流水、频率最高,全关是纯组合乘法、延迟最小;IMPLEMENT:“AUTO” 让工具选,“DSP” 强制用 DSP 硬核,“GATE” 用 LUT 搭(仅在 DSP 资源耗尽时考虑)。
PH1P_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")
) u_PH1P_LOGIC_MULT (
.p(p), .a(a), .b(b),
.cea(cea), .ceb(ceb), .cepd(cepd), .clk(clk),
.rstan(rstan), .rstbn(rstbn), .rstpdn(rstpdn)
);FIFO:RAMFIFO 和 FIFO

PH1P 有两个 FIFO 原语,选型是本篇的重点:
| 对比项 | PH1P_LOGIC_RAMFIFO | PH1P_LOGIC_FIFO |
|---|---|---|
| 本质 | 软核 FIFO 控制器 | 硬核 FIFO 控制器 |
| 时钟模式 | 仅同步(单时钟) | 同步/异步(CLKMODE) |
| 位宽 | DATA_WIDTH 任意 | 1~320 |
| 深度位宽 | ADDR_WIDTH 任意 | 9~14(大深度) |
| 特色 | 实时深度指示 + Showahead 模式 | 几乎空/满标志、ECC、读指针输出 |
RAMFIFO 的独门本事是 Showahead 模式(SHOWAHEAD=“1”):数据先放到输出口等着,re 拉高表示”这个数据我拿走了”,省去一拍读延迟,做流式解析协议时非常好用。它还输出 rdusedw/wrusedw(可读/已写个数),可以实时掌握队列水位:
PH1P_LOGIC_RAMFIFO #(
.DATA_WIDTH(8), .ADDR_WIDTH(8),
.SHOWAHEAD("0"), .IMPLEMENT("20K")
) u_PH1P_LOGIC_RAMFIFO (
.rst(rst), .di(di), .clk(clk), .we(we), .dout(dout),
.re(re),
.empty_flag(empty_flag), .full_flag(full_flag),
.rdusedw(rdusedw), .wrusedw(wrusedw)
);PH1P_LOGIC_FIFO 则是跨时钟域场景的正解:CLKMODE=“ASYNC” 时读写两侧各自独立时钟/复位(clkw/wrst 与 clkr/rrst),标志信号各自同步到本侧时钟(full_flag 同步于 clkw、empty_flag 同步于 clkr)。afull_flag/aempty_flag 是可编程的”几乎满/空”水位报警(AE/AF 参数设定阈值),提前留出反应时间避免真满真空。
PH1P_LOGIC_FIFO #(
.AE(AE), .AF(AF),
.DATA_WIDTH(9), .ADDR_WIDTH(10),
.CLKMODE("SYNC"), .REGMODE_R("NOREG"),
.ECC_ENCODE("DISABLE"), .ECC_DECODE("DISABLE"),
.SSROVERCE("DISABLE"), .FIRSTWRITE_RD("DISABLE"),
.ASYNC_RESET_RELEASE("SYNC")
) u_PH1P_LOGIC_FIFO (
.clkw(clkw), .wrst(wrst), .we(we), .owe(owe), .csw(csw), .di(di),
.clkr(clkr), .rrst(rrst), .re(re), .ore(ore), .csr(csr),
.dout(dout),
.fifo_wrpointer(fifo_wrpointer), .fifo_rdpointer(fifo_rdpointer),
.empty_flag(empty_flag), .aempty_flag(aempty_flag),
.full_flag(full_flag), .afull_flag(afull_flag),
.ecc_sbiterrinj(ecc_sbiterrinj), .ecc_dbiterrinj(ecc_dbiterrinj),
.ecc_sbiterr(ecc_sbiterr), .ecc_dbiterr(ecc_dbiterr)
);选型口诀:同步小队列 → RAMFIFO(要 Showahead/水位就用它);跨时钟域或大深度 → FIFO。
通关标准:
① 能不看手册说出 BUFG 的两个端口和用途,并解释为什么高扇出信号要挂 BUFG; ② 能根据”容量、时钟域数、是否要 Showahead”三个条件在 DRAM/ERAM/RAMFIFO/FIFO 里做出正确选型; ③ 能正确例化一个 SEQ_DIV,并解释 numer/denom 的真实含义与 done 握手时序。
为什么 PH1P_LOGIC_MULT 的复位信号名字带 n,而 SEQ_DIV 的 rst 不带?
因为 MULT 内部复位是低电平有效(rstan/rstbn/rstpdn 的 n 表示 negative),而 SEQ_DIV 的 rst 是高电平有效。原语手册没有统一约定,每个原语都要核对端口描述里的”高有效/低有效”字样,接反了芯片上电就处于复位态,表现为”逻辑全死了但没有报错”。