☰
CDL网表解析与Virtuoso导入LVS对比实战指南
2026/10/29 8:31:46 网站建设 项目流程

简介:这套PPT围绕电路描述语言CDL展开,面向数字电路设计、测试与仿真验证学习者,系统讲解如何用CDL描述电路的结构、连接与逻辑功能。内容覆盖CDL语法规则、函数/连接语句、说明语句与继续语句,并逐一列出AND、OR、NAND、NOR、XOR、NOT、FOUT、IN、OUT、VCC、GND及END语句的用法;同时以C17电路为例给出完整描述实例,便于对照理解。PPT还介绍了奈良大学用C语言编写的CDL编译器,展示其从电路描述到内部结构表的编译流程、电路表元素结构及前驱后继扩展表等实现细节,适合作为课程报告、实验预习或自学入门材料。资源为单个PPT压缩包,文件总数为1,类型为PPT,包体大小约196KB,轻量易用。目前已有758人学习下载,内容既有语法讲解又有实例和工具机制说明,可帮助快速掌握CDL描述方法,并迁移到常见数字电路的建模与验证中。

1. 电路描述语言 CDL(PPT):一份连版图工程师都当词典用的网表

做模拟版图或者数字后端验证的工程师,几乎每天都要跟一个带.cdl后缀的文本文件打交道。这张 PPT 如果只讲一句话,那就是:电路描述语言 CDL 不是“可以写循环的编程语言”,而是专门描述电路连接关系的结构化网表。它把原理图里画出来的器件、引脚、连线全部转成纯文本,让 Virtuoso 导入 CDL 文件之后,版图和原理图能逐条对比,也就是跑 LVS 的基础。适合谁用?画版图的、做验证的、以及正在接手一个没有原理图只有 CDL 网表的物理设计项目的人。要理解它,不用谈抽象建模,直接打开一个真实 CDL 文件逐行看最快。

2. CDL 文件的基本结构:从 .SUBCKT 到 .ENDS,每一行都在回答一个问题

CDL 作为网表,内容密度很高。大多数人第一次看 CDL 的感觉是“所有字都认识,连起来不知道在说什么”,因为它不像 Verilog 有assign、always这种语义明显的句子,而是靠固定字段顺序承载含义。要把 CDL 用熟,先要把这些字段顺序刻进脑子里。

2.1 器件声明行的字段顺序:为什么第 4 个字段决定了连接关系

CDL 里最常见的器件行是 MOS 管,开头是字母M,紧接着是器件名,然后依次是漏、栅、源、衬底四个连接端,最后是模型名和尺寸参数。一个典型的 NMOS 行长这样:

M0 n1 n2 gnd! gnd! nmos4 W=1u L=180n M=1

这一行的意思是:器件名叫M0,漏极接到节点n1,栅极接n2,源极和衬底都接全局地gnd!,用的模型是nmos4,沟道宽度 1 微米、长度 180 纳米、并联个数是 1。注意第五个字段即衬底节点:对 MOS 管而言,CDL 里主动画衬底连接没有任何伏笔,这个节点在版图 LVS 时就是用来判定体区接法的关键。如果衬底和源没接同一个节点,LVS 会报 common potential 或者 isolation 错误,这是后面踩坑章节第一个要说的故事。

双极管和电阻电容的字段也遵循同样逻辑。用Q开头的三极管行,顺序是集电极、基极、发射极、模型名;用R开头的电阻行是正端、负端、模型名、阻值;用C开头的是正端、负端、模型名、容值。一个容易被新手误用的细节是:电阻的阻值字段写法直接决定了 Spice 仿真和 LVS 结果的量纲,比如R=2k是 2 千欧,而R=2K在某些 CDL 解释器里是“2”而不是“2 千”,因为后缀大小写含义不同。见过不止一个工程师因为k和K的写法问题,在寄生参数提取后看到离谱的电流值。我一般建议:写标准格式R=2e3,彻底避开这个问题。

2.2 子电路、全局节点和模型:层次化网表的骨架

CDL 文件里,单个顶层电路通常被包在.SUBCKT和.ENDS之间。.SUBCKT后面的第一个字符串是子电路名,再往后是外部引脚列表。在子电路内部,如果引用另一个子电路,用X开头命名,比如X1 n1 n2 vdd! gnd! INV就表示实例X1是一个名为INV的反相器子电路。LVS 工具对比层次化网表时,会先展开这类引用,或者保持层次按顶层对比,所以是否展开子电路必须在规则文件里明确写,不能靠猜。

