☰
电路描述语言CDL语法详解与编译器实现:从C17基准电路到网表解析
2026/10/11 16:19:21 网站建设 项目流程

简介:电路描述语言(CDL)是一种用于描述电路结构、连接关系与逻辑功能的专用编程语言,在数字电路测试与仿真场景中常用于结构化建模。这份PPT讲解材料以C17电路为主线,系统梳理CDL的语法规则:描述语句分为函数/连接语句、说明语句和继续语句三类,除第一列外忽略所有空格,标号为不超过8个字符的字符串。内容逐一讲解AND、OR、NAND、NOR、XOR、NOT基本门语句以及FOUT、IN、OUT、VCC、GND、END等函数/连接语句的书写格式,并给出C17电路从原始输入、扇出分支到原始输出的完整CDL描述实例,便于读者对照理解每个语句的用法。资源还介绍了日本奈良大学用C语言编写的CDL编译器,说明编译器如何将CDL文本转换为电路内部结构表,为后续电路仿真与验证提供落地参考。资源包共1个PPT文件,大小约196KB,重点集中、篇幅精炼,适合数字电路学习者、硬件测试人员及相关专业学生快速查阅与入门,目前已有758人学习下载。

1. 先搞懂CDL到底是干什么的:一份PPT讲透的电路描述语言

拿到一份名为“电路描述语言CDL”的PPT,很多人第一反应是“这和Cadence的CDL网表是不是一回事”。先给结论:这份PPT讲的是门级电路描述语言CDL,语法极简,专用于描述C17这类基准电路的教学工具,不是模拟晶体管级网表。它解决的核心问题是,如何用文本把一张原理图的连接关系写清楚,并且交给编译器变成可以直接遍历的数据结构。适合正在做数字电路课程设计、要写网表解析器、或者想从底层看EDA工具如何处理门级网表的工程师和学生。读这份PPT时建议带着一个需求去读:我不光要看懂C17,我还想自己写一个CDL,并且让编译器接受。

2. 语法规则:五个动词就把电路说清楚

2.1 三类语句:第一列位置决定语句身份

CDL语法不像Verilog那样需要begin/end块和关键字包裹,它靠行首字符来区分语句身份。PPT里分得很清楚:第一列为空格的是函数/连接语句,这是绝大多数逻辑描述的主体;第一列为*的是说明语句,相当于注释;第一列为=的是继续语句,用来把一个逻辑行拆成多行写。这条规则第一眼看有点“玄学”,但实际是为了让编译器能用极简的方式做词法判断——只需要扫描第一列字符,就能决定走哪条解析分支。

来看一段示例:

* this is a comment AND / inp1, inp2 / out1 NAND / inp3, out1 / out2 = / inp4 / out3 END

第一行星号开头,编译器整行跳过;第二、三行是真正的函数语句,注意行首有空格;第四行以等号开头,是继续语句。在实际文件里,继续语句的完整效果是把等号后面的内容合并到上一个未闭合的语句上,等号行里不能再出现关键字,只能继续补输入或输出列表。PPT里特意强调,除第一列外,所有空格都会被忽略,也就是说语句内部怎么写空格都不影响结果,这个设计让格式化变得很自由。

继续语句的合并规则有一个容易踩的细节:=后面的内容不是当做一个新的独立语句,而是拼接到上一个还没结束的函数语句上。举个例子,如果有一个FOUT语句要扇出六根分支,一行写不下,可以写成:

FOUT / n1 / f1, f2, f3 = / f4, f5, f6

解析器读到第二行时,不能把它当成一个新关键字为空的语句,而是要把等号后面的部分接到上一行的输出列表末尾。所以写解析器时,需要维护一个“当前未闭合语句”的状态,遇到=开头就追加而不是新建。这里还要注意,注释行不能参与继续语句拼接,=行和*行之间不能互相交叉。

参数上要注意的是,函数语句和继续语句的判定只关心第一列,第一列是空格就按新语句解析,第一列是等号就按续行解析。因此编辑文件时千万不要用全角空格,也不要让编辑器“自动缩进”掉行首的空格。很多初次接触CDL的人会把行首空格删掉,结果所有语句都被当成“未知行”,编译器直接报语法错误。

2.2 函数/连接语句:AND、OR、NAND、FOUT等关键字的格式

