ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PAL输出反馈结构解析:宏单元、极性熔丝与JEDEC工程实践

PAL输出反馈结构解析:宏单元、极性熔丝与JEDEC工程实践

简介:PAL(可编程阵列逻辑)是EDA/PLD设计中的经典器件,这份PDF资料围绕其输出与反馈结构展开,适合数字电路课程学生、电子设计竞赛选手及可编程逻辑入门开发者阅读。资料系统梳理五种典型结构:专用输出基本门阵列、可编程I/O、寄存输出、异或结构及算术选通反馈结构,逐一说明输出极性设定、三态缓冲器选通、D触发器反馈、异或门条件保持与取反、反馈选通生成16种逻辑组合等关键细节,并结合原理图直观呈现信号路径,帮助读者理解组合逻辑、时序逻辑及算术运算在PAL中的实现差异。该PDF文件共1个,大小约218KB,内容精炼、图示完整,便于在移动端或电脑端快速查阅。资源已有235人次学习,通过梳理这些结构,读者可掌握PAL内部工作原理,为后续学习CPLD/FPGA等更复杂的可编程器件提供扎实基础。

1. 为什么学 PAL 要先看“输出和反馈结构”:一段反直觉的板级连线

如果只说一句话说清 PAL 的输出和反馈结构,那就是:在可编程逻辑器件里,一个引脚既能做输出,又可以把当前电平接回内部逻辑参与下一次运算。这个“既能出、又能回”的结构,正是 PAL 被用于胶合逻辑、总线控制器和简单状态机数十年的根基,也是它在 EDA 工具中的行为与普通 AND-OR 阵列描述完全不同的地方。很多人第一次接触 PAL 只盯着输入方程和乘积项,直到给输出加了反相后整片逻辑全反了,或者仿真里正确的反馈环到了板子上不停翻转,才意识到输出宏单元里的极性熔丝、输出使能(OE)和反馈多路器才是真正的指挥官。这篇笔记按我调器件、抓毛刺、和编程器搏斗的顺序,把输出和反馈结构拆开,再带到 EDA 工具里写出能编译、能烧录、能对上实测波形的工程写法,最后列出最容易翻车的地方。适合谁看:还在用 PLD 做胶合逻辑的嵌入式工程师、想把老芯片用明白的爱好者,以及只写过 FPGA 但没接触过“宏单元”概念的入门者。

2. 输出宏单元解构:触发器、极性熔丝与OE控制,反馈藏在哪里

PAL 里的“输出”从来不是一个简单的反相器加焊盘,而是一整个宏单元(OLMC)。宏单元内部至少包含四样东西:一个可旁路的 D 触发器、一个输出极性熔丝、一个三态输出使能控制,以及一个决定“从哪条路读回”的反馈多路器。反馈路径之所以重要,是因为它决定了一个输出引脚在下一拍究竟引用哪个信号源:是引脚上的电平,还是触发器 Q 端的内部逻辑。这二者的区别,直接造成了组合反馈和寄存器反馈两套完全不同的行为体系。

2.1 从 PAL16R8 到 22V10:宏单元的几种典型“拼法”

先看几类常见器件的宏单元组成,能帮你建立“反馈路径不止一种”的整体印象。PAL16L8 是纯组合输出,没有触发器,引脚在输出禁用时可以作为输入读回;PAL16R8 的 8 个输出全部挂在 D 触发器上,统一时钟、统一 OE,反馈从触发器 Q 端直接取;PAL16R4 和 PAL16R6 则是部分寄存器、部分组合的混合体,反馈来源也随之分成两派;到了 22V10,每个宏单元都可以在组合和寄存器之间配置,而且每个宏单元有独立的 OE 和独立的极性选择,反馈来源可在引脚与触发器之间二选一。

器件例子宏单元类型触发器数量OE 控制方式反馈可选来源
PAL16L8全组合无全局/乘积项引脚
PAL16R8全寄存器8全局触发器
PAL16R4/PAL16R6组合+寄存器混合4 或 6全局引脚或触发器
22V10每宏单元可配置10独立引脚或触发器