全局节点在 CDL 里用.GLOBAL声明,声明后的节点即使不在.SUBCKT引脚表里也会被子电路内部识别。标准工艺库里常见的写法是.GLOBAL gnd! vdd!。这里有一个非常隐蔽的坑:.GLOBAL声明是全局生效的,如果两个不同电压域的地信号都叫gnd!,那它们就物理短接了;如果工艺库把数字地写成gnd!、模拟地写成avss,而 CDL 里avss恰好忘了加进.GLOBAL,那么所有引用avss的器件在顶层 LVS 时会报 floating 节点。节点命名的“统一”也是 CDL 使用里最反复被验证的一条铁律。

模型定义部分用.MODEL开头,后面跟模型名、类型关键字和参数。模型名必须和器件行的第 5 个字段对应。如果 CDL 里器件写的是nmos4,但.MODEL定义的名字是nch,LVS 规则中的 device mapping 文件又没做映射,结果就是工具提示model not found。补充一个经验:标准工艺厂给的 PDK 里,CDL 一般不是一个完整独立文件,而是引用cdl/目录下多个.scs片段,所以做前端仿真拿到的模型和后端 LVS 用到的网表经常来自不同来源,对比之前先确认模型引用路径是同一套,这比调任何参数都重要。

3. 从 Virtuoso 导出 CDL:前期设置和选项,决定拿到的是“能用”还是“折磨人”的网表

很多人以为导出 CDL 就是点一下菜单,其实大半的 CDL 坑都是在导出环节埋下的。Virtuoso 的 CDL 导出走的是File -> Export -> CDL,或者用命令行cdlOut。工具会读取原理图视图的连通性,按选项生成网表。导出之前必须先确认三个东西:原理图里每个器件是否有model属性、每个引脚是否标注了方向、每个管子的w和l是否写在属性里。这三个缺一个,导出的结果就是“缺胳膊少腿”的网表,直接拿去 LVS 必然报一堆错误。

3.1 导出前必调的选项:scale、pin order、include model

在导出界面的选项框里,有几个参数会影响网表的最终形式。

一个是Scale。这个值是长度单位换算系数,常见工艺库默认单位是米,但 CDL 文本里写的 W 和 L 往往不带单位后缀。如果 schematic 里尺寸单位是微米,导出时把scale设为1e-6,那么生成的网表条目会写成不含单位的数字,例如W=1表示 1 微米。如果这里设成1,那么网表里可能直接出现W=1e-6这种写法。这两种写法在 Spice 语义里都合法,但 LVS 工具与版图提取网表做尺寸对比时,比较的是“数值乘以 scale 之后的结果”,所以scale必须和版图规则文件里的单位假设一致。最常见的悲剧就是把scale忘了设,导致导出的 W 值要么是实际值的 1 万倍,要么是 1 万分之一,最后 LVS 报几百个尺寸失配,还以为是器件画错了。

另一个关键项是Output Pin Order。CDL 导出时有两种引脚排序方式:按原理图引脚实际位置排序,或者按字母排序。字母排序会让同一个 cell 在不同层次里看起来非常齐整,方便人眼比对;但是 LVS 做器件连接对比时,如果网表引脚顺序和版图的引脚顺序不一致,工具会报connectivity mismatch。所以正确的做法是:把 pin order 设为“与 schematic 视图的 pin 顺序一致”,导出前打开 schematic 的Edit Pin确认顺序,再导出,避免绕弯。

Include Model选项决定导出的 CDL 文件里是否带上.MODEL语句。如果 PDK 单独提供了标准模型文件,导出时通常不勾选,而使用Include Library Model File指定.scs模型文件路径。两种做法都可以,但如果选了 include 而又不指定路径,工具会把模型写成空壳.MODEL nmos4 nmos,此时模型参数的缺失会让后续 LVS 无法正确判断器件极性。建议:导出时不 include,模型统一由 LVS 规则文件管理,这样后面换工艺版本时只需要改一处引用。

3.2 手写生成 CDL 的兜底方法:从零写一个最小子电路

有些场景下原理图可能不存在,比如拿到的上游数据只有版图 gds,或者网表本来就是用脚本拼出来的。这时可以用文本编辑器直接构造 CDL。一个最小的反相器子电路如下:

