Skip to content

SWB 附录 A:预处理器与引用语法

来源:swb_ug.pdf Appendix A(W-2024.09) 原文标题:Preprocessor and Reference Syntax 说明:本页按 2024 版附录 A 的原顺序整理,重点覆盖 @...@ 引用、树导航、# 命令、split 命令与节点表达式。

本附录目录

附录说明

本附录讨论两类基础能力:

  • @...@ 引用与树导航语法
  • 预处理器命令,也就是所有以 # 开头的控制语句

它和正文里的 Chapter 6、Chapter 12 关系很紧,但职责不同:

  • Chapter 6 更偏“如何在项目中用预处理推进流程”
  • Chapter 12 更偏“层级项目组织下路径如何变化”
  • 附录 A 则更像完整语法参考表

@ 引用与树导航

手册先给出 @ 引用的 EBNF 结构。按原意改写后,可以理解成:

text
reference: simple_reference [ operator [operator] ]

simple_reference:
  "node" | "previous" | file_type ["/i"] | ["/o"] |
  "experiment" | "experiments" | "process_name" | "swb_parameter" |
  parameter_name | variable_name

operator:
  ":" | "|"
  后面可跟:
  +number | -number | tool_label | first | last | index | all | min | max

基本组成

名称含义
file_type工具数据库中定义的某一种文件类型
/i指向当前工具的对应输入文件
/o指向当前工具的对应输出文件
parameter_name已声明参数之一
variable_name已知变量之一
tool_label仿真流程中某个工具实例的标签
experiment返回当前节点所属的第一个实验
experiments返回当前节点所属的全部实验
process_name返回当前节点所属的 process name
swb_parameter返回当前节点的参数名

/i/o 的语义

手册对 file_type 的扩展说明比较关键:

  • file_type/i 指向当前工具的对应输入文件
  • file_type/o 指向当前工具的对应输出文件
  • 如果不写 /i/o,SWB 会沿树向上查找“前一个匹配工具”生成的隐式输入文件

这也是为什么 @tdr@@plot@ 这类引用在不同上下文里可能解析到不同文件。

横向流程方向

手册先解释了横向流程下的导航规则。

在这种方向里:

  • 垂直方向的一个单位表示一个完整仿真阶段
  • 中间节点,也就是虚拟节点或 split point,不计入这个单位

同时,横向与纵向导航运算符是可以组合使用的。

运算符方向含义

运算符 / 写法横向流程中的含义
``
:垂直导航运算符
+number向右的相对引用,或向下的垂直相对引用
-number向左的相对引用,或向上的垂直相对引用
number向右或向下的绝对索引引用
all返回指定水平层上的全部引用
first返回该层最左侧引用
last返回该层最右侧引用
index返回该层的水平索引,而不是节点号;最左节点索引为 1
min返回该层最左节点索引,始终为 1
max返回该层最右节点索引

手册还特别说明:在“垂直运算符”位置,你也可以直接使用工具实例标签 tool_label 作为绝对位置指示器。

横向流程中的求值结果

一个引用最终会返回什么,取决于它引用的是哪一类对象:

  • 若使用 indexminmax 作为水平运算符,返回的是水平节点索引
  • 若引用的是 parameter_name,返回参数值
  • 若引用的是 file_type,返回文件名
  • 若引用的是 tool_label,返回工具标签
  • 若引用的是 nodeprevious,返回节点号

如果一个引用会得到多个值,SWB 返回的是一个以空格分隔的列表。

横向流程中的典型例子

引用含义
@node@当前节点号,也就是当前工具实例输出节点的节点号
@node:all@当前树层上所有节点号列表,也就是当前工具输出节点所在层的全部节点
@node:2@当前树层上第 2 个节点的节点号
@node:+2@当前节点下方两个位置处的节点号,同列
@node:-1@当前节点正上方的节点号,同列
@node:first@当前树层最上方节点的节点号
`@node-1:all@`
`@node+3@`
@node:index@当前节点索引
@node:min@当前层第一个索引,始终为 1
@file_type@第一个前置匹配工具输出的该类型文件
@file_type:all@当前树层上该类型文件的全部文件名
@file_type:3@当前树层第 3 个工具实例生成的该类型文件
@file_type:+1@当前节点下方节点上该类型文件
@file_type:last@当前层最右侧该类型文件
@file_type/i@当前工具该类型输入文件,例如 n5_mesh.tdr
@file_type/o@当前工具该类型输出文件,前提是这个文件类型被声明为输出
`@file_type/o-1@`
@node:max@当前树层最后一个索引,也就是该层节点总数
@tool_label@当前节点对应工具标签
`@tool_labelall@`
`@tool_label1@`
`@tool_label+1@`
`@tool_label-1@`
`@tool_labelfirst@`
`@tool_labellast@`

