Skip to content

SWB 附录 H:Tcl 命令块

来源:swb_ug.pdf Appendix H(W-2024.09) 原文标题:Tcl Command Blocks 说明:本页按 2024 版附录 H 的原顺序整理,尽量完整保留 Tcl 命令块的写法、求值机制、适用场景与限制。

本附录目录

附录说明

本附录讨论如何使用 Tcl 命令块。

不过手册一开头就给出非常明确的警告:Tcl 命令块会对项目预处理和执行性能产生负面影响,而且在新的 SWB 项目里通常已经不再需要。因此,手册明确建议不要在新的 Sentaurus Workbench 项目中继续使用 Tcl 命令块。

这意味着,这篇附录更适合在以下场景中查阅:

  • 维护旧项目
  • 理解历史项目为什么这样写
  • 需要兼容旧的 Tcl block 逻辑

创建 Tcl 命令块

手册说明,Tcl 命令块由任意数量的 Tcl 命令行组成,块边界由 !()! 符号限定。

在项目预处理的最后阶段,预处理器会把这些 Tcl 块提取出来并求值。随后,在节点输入文件中,每个 Tcl 块都会被其 Tcl 求值后的标准输出替换。

典型示例

手册用一个 Sentaurus Device 输入文件片段举例,展示 Tcl 命令块如何参与压电 / 自发极化计算,并把结果回写到 SWB 变量中。原文代码很长,这里保留其核心结构:

text
#if @<polarization>@ == "on"
!(
...
set q 1.602e-19
set Psp_AlN ...
set Psp_GaN ...
set Psp_AlGaN ...
set DPsp ...
set e33i ...
set e31i ...
set c13i ...
set c33i ...
set straini ...
set Ppz_AlGaN ...
set DPpz ...
set intCharge ...
set SWB_VARIABLES(Charge) [format %.6e $intCharge]
)!
* Spontaneous polarization for AlGaN: !(puts -nonewline [format %.2e $Psp_AlGaN])!
...
#endif
...
Physics(materialinterface="GaN/AlGaN")
{
#if @<polarization>@ == "on"
Charge( Conc= !(puts -nonewline [format %.4e $intCharge])! )
#endif
...
}

这个例子的核心意思是:

  • 在 Tcl 块里先完成一系列中间量计算
  • 再通过 puts 把需要写回节点输入文件的值输出出来
  • 还可以通过 SWB_VARIABLES(...) 把结果写成 SWB 变量

预处理后的结果

手册接着展示该文件预处理后的节点文件效果。也就是说,原来夹在 Tcl 块里的表达式,最终会被替换成已经求值后的具体数字。例如:

text
* Spontaneous polarization for AlGaN: -5.06e+13
* Piezopolarization for AlGaN: -1.85e+13
* Total AlGaN Polarization: -6.91e+13
* Total GaN Polarization: -1.81e+13
...
Physics(materialinterface="GaN/AlGaN")
{
Charge( Conc=1.3925e+13 )
...
}

没有输出时会发生什么

手册特别提醒:如果一个 Tcl 命令块没有打印任何内容,也就是没有使用 Tcl puts 命令,那么 Tcl 求值的标准输出就是空的。

这意味着在预处理后的节点输入文件里,这个块不会产生任何文本输出。

预处理 Tcl 命令块

手册说明,Sentaurus Workbench 预处理器会在项目预处理的最终阶段自动求值 Tcl 命令块。

它的处理顺序是:

  1. 先完成预处理器 # 命令的求值
  2. 再完成 @ 引用和 @ 表达式的解析
  3. 最后才开始求值 Tcl 命令块

这也是为什么正文和附录 A 一直强调 Tcl blocks 不是“第一阶段语法”,而是最后阶段语法。

实验级求值规则

手册给出一个非常重要的规则:Tcl 命令块遵循“按实验求值”的规则。

也就是说,属于同一 experiment 的各节点输入文件中的 Tcl 块,会在同一个 Tcl 解释器上下文中,从第一个节点一直按顺序求值到最后一个节点。

这样带来的结果是:

  • 如果你在 experiment 的第一个节点 Tcl 块里定义了某个 Tcl 变量
  • 那么同一 experiment 中其他节点的 Tcl 块都可以继续访问它

这也是 Tcl block 能表达“按实验累积状态”的原因。

不能出现在 # 命令中

手册明确指出:Tcl 命令块不能用在预处理器 # 命令中。

原因很简单:Tcl 命令块只会在项目预处理的最终阶段求值,而 # 命令需要更早被求值。因此,如果把 Tcl 块放进 # 命令里,# 命令的求值就会失败。

手册给出的错误用法示例是:

text
#include "!(puts -nonewline "[file join /the/path myfile.cmd]")!"

在这种情况下,手册建议改用 @ 表达式:

text
#include "@[file join /the/ myfile.cmd]@"

显式进行 Tcl 预处理

为了缩短预处理时间,手册说明你也可以显式只做 Tcl 预处理。可用方式包括:

  • 选择 Project > Operations > Preprocess Tcl Blocks
  • Ctrl+B
  • 右键项目后选择 Project > Preprocess Tcl Blocks

在这种模式下,只有工具输入文件中的 Tcl 命令块会被 Tcl 求值;而所有尚未解析的 SWB 参数和表达式(@...@@<...>@@[...]@)都会先被替换成临时值。

手册特别指出:这种“轻量预处理模式”只适合测试用途。若要得到最终正确结果,仍必须执行完整预处理流程。