以 22V10 作为学习样本是最划算的:它的每个宏单元暴露了全部行为边界,老器件的结构可以看作把 22V10 砍掉一部分后的特例。注意,这些名字在业界是通用器件分类命名,不同厂商在输出极性定义、OE 有效电平上可能不完全一致,所以真正动手前一定要以器件数据手册里的“宏单元结构图”为准,而不是拿某一个片子上的经验硬套到另一片。宏单元结构图里通常会用开关符号标出极性熔丝和反馈多路器的位置,照着路径走一遍,你就能看懂编译器生成的熔丝位到底是干什么的。

2.2 输出极性:方程里的“!”不决定引脚电平,熔丝才决定

写 EDA 方程时,!Q只是逻辑非,但引脚上的真实电平要经过输出极性熔丝的第二次处理。极性熔丝设为同相(Active High)时,引脚输出方程计算结果 F;设为反相(Active Low)时,引脚输出!F。于是会出现一个非常反直觉的情况:方程里写的是Q = A & B,如果极性是反相,引脚实际给出去的是!(A & B)。

这个细节对反馈的影响极大。组合反馈是从引脚读回来的,所以读到的电平已经经过一次极性反相;寄存器反馈是从触发器 Q 端读回的,Q 端没有经过输出反相。也就是说,同一个宏单元,你用引脚反馈和用寄存器反馈,在方程里看到的逻辑值可能正好相反。这个“背着负号跑”的问题,是很多 PLD 项目里“仿真全对、板子全反”的头号来源。

极性设置引脚输出引脚反馈读回寄存器反馈读回
Active HighFFF
Active Low!F!FF

如果设计里既想要反相输出驱动负逻辑负载,又要把这个信号拿回内部做条件判断,最稳的做法是:反馈从寄存器取(前提是该宏单元是寄存器模式),或者在外围再补一次反相,而不是让反馈路径上同时背着两个负号到处跑。很多编译器不会替你纠错,因为它只负责实现方程,不负责判断你的方程意图到底是在引脚上取还是在触发器上取。

2.3 OE与三态:反馈在什么时候“能”发生

OE 控制的是三态驱动器。OE 有效时,引脚被内部逻辑强驱动,反馈读回的是内部逻辑的电平;OE 无效时,引脚进入高阻,引脚反馈读到的不是内部逻辑,而是外部电路强加的电平或悬空噪声。这就是同一个 I/O 引脚既能输出又能当输入用的物理基础:输出被禁用的一瞬间,引脚就变成了输入。寄存器反馈则永远有效,它直接从内部信号节点取数,不受 OE 控制,也不受引脚上外接负载的影响。

OE 状态内部逻辑引脚电平引脚反馈读回
有效FF(被驱动)F
无效F由外部决定外部电平,不是 F

这带来两个工程结论。第一,把 PAL 的 I/O 口当总线读回时,必须先禁用 OE 再读,否则读回的是驱动器输出的值,等价于自己确认自己,外部数据根本进不来。第二,如果想让内部状态不随外部总线变化,反馈应该选寄存器路径,绕开引脚电平这种“公共区域”。另外要记住一个硬约束:像 22V10 这类器件,所有寄存器的时钟是同一个全局时钟,不能为单个触发器选不同时钟源,复位和预置也常常是全局引脚控制。因此 PAL 里的“寄存器输出”是一组被全局时钟同步的触发器,而不是随意可控的独立时序单元,这决定了它做不了复杂状态机,却很擅长做总线译码、地址选通、锁存读回这类“信号整形”工作。

3. 在 EDA 工具里描述输出与反馈:方程、真值表和 JEDEC 熔丝

EDA 工具的本质工作,是把你的逻辑描述翻译成一组熔丝位。反馈不是一句“我需要在内部接一根线”就能实现的,你必须通过方程里的“自引用”、状态机里的“当前状态”、或者显式的反馈声明,让编译器知道要把哪个信号引回宏单元。下面用常见的 CUPL 风格语法做示例,换成 ABEL、PALASM 时语法不同,但思想一样。

3.1 用 CUPL 写一个带反馈的寄存器输出:最小可编译示例