纵向流程方向

附录 A 接着给出纵向流程下的规则。

在这种方向里:

  • 水平方向的一个单位表示一个完整仿真阶段
  • 中间节点,也就是虚拟节点或 split point,同样不计入这个单位

手册指出,在纵向流程中,“垂直运算符”可使用的附加关键字依然包括 allfirstlastindexminmax,只是方向解释会发生变化。

运算符方向含义

运算符 / 写法纵向流程中的含义
``
:水平导航运算符
+number向下的垂直相对引用,或向右的水平相对引用
-number向上的垂直相对引用,或向左的水平相对引用
number向下或向右的绝对索引引用
all返回指定垂直层上的全部引用
first返回该层最上方引用
last返回该层最下方引用
index返回垂直索引,而不是节点号;最上方节点索引为 1
min返回该层最上方索引,始终为 1
max返回该层最下方索引

手册还说明:在“水平运算符”位置,也可以使用 tool_label 作为绝对位置指示器。

纵向流程中的求值结果

纵向流程下的求值原则与横向流程相同,只是索引方向从“水平”变成“垂直”:

  • 若使用 indexminmax,返回的是垂直索引
  • 参数名返回参数值
  • 文件类型返回文件名
  • 工具标签返回工具标签
  • nodeprevious 返回节点号

如果得到多个值,依然返回空格分隔列表。

纵向流程中的典型例子

引用含义
@node@当前节点号
@node:all@当前树层全部节点号
@node:2@当前树层第 2 个节点号
@node:+2@当前节点右侧两个位置的节点号,同一行
@node:-1@当前节点左侧紧邻节点号
@node:first@当前树层最左侧节点号
`@node-1:all@`
`@node+3@`
@node:index@当前节点索引
@node:min@当前层第一个索引,始终为 1
@file_type@第一个前置匹配工具输出的该类型文件
@file_type:all@当前树层该类型全部文件名
@file_type:3@当前树层第 3 个工具实例的该类型文件
@file_type:+1@当前节点右侧节点上的该类型文件
@file_type:last@当前层最下方该类型文件
@file_type/i@当前工具该类型输入文件
@file_type/o@当前工具该类型输出文件
`@file_type/o-1@`
@node:max@当前树层最后一个索引,也就是当前层节点总数
@tool_label@当前节点工具标签
`@tool_labelall@`
`@tool_label1@`
`@tool_label+1@`
`@tool_label-1@`
`@tool_labelfirst@`
`@tool_labellast@`

返回当前目录的补充引用

手册在这一部分最后补充了两种“不带后缀”的路径引用:

引用含义
@pwd@项目的绝对路径
@pwd@/@file_type@带绝对路径的文件引用

# 命令

附录 A 随后说明所有预处理器命令的共同规则:任何预处理器命令都必须以 # 作为行首第一个字符。初始 # 后面允许有空格或制表符缩进。

手册说明,spp 能识别以下命令。

预处理器命令总表

命令含义
#<string>普通注释。spp 会把所有注释行从预处理结果中去掉,并用空行替换。这里的 <string> 是不属于其他预处理命令的任意字符串。
#define <name> <string>定义新宏 <name>,后续出现的 <name> 会被替换成 <string>
#undef <name>取消之前定义的宏。
#setdep <list of nodes>显式设置这些节点的依赖关系。
#remdep <list of nodes>显式移除这些节点的依赖关系。
#include "<filename>"在当前位置包含指定文件内容。被包含文件会像当前文件的一部分那样被 spp 处理。
#includeext "<filename>"#include 类似,但会对文件名做更高级处理,允许 <filename> 中包含已由 #define 定义的宏,也允许在包含过程中定义新宏。
#if <expression>如果表达式求值非零,则到匹配的 #else#elif#endif 之前的后续行会被保留。表达式必须是标准 Tcl 表达式,并且在求值前会先展开其中的 @ 替换。
#if in <gexpr>#if 类似,但条件改成“当前节点是否属于 gexpr 返回的节点集合”。
#ifdef <name>仅当宏 <name> 之前已通过 #define 定义时,保留后续内容。
#ifndef <name>仅当宏 <name> 之前尚未定义时,保留后续内容。
#elif <expression>可在 #if 和匹配的 #else / #endif 之间出现任意多个。只有在前面 #if 与所有前置 #elif 都为零,而当前表达式非零时,才会保留它后面的内容。
#else反转当前条件段的保留逻辑。如果前面的条件已经成立,则 #else#endif 被忽略;反之则保留。条件块可以嵌套。
#endif结束 #if#ifdef#ifndef 段。
#exit在这一行停止预处理,后续全部内容都被剥离。
#verbatim <string>不剥离该行,只去掉 #verbatim 前缀,后面的内容原样保留。
#rem <string>该行不会被剥离,但行内其余部分仍会进行 @ 替换。
#noexec当前节点不执行,也就是不提交到调度器。
#set <varname> <value>设置变量值,并在 SWB 的 Variable Values 视图中显示该变量。
#seth <varname> <value>设置变量值,但在 Variable Values 视图中隐藏该变量。