.SUBCKT INV A Y vdd! gnd! M1 Y A vdd! vdd! pmos4 W=2u L=180n M0 Y A gnd! gnd! nmos4 W=1u L=180n .ENDS .GLOBAL gnd! vdd!

这段结构对应的连接关系就是 PMOS 的源和衬底接到vdd!,漏接到输出Y,栅接输入A;NMOS 的源和衬底接gnd!,漏也接Y。用这种方式手写网表时,最大的风险是尺寸单位不写清楚。建议在文件最前面加一行注释说明单位,比如* Units: scale=1e-6,然后用.SCALE 1e-6这种显式声明定义整个文件的长度系数。.SCALE是 Spice 的合法控制语句,它会作用于后续所有未带显式单位的尺寸,相当于给整个文件加了一个全局乘数,比每个器件后面逐个换算单位可靠得多。

手写 CDL 还有一个容易忽略的问题:节点命名不能以数字开头,也不能包含非法符号。工艺库里常用vdd!、gnd!这种带感叹号的写法,因为它们是 Cadence 的“全局信号”约定。如果手写的网表里写成vdd而不是vdd!,导入 Virtuoso 做 LVS 时常常会被识别成普通内部节点,导致顶层存在未连接的电源引脚。使用什么命名体系,要和 LVS 规则文件里定义的名字完全一致,这是手写网表的入场券。

4. Virtuoso 导入 CDL 文件:两种路径、映射配置与第一轮 LVS 执行

热词里“virtuoso导入cdl文件”是大家最高频碰到的问题。常见做法分两种:一种是把 CDL 当作网表源参与 LVS 对比,不改变现有设计库;另一种是真正导入成 schematic view,让版图工程师能直接看到画出来的电路。这两种路径的配置思路完全不同,先都讲清楚。

4.1 用 CDL 直接进入 LVS:Calibre / Assura 的网表输入配置

做 LVS 时,CDL 文件和版图提取网表不是对等概念。版图侧通过 rule deck 中的LVS EXTRACT从 gds 提取出连接关系,而 CDL 作为原理图侧输入,会经过另一个解析器。在 Calibre 的 rule file 里,典型配置是下面两行:

LAYOUT PRIMARY 芯片顶层cell名 SOURCE PRIMARY 芯片顶层cell名

以及网表文件输入设置:

LVS SOURCE CDL 网表.cdl LVS LOCK TRUE

这里最需要用户关心的不是命令本身,而是LVS SOURCE这一行的读取方式。Calibre 默认用 Spice 语法解析 CDL,所以文件里的.SUBCKT、.MODEL、.GLOBAL都会按 Spice 语义理解。为了提升对比精度,一般还把 CDL 里不需要参与对比的“测试用节点”过滤掉,对应下面这类声明:

LVS FILTER VDD VSS 0

如果规则文件里这个过滤条件写错,比如把vdd!当普通节点过滤掉了,LVS 结果里会出现“电源全部浮空”的诡异现象。所以我常建议:第一次跑某个新 CDL 时,先不加 filter,看完整报告,确认节点连接关系正常后再加过滤,避免黑匣子里藏问题。别人给的规则文件只能当模板,里面的过滤条件要按照你的实际情况重写。

4.2 把 CDL 导入成 schematic view:CDL In 的操作流程

另一种路线是用File -> Import -> CDL,把 CDL 转成原理图视图。这个功能大多数人只在流程搭建时用一次,但配置错了往往让人摸不着头脑。导入界面需要指定三个东西:工艺库 mapping 文件、输出库名、导入时用到的 device mapping。

工艺库 mapping 文件的作用是把 CDL 里的模型名映射到 Virtuoso 原理图里的器件视图名。比如:

nmos4 nch pmos4 pch

这行映射表示:网表里所有nmos4模型名都被替换成原理图器件nch。如果没有这份映射,工具会按默认规则生成缺少 model 属性、只有形状没有参数的通用符号,随后你要手动给每个器件补参数,基本等于重画一遍原理图,翻车概率极高。Device mapping 和 LVS 的 device map 不是一回事,前者针对“网表符号转原理图符号”,后者针对“识别器件极性”,在导入时千万别填错地方。