以一个带输出使能的双态互换逻辑为例,展示如何同时使用寄存器反馈和组合反相输出。这个例子里,Q 被配置成寄存器输出,并把自己的值反馈回来参与下一拍运算,QB 则是从寄存器 Q 直接取反的组合输出。

/* 22V10 风格宏单元描述,CUPL 语法示例 */ PIN 1 = CLK; /* 全局时钟输入 */ PIN 2 = EN; /* 输出使能控制 */ PIN 14 = A; /* 数据输入 */ PIN 23 = Q; /* 寄存器输出宏单元 */ PIN 22 = QB; /* 组合反相输出宏单元 */ /* Q 为寄存器输出,.D 后缀表示接入 D 触发器输入端 */ Q.D = (Q & !A) # (!Q & A); /* 反馈项 Q 和 !Q 来自内部反馈路径 */ Q.OE = EN; /* 三态使能由 EN 控制,外部电路按 Active Low 使用 */ /* QB 为组合输出,取内部 Q 的反相;引脚电平还要再经过极性熔丝 */ QB = !Q;

逻辑说明:Q.D让编译器把该宏单元置为寄存器模式,方程右侧出现的Q和!Q不消耗外部输入引脚,它们来自宏单元内部的反馈网。QB写成组合输出,从内部 Q 端取数,因此不受引脚极性影响,但最终送到引脚的电平会被极性熔丝再处理一次。参数说明:.D后缀是“数据输入”的标识,不是触发器类型的开关;同一个宏单元如果写Q = ...就成了组合输出。.OE是使能项,省略时宏单元输出被配置为常开。实际项目中我一般会让 Q 和 QB 都配上.OE,避免上电瞬间两路同时驱动总线。

这里有一个非常实用的经验:方程右侧出现的每个反馈项,都会折算成内部逻辑阵列里的一个乘积项。PAL 的每个输出可用乘积项数量有限(常见是 6 到 10 个),所以并不是“外部引脚不够才用反馈”,而是反馈本身也要计入资源预算。写方程前先把右边每一项列出来数一遍,是避免编译失败最便宜的办法。

3.2 用真值表和状态机描述反馈:把“当前状态”交给工具

当反馈被用来实现状态机时,最忌讳的是手写一堆Q.D方程硬凑状态转移。工具提供的高层写法,比如 CUPL 的sequence块、ABEL 的state_diagram,就是专门干这个的。你只需要声明状态变量,编译器会自动完成“读当前状态 → 产生下一个状态 → 反馈回触发器”的全部布线。

/* 模3计数器:状态变量 Q 在反馈路径上被反复读取 */ field Q = [Q1:Q0]; /* 两个触发器拼成一个状态向量,低位在右 */ sequence Q { present 0 next 1; /* 当前状态 0,下一拍进入 1 */ present 1 next 2; present 2 next 0; present 3 next 0; /* 把非法状态拉回已知状态 */ }

逻辑说明:present后面的值,在硬件上就是“反馈路径上读到的当前状态”。编译器会把 Q0、Q1 分别接到对应触发器的反馈输入端,并根据状态转移表生成乘积项。参数说明:field的位序不能写反,低位放右边,否则状态编码和实际硬件映射对不上;present 3 next 0这种非法状态处理不是所有编译器都支持,老工具可能要求你穷举全部状态。写状态机时千万不要以为“状态”存在某个存储区,它在 PAL 里就是你选定的那组触发器,每一拍都从反馈路径读回旧值,计算出新值后再写回,所谓状态机不过就是“反馈加上时序判决”的循环。

还有一个必须留意的参数:状态跳变发生时,新状态要等下一个时钟沿才能被写入触发器,但反馈路径上的旧状态必须稳定保持到写入完成。因此关键路径 = 反馈路径延迟 + 组合判决延迟 + 触发器建立时间。PAL 的 tPD 和 tCO 在数据手册里给的是典型值,温度和工作电压会改变这些值,做总线时序预算时不能按最理想数值算,至少要留 20% 余量。老器件不代表可以不做时序分析,反而因为寄存器少、反馈直接,时序问题更容易暴露。

3.3 从 JEDEC 反推宏单元配置:烧录前最后一道肉眼看图