条件命令的理解

附录 A 对 #if / #elif / #else 解释得很细,核心逻辑可以概括为:

  1. 先看最外层 #if 是否成立。
  2. 若不成立,按顺序继续判断每个 #elif
  3. 一旦某个 #elif 成立,后面的 #elif#else 都不再生效。
  4. 如果前面都不成立,才轮到 #else

这个规则和传统预处理器很像,但这里的表达式是 Tcl 表达式,而且会先做 @...@ 替换。

split 命令

附录 A 接着讨论 split point 相关命令。

手册先给出一个重要限制:多个 split point 只有在它们的出现顺序与仿真流程中对应参数的顺序一致时才有效。若顺序不一致,预处理器只会采用它认为“最佳匹配”的部分,而忽略某些 split point。

手册还强调:一个与 split 区段对应的“部分输入文件”,在前向引用语义上仍然被视为普通工具输入文件。常见错误是:某个参数引用出现在 split point 之前,但被引用参数在流程里其实位于 split 参数之后。这样很容易触发预处理错误。

split 相关命令

命令含义
#header标记 header 区段起始。header 会在每个 partial input file 的最开头被复制到 load 命令之前。只能定义一个 header。
#endheaderheader 区段结束行,预处理后会替换成空白行。
#postheader定义一个 postheader 区段。它会在每个 partial input file 的 load 命令之后复制进去。这个区段适合放“必须在 load 之后重新设置的仿真器默认项”。可以定义多个 postheader,按原文件中的定义顺序追加;但不允许嵌套或重叠。
#endpostheaderpostheader 区段结束行,预处理后会替换成空白行。
#split @PNAME@在参数 PNAME 上定义一个 split point。当前文件会被切成两段:从上一个 split point 到当前行的一段,以及从当前行到下一个 split point 或文件结束的下一段。当前行会在第一段末尾被替换成 save 命令。第二段文件则以 header、load 命令、可选 postheader,再加上真实 partial input section 的顺序开始。这里的 param_name 指 split 所作用的树层。
#split PNAME#split @PNAME@ 等价,是为兼容旧项目保留的旧语法。新项目里手册明确建议使用 #split @PNAME@

header 与宏定义的注意事项

手册给了一个非常实用的提醒:凡是必须在所有 split 文件中都唯一且共享的 # 预处理器命令,都应该放到 #header ... #endheader 里。

例如,以下宏定义如果放在 header 中:

text
#header
...
#define macro1 string1
#define macro2 string2
#define macro3 string3
...
#endheader

那么所有由 #split 生成的文件都能看到这些宏。

如果不放在 header 里,它们只会出现在第一个拆分文件中,后面生成的拆分文件就不知道这些宏,最终可能导致预处理错误。

节点表达式

附录 A 最后给出节点表达式 GEXPR 的 EBNF。

text
gexpr    : gterm [operator gterm]
operator : "+" | "*" | "-" | "^"
gterm    : scnr["|"level][":{" filter "}"] |
           tool_label[":{" filter "}"] |
           node | "(" gterm ")" | "~" gterm
node     : integer
scnr     : "all" | identifier
level    : integer | "last" | tool
tool     : identifier
filter   : tcl_expr

基本组成

名称含义
scnr场景名
tool_label工具实例标签
node节点号
level树层级,从 0 开始
last最后一层
tool工具标识符
filterTcl 表达式

运算符含义

运算符含义
+“或” / 并集
*“与” / 交集
-差集
^异或
~向根扩展(extend-to-root)的一元运算符

理解方式

从语义上看,节点表达式就是一套“选节点集合”的语言。你可以:

  • 直接选某个场景
  • 选某个工具标签对应的节点
  • 选具体节点号
  • 对节点集合做并、交、差、异或
  • 再叠加过滤条件

它也是 #if in <gexpr> 这类命令的基础。


附录 A 的核心价值,是把正文中分散出现的语法点收成一张完整参考表。以后继续细化时,这页最值得再补的两块是:

  1. 增加“高频引用速查表”,把 @node@@plot@@tdr@@pwd@@pwdout@ 单独做成短表
  2. 补“横向流程 / 纵向流程”对照示意,让 |: 的方向差异更直观

基于 Sentaurus TCAD 官方文档构建

代码块