Tcl 命令块与 SWB 变量

手册接下来说明,Tcl 命令块可以用来创建或更新 Sentaurus Workbench 变量。

从效果上看,它们等价于通过 #set#seth 创建的预处理变量。

要做到这一点,需要在 Tcl 块里更新一个特殊的全局 Tcl 数组:

text
SWB_VARIABLES

基本写法

如果希望让 SWB 预处理器把变量 myvar 初始化成 myval,就需要在 Tcl 命令块中写:

tcl
set SWB_VARIABLES(myvar) "myval"

创建多个变量的示例

手册给出的示例如下:

text
!(
...
set SWB_VARIABLES(var1) 1
set SWB_VARIABLES(var2) 2
set SWB_VARIABLES(var3) 3
...
)

这个例子的结果,就是在 SWB 中创建变量 var1var2var3,其值分别为 123

原文配图如下:

图 82:创建三个变量

图 82:通过 Tcl 命令块创建三个 SWB 变量。

Tcl 命令块中的输入输出操作

附录 H 接着说明:如果在 Tcl 命令块里执行文件写入,必须显式刷新输出流。

也就是说,写文件后要调用 Tcl 的 flush 命令。否则,在节点真正执行之前,这个文件都不会变得可用。

手册示例如下:

tcl
set FID [open "@pwd@/tmp_n@node@_ins.cmd" w]
...
puts $FID "Hello World"
flush $FID
close $FID

这个提醒非常关键,因为很多人会误以为 puts 之后文件已经可读,但在 Tcl block 场景里,如果不 flush,你后续流程里可能根本读不到它。

什么时候适合使用 Tcl 命令块

尽管手册一开始就不推荐在新项目中继续使用 Tcl 块,但它还是列出了两个“确实有用”的典型场景。

场景 1:工具原生语言难以完成的复杂计算

第一类场景是:你需要做一些复杂计算,而这些计算很难直接用工具自身语言实现,但计算结果又需要回灌到工具原生语法中。

手册举的例子是 Sentaurus Device 输入文件中,根据 @Type@ 的不同,动态设置:

  • SIGN
  • HFS1
  • HFS2
  • DG
  • cTemp
  • EQN0
  • EQNS

它的核心逻辑是:

  • @Type@ == "nMOS",就写一套参数和方程组合
  • 否则就写另一套参数和方程组合

这类逻辑如果全用工具原生语法写,往往会更难维护;而放在 Tcl 命令块里,会显得更集中。

场景 2:增强工具原生语言的表达能力

第二类场景是:借助 Tcl 命令块扩展工具语言本身不容易表达的结构。

手册示例是:在 Sentaurus Device 的预处理命令文件里,使用一个 Tcl for 循环,一次性生成 100 段具有不同浓度的 Physics 段落。

也就是类似这样的结构:

text
!(
for {set i 0} {$i < 100} {incr i} {
puts "Physics (materialInterface=\"Silicon/Oxide\")"
puts "Charge(Conc=[expr 6.0e11 + $i*1e11])"
}
)!

手册把它视为“用 Tcl 模拟 for-loop 扩展工具原生语言能力”的典型例子。

对 Tcl-aware 工具的提醒

手册还有一个很重要的说明:对于像 Sentaurus Process 这样本身就支持 Tcl 的工具,虽然在语法上允许使用 Tcl 命令块,但这样做并没有额外好处。

相反,它可能导致混淆,因为:

  • Tcl 命令块
  • 工具原生 Tcl 流程

这两者实际上是在不同作用域中求值的。

此外,这种写法还会降低工具命令文件的可读性。

Tcl 命令块使用规则汇总

附录 H 最后给出规则汇总。按原文意思整理如下:

  1. Tcl 块是在一对 !()! 之间放置任意数量的原生 Tcl 指令。
  2. Tcl 块不能嵌套。
  3. Tcl 块可以插入任何预处理文件中的任意位置,例如工具命令文件和 Sentaurus Device 的参数文件。
  4. 每个文件中都可以包含任意数量的 Tcl 块。
  5. 所有 Tcl 块都由一个单独的 Tcl 解释器求值,并且这个解释器是“每个 experiment 独有”的。
  6. 所有 Tcl 块都会按工具从左到右、按每个文件从头到尾的顺序被求值。
  7. 一个 Tcl 块可以引用前面块中设置的 Tcl 变量,而前面的块既可以在同一文件里,也可以在同一 experiment 的另一个文件里。
  8. Sentaurus Workbench 会用 Tcl 块的标准输出替换掉该块本身。因此,如果你想提取 Tcl 变量 aaa 的值,就必须显式写 puts $aaa。如果 Tcl 块本身没有任何输出,那么预处理文件中也不会出现任何文本;但该块里定义的 Tcl 变量和过程仍然会保留在当前 experiment 的解释器中,供后续使用。
  9. SWB 预处理器会在标准的 #... 预处理和 @...@@<...>@@[...]@ 表达式解析之后,才统一求值所有 Tcl 块。因此,你可以在 Tcl 块里使用这些标准预处理指令;但不能反过来在 # 命令和 @ 表达式中使用 Tcl 块。

附录 H 的核心价值,不是鼓励继续写 Tcl block,而是帮你在接手旧项目时看懂三件事:

  1. Tcl block 什么时候求值
  2. 为什么它能跨节点共享 experiment 级 Tcl 变量
  3. 为什么它常常让预处理更慢、更难读、也更难调试

基于 Sentaurus TCAD 官方文档构建

代码块