JEDEC 文件是编程器烧录的标准格式,里面用熔丝位形式记录了整个器件的配置。宏单元配置位的位置,通常能在器件数据手册的“Fuse Map”章节查到。烧录前我会用编程器软件的 fuse view 功能,把 JEDEC 里的几个关键选择位和设计意图对一遍,这一步能拦截掉相当一部分低级错误。

配置位(示例)典型含义烧录前要确认的问题
SYN / 宏单元模式位组合输出还是寄存器输出是否与方程里的.D后缀一致
AC0 / AC1(22V10 系列)反馈来源选择反馈到底来自引脚还是触发器
极性位Active High 或 Active Low方程里的!是否和极性设置重复
OE 选择位常开或受乘积项控制.OE是否真的被综合进熔丝位

检查流程并不复杂:先读 JEDEC 头部记录的器件型号和熔丝总数,确认没拿错型号;再切换到 fuse view,把宏单元配置位逐项对准;最后上板用逻辑分析仪测量关键输出电平。这里最容易栽跟头的是不同编程器软件对“熔丝烧断”的 0/1 显示约定不一致,同一个位置上 A 工具显示烧断为 1,B 工具显示烧断为 0,肉眼看反就全盘皆错。所以不要只看软件的图形界面,必要时把原始 JEDEC 位串导出,按数据手册的位定义手动推一遍关键位置。

提示:JEDEC 里的反馈配置位通常不止一位,实际含义要以器件手册的宏单元结构图为准。不同厂商对 AC0、AC1 这类命名可能略有差异,别拿网上某个片段直接套用。

4. 反馈网络的三个行为边界:组合毛刺、异步循环与总线竞争

反馈在原理图上是一根线,在物理上却是实实在在的“时间差来源”。输入路径、逻辑阵列路径、输出缓冲路径和反馈路径各自的延迟不同,当这些路径汇合到一个节点时,竞争冒险就出现了。下面三个边界是 PAL 反馈网络里工程风险最高的地方,每一个我都见过对应的板级故障。

4.1 组合反馈路径上的毛刺:翻转信号在直通与反馈之间打架

当反馈来自组合输出时,一个输入信号变化会让两条路径同时开始传播:直通路径从输入缓冲直接进阵列,反馈路径则要经过输出缓冲、反馈线,再回到阵列。两条路径的延迟不一样,输出就会在短暂时间内出现多拍翻转,用逻辑分析仪看就是在干净的边沿上多出一串窄脉冲。举一个具体例子:方程Q = A # (!Q & B),当 B 为 1、A 从 1 变 0 时,原先把 Q 拉高的通路被关断,而!Q & B这条反馈通路还没来得及把电平传回,输出就被短暂拉低。

PAL 的组合输出没有触发器可以清洗毛刺,所以这类毛刺会直接出现在引脚上,驱动别的器件时可能造成误触发。治本的办法是改变宏单元模式:把需要被反馈使用的信号放到带触发器的宏单元上输出。寄存器输出天然在下一个时钟沿才变化,反馈路径拿到的是一个已经经过时钟同步的电平,毛刺不会被采样进去。如果设计约束不允许用寄存器,那么组合反馈环路里不要再串其他组合逻辑,让反馈只经过“缓冲 → 阵列 → 输出”最短路径,把延迟差压到最小。加滤波电容属于掩盖症状,不值得作为首选。

4.2 异步反馈循环:仿真时零延迟循环,板子上看是输出“闪烁”

组合反馈一旦形成逻辑环,行为就会变得非常不稳定。典型写法Q = !Q # A在 A 无效时退化成Q = !Q,这时无论仿真还是实际电路都会持续翻转。离散事件仿真器遇到这种结构,会陷入“零延迟反复调度”,表现是仿真器报迭代上限错误,或者波形一直在 0 和 1 之间高频抖动;真实器件上没有零延迟,但环路延迟极短,输出会变成不受控的振荡器,下游电路看到的就是一个诡异的闪烁信号。

PAL 是真的会把这种组合环路直接实现出来的,不像 FPGA 综合工具会报错或帮你做特殊处理。所以仿真模型里必须人为给反馈路径插入一个延迟,比如 1ns,模拟信号在反馈线上的物理传输时间,这样才能看到“带延迟的振荡”而不是仿真器死循环。下面是一个伪代码片段,演示行为仿真里给反馈加延迟的写法。