函数/连接语句是CDL的核心,PPT给了很具体的格式定义。AND、OR、NAND、NOR是一类,格式都是AND/inp1,inp2,...,inpn/out/,输入至少一个,输出恰好一个;XOR要求输入至少两个;NOT只允许一个输入;FOUT是扇出语句,把一个信号分支成多根线,格式为FOUT/inp/out1,...,outm/;IN和OUT分别声明电路的原始输入和原始输出,格式为IN/线名...,/管脚名.../,如果线名和管脚名完全一致,可以简写成IN/inp1,...,inpk//,注意是两个连续的斜杠结束。VCC和GND用来声明电源和地线,END标志描述结束。

建一张表把这些语句收在一起,方便写解析器时对照:

语句格式约束
AND/OR/NAND/NOR关键字/输入列表/输出输入n>=1,输出1个
XOR关键字/输入列表/输出n>=2
NOTNOT/输入/输出输入1个
FOUTFOUT/输入/输出分支列表分支不能作为另一个FOUT的输入
ININ/线名/管脚名同名可简写,结尾双斜杠
OUTOUT/线名/管脚名情况类似
VCC/GND关键字/线名列表可同时声明多根线
ENDEND结束标记

用一段完整示例说明:

VCC / vdd_line GND / gnd_line IN / a, b / pad_a, pad_b AND / a, b / n1 FOUT / n1 / n1_f1, n1_f2 OUT / n1_f1 / out_pad END

这里AND / a, b / n1表示把a和b相与,结果命名n1;FOUT / n1 / n1_f1,n1_f2表示n1扇出成两根线;输出线n1_f1接入管脚out_pad。值得注意的一点是,FOUT的分支不能是另一个扇出分支的输入,这句话的实际含义是:你只能对FOUT的源信号(比如n1)再做扇出,不能对n1_f1再做一次FOUT,否则会把扇出关系套成网状,破坏编译器里“前驱/后继”的线性结构。

关于分隔符还有一个容易拌脚的地方:语句末尾那个/到底写不写。PPT样例里有些语句行尾带斜杠,有些没有,比如END就没有。写解析器时最好的策略是:先把行尾多余斜杠剥掉,再用split('/')切分主体。如果保留行尾斜杠,AND / a,b / n1 /会切出一个空字符串,处理起来烦但不致命,怕的是空字符串和“省略结尾斜杠”两种格式混在一起,索引就乱套了。我一般统一按“主体部分不用结尾斜杠”来写,遇到原始文件再兼容。

2.3 标号命名与8字符限制:容易忽略的边界

标号就是每个信号线的名字,PPT里给了一条硬性规则:不超过8个字符,且字符串里不能包含空格、逗号和斜杠。为什么是8?看后面的编译器结构图就能找到答案——元素表里name字段定义为char name[9],9个字节为8字符标号加一个字符串结尾符预留位置。这意味着编译器在读取长名字时要么截断、要么报错,不同编译器实现不一样,所以写CDL时不要去碰这个上限。

养成习惯的做法:用“数字+GAT”表示门输出线,用“数字+FAN”表示扇出分支线。比如C17里的1GAT、8FAN,其中GAT来自Gate,FAN来自Fanout。这样命名一方面长度可控,另一方面从名字就能看出信号性质。标号不能重名,原始输入、门输出、扇出分支、原始输出全部共享同一个命名空间,写之前最好先列一遍节点清单。

这里还要提醒一个隐形坑:PPT写“除第一列外,忽略所有空格”,但标号本身不允许空格。分号、方括号这些虽然不在禁止列表里,但为了兼容不同编译器,建议只用字母、数字、下划线组成的标号。我一般把命名约束当成一道“代码规范”来做,任何标号在加入描述前都先跑一个8字符截断检查,否则写完一大份CDL再回来改名字,脖子都要酸掉。

2.4 语句顺序与连接关系的隐式规则

CDL通过标号引用建立连接,语法本身并没有强制要求语句必须按拓扑顺序写。但从C17实例看,FOUT语句总是紧跟源信号产生的语句,NAND语句也会在输入分支可用之后再出现。这带来一个经验:写CDL时最好按信号流向推进,原始输入在前,门逻辑在中间,原始输出殿后。这样编译器生成的前驱后继列表在flist里更集中,人工阅读时也不会出现“20FAN还没定义就被使用”的错觉。

如果你的目标是写一个解析器,建议采用两遍扫描:第一遍收集所有标号、类型和定义位置,第二遍再根据每个语句建立前驱和后继关系。两遍扫描的好处是容忍任意语句顺序,代价是需要额外维护标号到位置的映射。很多课程设计里翻车,不是因为看不懂CDL,而是因为只扫了一遍就想连边,遇到前向引用就崩。CDL是门级描述,理论上所有信号都可以“先用后定义”,所以解析器必须做好这个准备。