导入后生成的原理图通常会自动给每个实例起名,但实例名和连接关系保留下来了。用Check and Save检查一遍,再通过Create Symbol生成顶层符号,就可以拿来当普通原理图做仿真或对比。这一步有几个常见问题:电阻不能自动识别正负极性、感应器件的 BULK 端可能悬空、子电路引脚的顺序可能被重新清洗。我的习惯是导入后先做一次Extract,然后把版图和这个导入后的原理图跑一遍 LVS,中间通过则是导入配置正确性的证明。

5. Virtuoso 导入 CDL 与 LVS 对比的 5 个高频坑:这些现象、原因和解决方式

这个章节写的是我在多个项目里真正撞过的坑。每条都按“现象 -> 原因 -> 解决”来写,方便你遇到问题的时候直接对号入座。很多坑的根子不在 CDL 文件本身,而是“工具对 CDL 的解释方式”和“你预期它解释的方式”不一样。

坑 1:LVS 报几百个 pin mismatch,看起来是网表和版图各说各话
  • 现象:第一次跑 LVS,结果不是几百个 short,而是一大片pin order mismatch,几乎每个 cell 都中招。
  • 原因:CDL 导出时 pin order 设成按字母排序,而版图里的 pin 顺序是画版图时按视觉习惯排的。工具对比时用的是字符串匹配,顺序不一致就会认为“同名 pin 不在同一个位置”。
  • 解决:回到 Virtuoso,确认原理图的引脚排列顺序,然后在导出 CDL 时把 pin order 设为“按照 schematic 的顺序”,重新导出。已经导出的 CDL 也可以用脚本按顶层引脚顺序重排,但重排前必须先拿到一份权威的引脚顺序表,不能凭记忆。我的习惯是直接在 Virtuoso 里双击 pin 查看顺序,而不是看文件,因为文件里不显示画布上的视觉顺序。
坑 2:器件的 W 和 L 核对结果差 3~4 个数量级
  • 现象:LVS 报告里所有 MOS 管的尺寸全部失配,版图侧提取到的是 1.8u,CDL 里写的是 1.8e-6,数值上差了 10000 倍。
  • 原因:导出 CDL 时 Scale 参数设置成 1,而版图侧提取网表把尺寸换算到了微米;或者反过来,版图的单位基数是微米,CDL 里却是未缩放的米。两边量纲不同,直接对比必然全灭。
  • 解决:打开 CDL 文件头部,看有没有.SCALE语句,再确认版图提取网表的单位设置。让 CDL 顶层尺寸的物理量纲和版图侧一致:要么 CDL 里写W=1.8u并加.SCALE 1e-6,要么版图侧 rule file 里配置MASK UNIT MICRON。配置完成之后,LVS 的尺寸结果单位统一到微米,再跑一次。
坑 3:导入 CDL 生成的原理图里,模型名全部变成@@nmos4一类
  • 现象:用 Import CDL 生成的 schematic 里,MOS 管符号旁边没有模型名,属性栏里显示一个带@@的奇怪字符串;这类元件拿去仿真直接报“unknown model”。
  • 原因:导入时没有提供可用的 model mapping 文件,工具把网表里的模型名当成了任意字符串,而不是映射到原理图库里的模型视图。带@@前缀通常表示“映射失败后保留的原始名”。
  • 解决:先创建一份完整的 mapping 文件,把 CDL 里出现的模型名逐一和 PDK 原理图库里的视图名对齐。一个快速检查方法是:用文本编辑器打开 CDL 找出一行的模型名,然后在 Virtuoso 的 Library Manager 里搜索同名 cell。如果搜不到,必然是这个模型不在当前 attach 的工艺库路径里,得先修库路径引用。映射表准备好后再重新导入,生成的器件就有正确的模型属性。
坑 4:子电路引用的X器件报subckt not found
  • 现象:CDL 里明明写X1 a b vdd! gnd! INV,LVS 却报can't find subckt INV;搜索网表文件,.SUBCKT INV就在另一个文件里。
  • 原因:CDL 支持多文件网表,导出时如果子电路定义在底层库文件里,顶层导出不会自动把底层.SUBCKT合并进来。LVS 解析器只读到了引用,没有读到定义。
  • 解决:确认顶层导出时是否勾选了Include Subckt,或者直接把所有用到的.SUBCKT定义合并到一个 netlist 文件里。合并时注意子电路嵌套:如果INV内部还引用了别的X器件,那个子电路也必须被包含。可以用一个简单命令做合并,比如把同目录下所有.scs文件按依赖顺序拼接。但手动拼接之前先看清文件头,有些 PDK 的底层文件自带.include "其他文件",带上这个路径一起拼才完整。