/* 仿真模型片段:人为在反馈路径上加入延迟,打破零延迟事件环 */ assign fb = #1 Q_wire; /* 反馈路径估算延迟 1ns */ always @(fb or A) Q_wire = !fb & A;

逻辑说明:这段代码的核心不是给设计增加延迟,而是为了让仿真事件循环能收敛。#1表示反馈信号延迟 1ns 后到达,always块每次响应输入变化重新计算输出。参数说明:延迟值取 1ns 只是示例,实际应参考器件数据手册里的反馈路径延迟参数。真在板子上出现这种振荡时,先查方程里有没有“输出出现在自己方程右侧且中间没有寄存器”的项,再考虑把环路打断。判断良性保持环和恶性振荡环有个简单办法:反馈路径上存在奇数个逻辑非,且环路中没有寄存器把信号锁存,那么振荡概率极高。

4.3 双向IO的总线竞争:OE 开着的瞬间,“读回”的是自己

PAL 的 I/O 引脚做反馈时,OE 有效则内部驱动器强力输出,这时反馈读回的大概率是驱动器送出的电平,而不是你期望的外部信号。如果外部器件也在同一时刻驱动这根线,两个驱动器就会对拉,轻则电平不确定,重则超过器件绝对最大额定值造成损伤。这种故障在总线式读回设计中最常见:想读外部状态数据,但忘了先把 PAL 自己的 OE 关掉,或者关断时序比外部器件慢,结果读回的是自己上一拍输出的回环。

解决方式要从时序上下手。常规做法是严格安排使能顺序:先让 PAL 的 OE 失效,让引脚进入高阻,再让外部器件驱动总线;切换之间留出空档时间,不能让两个使能信号同时有效。空档可以用 OE 信号的 RC 延迟产生,也可以用第二个 PAL 输出专门生成使能时序。但如果外部信号本身很忙、边沿很多,我更建议换一种结构:把读回信号从双向 I/O 挪到一个专用输入引脚上,让 OE 控制的输出路径和读回路径彻底分开。这样做的代价是多占一个引脚,换来的是反馈信号干净可靠,不用再操心使能竞争。

提示:把 PAL 的引脚反馈路径复用为输入时,一定要检查该宏单元的 OE 是否被默认综合成“常开”。常开 OE 会让你根本没有办法把引脚切到输入模式,读回自然永远是内部逻辑。

5. PAL 输出反馈的常见问题与排查:仿真对而板子错的四个场景

下面的四个问题,是我在实际调试中遇到最多、且最容易在 PAL 上栽跟头的场景。每一个都按“现象 → 原因 → 解决”展开,新手可以直接对号入座,熟手也可以用来做烧录前的检查清单。

5.1 方程里的“!”和引脚电平对不上:输出反相排查

现象:仿真波形完全符合预期,上板后用万用表或逻辑分析仪测量,发现电平整体反转,有时是部分输出反、部分输出不反。 原因:宏单元的极性熔丝设置与方程里的非号叠加,出现“负负得正”或“正负抵消”。仿真工具通常会显示内部节点的逻辑值,不会模拟输出极性熔丝对引脚电平的影响,所以仿真永远是对的,板子永远是错的。 解决:在编译报告里找到每个宏单元的 Active High / Active Low 标注,逐条与设计意图核对。如果输出要低有效,就把器件极性设为 Active Low,方程里写正逻辑;不要在方程里写!又让极性熔丝也反相,人为制造双重负号。最好在代码注释里把“该引脚期望电平”标明,编译后对着报告检查一遍。

5.2 反馈读回的数值“粘住”:OE 未及时禁用