3. 用C17电路走一遍完整描述:从原理图到CDL文本

3.1 C17电路的基本结构

C17是ISCAS-85基准电路里的经典小电路,PPT里统计得很清楚:电路中的结点数17,原始输入5,原始输出2,内部门输出4,扇出点3,扇出分支6。这里的“结点”指的是信号线(网),不是物理器件;内部门输出4个对应4个NAND门;扇出点3个对应3GAT、11GAT、16GAT这三个需要分叉的信号;扇出分支6个对应8FAN、9FAN、14FAN、15FAN、20FAN、21FAN这六根分支线。理解这些统计数字,是验证CDL描述是否完整的第一把尺子。

写CDL之前,我会把C17画成一张信号流向图,把输入标号放到左边,输出标号放到右边,然后用箭头表示NAND关系。C17有5个原始输入1GAT,2GAT,3GAT,6GAT,7GAT,2个原始输出22GAT,23GAT,4个门分别是10GAT、11GAT、16GAT、19GAT,但电路实际有6个NAND,因为22GAT和23GAT这两个输出也是NAND门的产物,只是它们同时也是原始输出,没有单独计入PPT那个“内部门输出数4”的统计里。

如果你手里只有CDL文本、没有原理图,可以根据每条语句倒推出电路拓扑。方法很简单:把所有NAND语句的输入标号放到方格左边,输出标号放右边,FOUT语句画成分叉箭头,最后把同名标号合并成一条网线。这个动作做完,C17的结构就完全复原了。C17名字里的“17”指17个节点,但它不是17个门,表示的是线网数量,门只有6个,这个口径别记混。

3.2 逐行解读C17的CDL描述

PPT原文给了一段完整的C17描述,整理后是下面这份,行首统一保留一个空格,避免被当作其他语句:

IN/ 1GAT,2GAT,3GAT,6GAT,7GAT // OUT/ 22GAT,23GAT // FOUT/ 3GAT/8FAN,9FAN / NAND/ 1GAT,8FAN /10GAT NAND/ 9FAN,6GAT /11GAT FOUT/ 11GAT/14FAN,15FAN / NAND/ 2GAT,14FAN /16GAT FOUT/ 16GAT/20FAN,21FAN / NAND/ 15FAN,7GAT /19GAT NAND/ 10GAT,20FAN /22GAT NAND/ 21FAN,19GAT /23GAT END

逐行看。第一行声明五个原始输入,线名和管脚名相同,所以用双斜杠简写。第二行声明两个原始输出。第三行FOUT/3GAT/8FAN,9FAN说明3GAT这根线要分叉成8FAN和9FAN两根分支。接下来的NAND/1GAT,8FAN/10GAT表示第一个NAND门:输入是原始输入1GAT和分支线8FAN,输出10GAT。同理NAND/9FAN,6GAT/11GAT是第二个NAND门,输入9FAN和6GAT,输出11GAT。之后11GAT再次扇出成14FAN、15FAN,16GAT扇出成20FAN、21FAN。到后面NAND/21FAN,19GAT/23GAT,是把21FAN和19GAT做与非,输出23GAT。

这段描述的一个特点是,FOUT语句总是紧跟在产生源信号的语句后面。比如3GAT是原始输入,所以FOUT放在最前面;11GAT是第二个NAND的输出,所以FOUT紧跟其后。这样写的好处是阅读时信号流向非常清楚,人肉检查时不容易漏线。编译器虽然不一定要求严格拓扑序,但按这个顺序写,生成的前驱后继列表也会更集中,对应到PPT里的flist设计能发挥最大作用。

3.3 自己写一份CDL需要注意的连线顺序

把C17的例子吃透后,自己写CDL一般走四步。第一步,给所有信号线编号,原始输入和原始输出先定下来,门输出用GAT命名,扇出分支用FAN命名;第二步,按信号流向从输入到输出写出所有门语句,每产生一个新的GAT信号,就检查它是否需要扇出,需要就马上写FOUT;第三步,把所有用到分支线的门语句补全;第四步,用END结尾,再对照电路图数一遍门数、扇出分支数是否和设计一致。