坑 5:全局节点vdd!在 LVS 报告里变成浮空或短路
  • 现象:CDL 里明明所有管子都接了vdd!,但 LVS 报告说 100 个节点浮空,更离谱的是顶层电源网络被短路到地。
  • 原因:.GLOBAL vdd! gnd!语句写在子电路定义之后或根本没写。Spice 解析规则是.GLOBAL声明只作用于声明之后的子电路;声明放在文件最后,等于前面的所有内容都没有全局电源。至于短路到地,很可能是 CDL 里既有vdd!又有VDD,大小写被当作不同节点,而版图里只有vdd!,于是一堆本该接电源的管子被工具解释成接了一个悬空节点,在某种规则下又与地短接。
  • 解决:把.GLOBAL写在 CDL 文件的第 1 个子电路之前;全文件统一电源节点名,比如统一用vdd!而不是VDD。如果规则文件里对大小写不敏感,也一定在规则里显式声明LVS CASE SENSITIVITY EXACT或LVS CASE SENSITIVITY INEXACT,让工具的行为确定下来。每次拿到新 CDL 都先 grep 一下.GLOBAL那一行的位置,这是我最先做的事,能省掉后面所有电源相关的“玄学”排查。

6. 用脚本给自己留一张 CDL 的“后悔药”:pin 顺序一致性自动核查

最后一节给一个我能直接复用的进阶技巧:在导出 CDL 或导入 CDL 之前,先跑一个简单的 Python 脚本核查 pin 列表,防止因为引脚顺序错误浪费一整轮 LVS。核心做法是把 CDL 中.SUBCKT行提取出来,与一个参考文件里的引脚顺序做差集,输出差异。这个脚本不用很复杂,能在终端里跑起来就行。

import os import sys def extract_subckt_pins(cdl_path): result = {} with open(cdl_path, 'r', errors='ignore') as f: lines = f.readlines() i = 0 while i < len(lines): line = lines[i].strip() if line.lower().startswith('.subckt'): parts = line.split() if len(parts) >= 2: cell = parts[1] pins = parts[2:] result[cell] = pins i += 1 return result def main(): cdl_file = sys.argv[1] ref_file = sys.argv[2] cdl_pins = extract_subckt_pins(cdl_file) with open(ref_file, 'r') as f: ref_pins = {line.split()[0]: line.split()[1:] for line in f if line.strip()} for cell in cdl_pins: if cell in ref_pins: if cdl_pins[cell] != ref_pins[cell]: print(f"mismatch {cell}: CDL {cdl_pins[cell]} vs REF {ref_pins[cell]}") else: print(f"cell not in ref {cell}") if __name__ == "__main__": main()

脚本逻辑很简单:第一个参数传 CDL 文件路径,第二个参数传参考文件路径;参考文件每一行是“cell名 + 引脚顺序”。通过把提取出的引脚列表和参考文件做逐项比较,就能在 LVS 之前发现引脚顺序或命名变更。这个做法的价值不是替你做判断,而是把“人眼盯文件”的重复劳动改成机器检查。实际操作时,我会把参考文件直接定为签核之前的权威网表快照,每次 PDK 更新或者网表重新生成后,都先跑一次这段脚本,确认 pin 列表没有意外漂移,然后再进 LVS。这个习惯帮我挡下过至少两次“换了工艺 PDK 后各个 site 的 cell 引脚排序悄悄被 reset”的问题,属于用十分钟脚本避免一整周返工的典型投资。

脚本跑完还有一个动作要做:对比.GLOBAL和模型引用。我刚才用的提取函数只针对.SUBCKT,但更完整的“CDL 体检”还应该检查第文件头部的.SCALE是否和规则文件一致、.MODEL名是否在工艺库映射表中存在。这类检查写成断言放进 CI 里也行,在本地跑也可以,核心目的一样:把 CDL 文件的格式风险在进入 Virtuoso 导入流程之前尽量消掉。

最后补一句个人习惯:不管脚本多完善,拿到任何一份 CDL,我都会先打开文件头看 20 行再决定下一步。因为很多手上项目的问题答案就写在那里,看别人给的配置不如自己把文件头读懂。希望这个方向的内容能帮你在 CDL 上少走几段弯路。

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

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

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

立即咨询