现象:某个 I/O 引脚作为反馈输入时,读到的值始终不变,或者在特定时刻才变化一次,外部信号的变化完全反映不进来。 原因:OE 一直有效,内部驱动器始终把引脚拉到内部逻辑决定的电平,读回的自然永远是内部状态;或者外部驱动器和内部驱动器同时作用,电平停在中间判读区,被输入缓冲判定为固定值。 解决:检查.OE方程是否真的被综合进了熔丝。最直接的办法是量 OE 引脚的实际时序,然后在软件里把 OE 强制失效,人为给外部引脚加一个已知电平,确认反馈路径能拿到外部值。如果确认代码没问题,再用 fuse view 看 OE 选择位是否被编译器优化成了常开。

5.3 JEDEC 熔丝 0/1 约定不一致:烧出来的片子整体行为反相

现象:同一份 JEDEC 文件,在某个编程器软件里烧录后逻辑全反,在另一个软件里烧录却正常。 原因:不同编程器对熔丝状态的定义不一致。有的软件把“烧断”显示为 1,有的显示为 0;JEDEC 文件里的每一位本身没有绝对含义,全靠编程器按器件定义解释。一旦解释差了一个反相,极性位就反了,整个芯片行为随之反转。 解决:烧录前把 JEDEC 原始位串导出,按器件数据手册的 Fuse Map 手动推算关键位置的状态;烧录后用逻辑分析仪验证关键输出电平,不要只依赖编程器的“校验成功”提示。校检成功只能说明熔丝位和文件一致,不能说明文件本身和设计意图一致。更稳妥的做法是同一片器件分别在两个软件里读回熔丝,对比差异。

5.4 上电后状态机乱飞:寄存器反馈没有可靠的初始化

现象:系统上电后状态机不进入预设的起始状态,有时复位后仍然异常,要断电等几秒再上电才正常,且每次上电初始状态都不同。 原因:PAL 的 D 触发器在失电后并不保证上电为 0,很多 PAL 宏单元没有像 CPLD 那样自带统一的上电复位逻辑。状态变量一上电是随机值,反馈把随机值带回状态机,导致落入非法状态或持续翻转。 解决:给器件配置外部复位电路,让复位引脚在电源稳定后再释放;在状态机里把非法编码显式导向已知状态,比如前面模3计数器里的present 3 next 0;如果关键状态输出在下游会误导通负载,外部加上拉或下拉电阻,把复位释放前的电平固定在安全值。这里要特别注意:不能假设“上电总是从 0 开始”,这是 PAL 时序设计里最昂贵的假设。

6. 更进一步:上板前的手工预检,判断你的输出方程能不能塞进这颗 PAL

最后分享一个我每次写 PAL 之前必做的手工预检:算“乘积项预算”。一个 PAL 宏单元的每个输出,通常有固定数量的乘积项可供实现方程,常见是 6 到 10 个,22V10 这类器件还会对乘积项数量分档。反馈项虽然不占用外部引脚输入,但每条反馈项会消耗一个乘积项额度;当方程右侧成员超过预算时,编译器要么报错,要么自动用“输出反馈”做二次折叠,把一个宏单元当中间节点用,这就会额外占用宏单元和反馈路径,输出延迟随之变大。

预算项示例数值说明
外部输入引脚5不含反馈路径引入的信号
寄存器输出3每个输出占用一个宏单元触发器
每输出乘积项预算8以器件数据手册为准
反馈项占用2Q 和 !Q 各占一个乘积项

具体做法是拿一张纸列出五列:输入数、输出数、触发器数、每输出乘积项数、极性要求。举例:5 个输入、3 个寄存器输出、每个输出 8 个乘积项预算,如果其中一个输出方程右侧展开后有 12 个乘积项,就必须手工化简,把公共项提前提取成中间变量,或者把方程拆到两个宏单元里再组合。这里我犯过最典型的失误,就是只看器件“10 个输出”就以为能写 10 路复杂逻辑,完全忽略了每个输出背后的乘积项预算和反馈路径占用率。写到一半编译报错,想靠换器件解决,结果封装、引脚分布、时序参数全得重来,比一开始就算清楚代价大得多。所以我的习惯是:先算账,再写方程;每编译一次都看报告里的 Macrocell Used 和反馈来源列表,确认编译器没有悄悄把组合输出改成“寄存器 + 反馈”的等价形式,因为一旦出现这种改写,时钟信号就必须接上了,而你的电路板可能根本没给这颗芯片布线时钟。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进