这里最容易翻车的点是FOUT分支的使用顺序。比如16GAT扇出成20FAN和21FAN后,20FAN被10GAT和20FAN的NAND使用,21FAN被21FAN和19GAT的NAND使用。写语句时如果先写了NAND再写FOUT,逻辑上没错,但手工阅读时容易误以为20FAN还没定义,编不过的时候还得回头检查。另外,每个NAND门只能有一个输出标号,千万别把一个门写成两个输出。C17里四个内部门分别输出10GAT、11GAT、16GAT、19GAT,然后22GAT和23GAT是另外两个NAND门的输出,但它们作为原始输出被OUT语句引用了,所以CDL里没有再为它们单独建中间节点。

还有一个容易被忽略的细节:一个信号线如果既要做扇出源,又要被某个门当作输入,那在CDL里就需要同时出现在FOUT语句的输入位置和NAND语句的输入列表里。比如3GAT既是原始输入,又是FOUT的源,所以它先在IN里出现,又在FOUT开头出现,之后的电路只接触8FAN和9FAN,不再直接接触3GAT。这种“一棵树被扇出后原信号消失”的规则,是CDL和普通网表最大的风格差异。写大电路时记住这一点,就不会在连接关系上迷路。

4. CDL编译器:把文本变成电路表的黑匣子

4.1 编译器的输入输出与处理流程

PPT里介绍的编译器是日本奈良大学提供的,用C语言实现。它做的事情很纯粹:读入CDL文本,检查语法,生成一张电路内部结构表,同时把错误信息和执行报告输出出来。输入文件是*.cdl,输出文件是*.tbl和*.prn,前者存电路表,后者存电路信息与错误信息。从流程图看,编译器有四个数据通道:#1是电路描述输入,#2是电路表输出,#3是错误信息输出,#4是屏幕上的执行报告。

这个设计对做课程设计很有参考价值:它把一个编译器的输入输出拆成“数据结果”和“诊断信息”两个流。*.tbl给后续的测试生成或仿真程序用,*.prn给人类看。如果你的项目要接这个编译器,建议先跑一个最简单的CDL文件,确认三个文件都生成,再开始处理复杂电路。最简单的文件不需要完整电路,一个IN、一个OUT、一个END就够,目的只验证环境链路。

编译器内部流程大致是四步:先做词法分析,按第一列字符区分继续语句和普通语句;再做语法分析,把每个函数语句的输入列表、输出列表拆出来;然后做语义建表,遇到新标号就追加到elmtbl,遇到引用就在flist里登记;最后做统计,把各类型门数量、输入输出数填到全局变量里。这套流程和PPT给的元素表结构一一对应,理解了表结构就理解了编译器。

4.2 电路表结构:ELST、flist、PIOT 如何组织

电路表的核心是元素表ELST,每个信号线在表中占一项。PPT给出的结构体如下:

typedef struct elmtbl { char name[9]; /* 标号名,最长8字符,加结尾符 */ int type; /* 标号类型,见下面的类型编码 */ int nfi, fil; /* 前驱个数,以及前驱位置索引 */ int nfo, fol; /* 后继个数,以及后继位置索引 */ } ELST;

这个结构的关键在fil和fol两个字段。它们不是简单的数组指针,而是一个“间接索引”:如果nfi == 1,fil直接存放唯一前驱在elmtbl数组中的下标;如果nfi > 1,fil存放的是所有前驱在flist数组里的起始下标。nfo和fol对后继同理。原因是每个信号的前驱和后继个数都不固定,用一个统一的flist线性表来存所有连接关系,比在每个元素上挂链表省空间,遍历时也更容易做缓存。读取某条连接边时,只需要从flist[fil]开始连续读nfi个整数就行。

PIOT是输入输出表,PPT里定义为:

typedef struct piotbl { char name[9]; /* 输入或输出管脚名 */ int ptr; /* 在elmtbl中的下标 */ } PIOT;

pitbl和potbl分别对应原始输入和原始输出表。还有一组全局变量:ngnd表示GND信号个数,lgnd在只有一个GND时直接存下标,多个GND时存flist中的起始位置;其他像inpnum、numout、nfout、nnand这些,分别统计输入数、输出数和各类门的数量。这些计数器在验证电路结构时很有用,比如PPT里C17那头就是靠这些数字确认描述无误。

如果要从零实现一个类似编译器,比较推荐的建表顺序是:先扫一遍CDL收集所有标号,确定每个标号在elmtbl中的最终位置;然后第二遍扫描,逐条处理语句,按“输入是前驱、输出是后继”的方向更新nfi、fil、nfo、fol。每次更新时先判断目标前驱数量:如果从0变1,直接写fil字段;如果从1变多,就要把原来存在fil里的下标搬进flist,再把fil改成flist中的新起始位置。这一步最容易写错,因为fil的含义在“单个前驱”和“多个前驱”之间切换了。

4.3 type类型编码表

type字段的数字不是随意定的,PPT给了一套统一编码。对于写编译器的人来说,这是语法到数据结构的映射表:

type含义type含义
0INPUT 输入6VCC 电源
1OR7GND 地
2AND8SRL 锁存器
3FOUT 扇出-1NAND
4OUTPUT 输出-2NOR
5XOR-3NOT

可以看到,基本门AND/OR/XOR用正数,NAND/NOR/NOT用负数。一个直观的理解是,负数代表“带反相的基础门”,这样在遍历电路时可以通过type的正负判断输出极性和门延迟模型,不用额外字段存“是否反相”。SRL被列在表里,说明CDL可以描述带存储的电路,C17只是纯组合逻辑,所以没有用到它。

如果你不打算用这个C编译器,只是想读它产出的tbl文件,可以写一小段Python把结构打出来:

import struct with open("c17.tbl", "rb") as f: data = f.read() # 这里按你收到的编译器实际导出格式解析; # 常见做法是先读元素个数,再逐个读name和type等字段 print("tbl size:", len(data))

不用纠结这段代码能不能直接跑,重点是思路:不同版本tbl的序列化格式不统一,拿到文件后先用十六进制编辑器看一眼头部。比如name[9]项是ASCII还是固定9字节、int是4字节还是2字节,都要以你手上的编译器为准。拿到布局后,主要任务就是把elmtbl里的name、type、nfi/nfo、fil/fol还原成连接关系,再把pitbl/potbl映射到对应标号上。

5. CDL文件落地与避坑:从PPT走向EDA工具

5.1 CDL语言与SPICE网表、Verilog网表的区别

这里必须把概念掰开。业界一提“CDL”,很多做模拟电路的人想到的是Cadence的CDL网表,用来在Virtuoso里导入晶体管级电路,格式类似SPICE。但这份PPT里的CDL是另一码事——它是门级教学用电路描述语言,语法只有AND/NAND/FOUT/IN/OUT等十来条,目标是把C17这样的基准电路用文本描述出来,并编译成内部表。两者名字相同,血缘不同,千万别混用。

维度PPT中的CDLCadence CDL网表Verilog网表
抽象级别门级/逻辑级晶体管级门级/RTL
语法风格斜杠分隔、行首分类类似SPICE的子电路声明模块实例化
典型用途教学、测试向量生成版图前后仿真、LVS逻辑综合、仿真
可读性极简但命名规则强晶体管众多,较难读结构化最好

如果你是在论坛搜“Virtuoso导入cdl文件”,通常指的是Cadence CDL网表,走的是Import→CDL的流程,需要在CIW窗口执行cdlIn之类的命令,具体以你手上的PDK为准。而这份PPT里的CDL没有直接对应的EDA导入流程,它更适合作为学习编译器设计和网表解析的样例。读完PPT你要获得的能力是:看到任意一个CDL描述,能手动画出电路连接表,而不是把它塞进Virtuoso。这种能力在写测试向量生成器、做故障注入脚本时非常实用。

5.2 如何把PPT的CDL知识用到实际工作中

实际工作里最常见的用法是写一个CDL解析器,用来做连通性检查或转成Verilog。我一般用Python大概两百行就能处理完,核心思路是先按行首字符分类,再把每行按斜杠分段。下面是一段核心拆分逻辑:

def parse_cdl(text): stmts = [] for line in text.splitlines(): if not line.strip(): continue if line[0] == '*': continue # 注释行 # 去掉行尾多余斜杠,保留语句主体 body = line.rstrip().rstrip('/') parts = [p.strip() for p in body.split('/')] if line[0] == '=': stmts[-1]['parts'].extend(parts[1:]) # 续行 continue stmts.append({'keyword': parts[0].strip(), 'parts': parts[1:]}) return stmts

这里有几个关键点。line[0]在Python里取字符串第一个字符,正好对应PPT说的“第一列”判定。body去掉行尾的收尾斜杠后,AND / a,b / out /会变成["AND", "a,b", "out"]。FOUT语句的parts长度不定,IN ... //简写会留下一个空字符串,需要用额外判断补齐管脚名。这种解析器做出来后,在真实CDL上跑会立刻暴露出大量语法错误,比如漏斜杠、多逗号、空语句。

有了语句列表后,你可以给每个标号建立节点对象,然后根据keyword在节点间连边。这样CDL就和一张有向图等价了,之后无论转成Verilog还是做扇出统计,都只是遍历这张图的事。常见的做法是把每个NAND语句转成一个Verilog门实例,输入列表映射到实例端口,输出信号接在实例的out端口上。FOUT不做映射,只需要在Verilog里用同名wire连接。

5.3 避坑:解析CDL时最常见的5个问题

整理五条实际踩过的坑,格式按“现象→原因→解决”:

  1. 现象:第一行总是报错,后续行全乱。原因:很多文本编辑器默认把行尾空格去掉,函数语句行首的空格被清掉后,编译器不认为它是合法函数语句。解决:用sed或二进制查看工具确认行首字符。之前我给文件做批量格式化时用sed -i 's/^/ /'给所有行补一个前导空格,但更稳妥的办法是在编辑器里关闭“trim trailing whitespace”,并开启“显示空格”。

  2. 现象:IN/inp1,...,inpk//解析出多个空串,管脚名对不上。原因:简写形式的双斜杠让按/分割时出现一个空元素。解决:在split之前先处理连续斜杠,或者split后过滤掉空串,并记录尾标记决定是否需要复制线名。

  3. 现象:NAND门的输入列表明明有3个信号,编译器只认了2个。原因:标号里不小心带了逗号后面的空格,虽然除第一列外空格被忽略,但逗号前多出来的空格会把标号切成两段。解决:在分词时统一做strip(),并且在写文件时坚持“逗号后不要空格”。

  4. 现象:FOUT分支被另一个FOUT当作输入后,编译器报“非法扇出”。原因:PPT明确说了,FOUT的分支不能作为另一个扇出分支的输入。解决:在解析器里维护一张“已作为分支出现”的集合,遇到FOUT输入时先查这个集合。

  5. 现象:门数量和PPT对不上,C17数出来5个NAND。原因:把22GAT、23GAT也当成门输出数了,而PPT的统计口径只数非原始输出。解决:写检查脚本时用type字段区分OUTPUT和NAND,不要靠标号后缀GAT猜测门角色。

这五条虽然是从小电路踩出来的,但在换到大网表时全是同类问题。解析CDL这种事,语法层面的坑反而是最少的,大部分时间浪费在文本规范和数据统计口径上。遇到奇怪报错,第一反应不要去看门逻辑,先检查文本本身。

6. 验证CDL描述正确性的一个实用技巧:用编译器回环检查

跑完编译器还不够,我最常用的一招是“回环检查”:先让编译器生成*.tbl,再从*.tbl反向生成一份CDL文本,把原始CDL和反向生成的CDL做diff。两份文本在语句顺序、斜杠位置上可以不同,但每条门的输入输出标号必须完全一致。如果编译器不支持反向写回,就写一个小脚本读tbl,按“先输出IN、再按门类型输出逻辑门、最后输出OUT和END”的顺序重建,然后和原始文件比对,比对前先统一去掉所有空格。

在跑编译器之前,我会先用一段快速的静态脚本过一遍,专门抓三类问题:未定义标号、标号重复赋值、FOUT分支被复用。脚本核心逻辑如下:

def check_cdl(stmts): defined = set() used = set() fanout_branches = set() errors = [] for s in stmts: if s['keyword'] == 'IN': for n in s['parts'][0].split(','): defined.add(n) elif s['keyword'] in ('AND', 'NAND', 'OR', 'NOR', 'XOR'): for n in s['parts'][0].split(','): used.add(n) defined.add(s['parts'][1]) elif s['keyword'] == 'FOUT': fanout_branches.update(s['parts'][1].split(',')) defined.add(s['parts'][0]) for n in used: if n not in defined: errors.append("undefined signal: " + n) print("errors:", errors)

这个脚本故意不处理OUT语句,因为OUT里的信号一定在别处已经定义过,如果出现在used集合里,会被最后那个循环抓到。fanout_branches集合目前只用于跟踪,真正确认FOUT分支有没有被错误复用,需要再遍历所有语句的输入列表,把输入列表与fanout_branches求交集。脚本的价值是尽早发现命名层面的低级错误,不用等编译器输出几十行报错才开始排查。

从那以后我每次写完CDL,都强制自己先跑一遍静态脚本,再跑编译器的回环diff,确认无误后才能交给仿真工具。C17这种小电路,手数也能数明白,但换到几十个门的时候,人工检查根本不现实。把这两道检查固化成习惯,省下的纠错时间比写脚本的时间多得多。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询