design compile 介绍

dc 查看命令帮助 ( 使用 man )

man

1
2
3
man cmd
e.g 
man report_area

-help

1
set_min_library -help

help

1
help *clock

列出所有以clock 结尾的命令

Design Compile (dc)常用命令

DC 综合基本步骤

DC 8类对象

dc 基础命令

获取对象的命令

  • get_ports
  • get_pins
  • get_designs
  • get_cells
  • get_nets
  • get_clocks
  • all_inputs
  • all_outputs
  • all_clocks
  • all_registers

analyze

把文件读入工程
analyze命令将设计转换成中间格式 *.mr,/.syn,/.st
读入过程中 analyze 会对输入文件进行语法错误及逻辑可综合性检查
若有错误则会有相应提示

CE33E74246F2467CE2227702ADFEE3C4.jpg 如 analyze -f verilog {top.v a.v b.v} 默认情况下花括号内的第一个文件将被视为顶层

elaborate

elaborate命令先检查analyze生成的中间格式,再将对象
转成内部数据格式*.db
通常和 analyze 成对使用

elaborate 后面加上一个模块的名字 则该模块被设置成顶层如:
elaborate b

  • parameter
    parameter 参数可以改变顶层中的参数 这是唯一一种通过命令行在读入design 的时候改变模块参数的方法如
    elaborate top -parameter “WIDTH=4”

read

read命令和analyze与elaborate命令的不同是read命令
不进行语法检测只作格式翻译与优化

compile_ultra

高级的编译命令

  • retime
    这个选项可以让工具在优化的时候自由组合移动某些逻辑以达到优化timing 的作用
  • incremental
    增量编译

report_constraint

  • all_violators
    可以输出所有的时序违例

report_timing

输出时序路径的细节

  • -delay max/min
    max 输出 setup time min 输出 hold time
  • -from
    起点
  • -to
    终点
  • -through
    经过哪些 pin port
  • -group
    只输出该 group 的信息
  • -input_pins
    只输出输入引脚相关的信息
  • -max_paths
    指定只输出多少条最坏的时序路径信息
  • -nworst
    输出该端点上的最坏的几条时序路径信息
  • -nets
    输出 fanout 信息
  • -capacitance
    输出等效电容信息
  • -significant_digits
    设置输出精度
  • 下图是 路径的Delay Time

    如图 Incr 表示单个Cell 的时间延时 Path 表示整条路径上的延时 f 表示下降沿(falling)传输 r 表示上升沿(raising)传输 带*号的是 Net + Cell 的延时之和
  • 下图是路径的 Required Time

    其中 Clock uncertainty 即是 skew 时钟歪斜 最终要判断路径是否 meet 就是Slack = Data requried time - Data arrival time 如果为正则是 meet

report_area

报告面积 一般用于评估面积

report_port -verbose

get_cells

用法参考 man get_cells

get_clocks

用法参考 man get_clocks

get_designs

用法参考 man get_designs

get_libs

用法参考 man get_libs

get_lib_cells

用法参考 man get_libs_cells

get_nets

get_nets * 将列出所有nets

get_pins

get_pins * 将列出所有pins, get_pins 的正则不好用, 为了能快速的获得结果, 可以在综合前保存的网表文件中搜索到相关的寄存器, 然后直接写出路径

如上图所示, 要获得lead_ad_ck 的话可以使用 get_pins get_lead_inst/clk_lead/dac_clk_reg/Q

get_ports

get_ports 只能获取顶层的端口, 子模块的端口需要用get_pins 来获取

write

保存设计文件

保存所有设计约束和环境属性

1
dc_shell-t> write_script –output scripts/pc_w.tcl

保存用于反标注(back-annotation)的标准延时文件 (sdf => standard delay file)

1
dc_shell-t> write_sdf scripts/PRGRM_CNT_TOP.sdf

将整个工程保存为 ddc 格式

1
    write -format ddc -hier -output unmapped/MY_TOP.ddc

将整个工程保存为 verilog 格式

1
    write -format verilog -hier -output unmapped/MY_TOP.v

如果只是想保存子模块的 ddc:

1
    write -format ddc MY_A -out MY_A.ddc

如果只是想保存子模块的 verilog:

1
    write -format verilog MY_A -out MY_A.verilog

ddc 格式是最全的格式 它会保存 design netlist, constraints and attributes

dc 库文件指定命令

指定元件库

set_app_var target_library .db
晶圆厂的器件库

指定符号库

set_app_var symbol_library .sdb
符号库主要用于显示原理图

指定连接库

set_app_var link_library “* xxx.db” 把目标库放入内存

工作库指定命令

  • define_design_lib ADD –path ./xxx

dc 优化选项

  • Pipeline
    可以让工具在优化的时候根据需要插入流水线
    1
    
          set optimize registers true -design Pipeline
    

    可以看到被加的流水线都是以 Sx 结尾命名的
    流水线出来的结果如果要往外送的话 要用寄存器寄存一下再送出去

DC TCL 约束相关

create_clock

获取端口 Clk 并设成周期为 2ns 的时钟 []内的不是必需的 -waveform 选项第一个参数表示在第 x ns时钟上升
在第 y ns 时钟下降以此来确定占空比 占空比默认 50%
create_clock [-name xxx] -period 2 [-waveform {x y}] [get_ports Clk]

group_path

给路径分组

由于综合工具如果发现同一组路径中最差的路径没办法优化到 meet 的话就会放弃优化提前退出综合以便不做无用功
由上图可以看出最差的路径在 COMBO 中已经超过了 Constraint Goal 所以当综合到 COMBO 的最差路径时综合就会结束
有时候我们不想综合提前结束 可以把路径分组这样其它组路径的 Critical Path 也会得到综合器的优化

1
2
3
group_path -name INPUTS -from [all_inputs]
group_path -name OUTPUTS -to [all_outputs]
group_path -name COMBO -from [all_inputs] -to [all_outputs]
  • 比较有意思的一个参数:critical_range
    这个参数可以指定一个范围 默认值是 0
    即最差路径到(最差路径-critical_range)之间的路径都能得到优化最差的路径也能得到优化看下图:

    这个 range 不是越大越好 如果太大了就会超过 Constraint Goal
    使得原本已经 meet 了的路径也继续优化这样会极大的增加综合器的用时
    一般不要超过时钟周期的 10%
  • weight
    设置权重 默认值为 1 权重越高工具就会越集中力量优化该 group_path

set_max_area

1
2
dc_shell-t>  current_design PRGRM_CNT_TOP
dc_shell-t>  set_max_area 100

上面的例子给 PRGRM_CNT_TOP 的设计施加了一个最大面积 100 单位的约束。
100 的具体单位是由 Foundry 规定的,定义这个单位有三种可能的标准:

  1. 一种是将一个二输入与非门的大小作为单位 1;
  2. 第二种是以晶体管的数目规定单位;
  3. 第三种则是根据实际的面积(平方微米等等)。

获取面积单位

先综合一个二输入与非门,用 report_area 看他的面积是多少,

  • 如果是 1,则是按照第一种标准定义的;
  • 如果是 4,则是第二种标准;
  • 如果是其他的值,则为第三种标准

.synopsys_dc.setup

dc 启动时会自动执行这个文件, 里面一般做一些变量的设置工作
这个文件可以在三个地方存在1.

  1. DC 安装目录下如: SYN2021.06-SP5/admin/setup/.synopsys_dc.setup

  2. Home 目录下: ~/.synopsys_dc.setup

  3. 在具体项目根目录下

优先级是 3 > 2 > 1

set 和 set_app_var

set_app_var

专门用于设置DC 内部变量, 如果设置的变量不是DC 的内部变量, 则在运行的时候就会报错, 所以用set_app_var 来设置DC 的内部变量, 可以有效的防止手误导致设置错变量

set

C shell 中用于设置环境变量, 在dc 中可以设置任何变量, set_app_var 能设置的 set 也能设置, 但是不推荐用set 替代 set_app_var

工艺厂商提供的库文件的格式

一般有两种, 一种是.lib 一种是 .db
其中.lib 是给人看的, .db 是给DC 读的

lib to db

可以通过 library_compile 工具来转换

顶层相关操作

列出当前顶层状态

1
current_design

设置顶层

1
current_design  xxx

ddc 格式

里面至少含有网表( .v )信息和约束( .sdc ) 信息
查看ddc 文件可以使用 design_vision

check_design

在综合前应该用check_design 检查一下设计有没有明显缺陷

1
2
check_design -html check_design.html
sh firefox check_design.html

list_design

可以列出当前设计的所有模块, 其中顶层模块后面会跟 (*)

Save the ddc design before compile

在check_design 后把DC 内存里面的内容保存为 .ddc 文件, 这样如果设计文件没有改动的话, 下次可以直接读取该ddc 文件来替代前面的4 步

Save the ddc design after compile

综合之后保留ddc 是给后端使用的
保存的网表文件.v 是用来做后仿或者交给别的布局布线工具使用如innovers

set_load

加负载 ( 一般来说是加容性负载即加电容 ), 单位一般是pF

get* 命令

help get* 可以查看所有以get 开头的命令

set* 命令

help set* 可以查看所有以set 开头的命令

all* 命令

all_inputs

Returns a collection of input or inout ports in the current design
如: all_inputs C*
返回所以以C 开头的输入端口

all_outputs

Returns a collection of output or inout ports in the current design.

all_clocks

Returns a collection of all clocks in the current design

all_registers

Returns a collection of sequential cells or pins in the current design.

remove* 命令

help remove* 可以查看所有以remove 开头的命令

add* 命令

help add* 可以查看所有以add 开头的命令

filter 命令

DC 访问环境变量

env

1
echo $env(DC_HOME)

get_unix_variables

1
get_unix_variables VCS_HOME

getenv

1
getenv DC_HOME

setenv

设置环境变量

1
setenv MY_ENV_VAR 10

时序约束

时序分析

起点

  • Inpurt port
  • Clock pin of Flip-Flop or register

终点

  • Output port
  • Any input pin of sequential device, expect clock pin

时钟约束

1
create_clock -period 2 [get_ports clk]

约束时钟周期为2ns 即时钟频率为 500Mhz 占空比为50%

clock_uncertainty

1
2
create_clock -period 2 [get_ports clk]
set_clock_uncertainty -setup 0.14 [get_clocks clk]

clock_latency

clock_transition

模拟时钟的斜坡

Pre/Post Layout Clock

时钟树综合之后就不需要使用 set_clock_latency -max 2 MCLK 了, 因为这时候寄存器的位置已经确定, 可以使用 set_propagated_clock MCLK 来让DC 自己去提取延时信息

set_input_delay

通过设置输入的延时, 来间接的告诉DC Tmax 最大是多少, 从而达到约束Tmax 的目的

set_output_delay

通过设置输出的延时, 来间接的告诉DC Tmax 最大是多少, 从而达到约束Tmax 的目的

Input delay multiple input path (-add_delay)

-clock_fall 意思是相对于时钟的下降沿延时0.3ns
-add_delay 意思是对同一条路径, 允许有两个约束, 让DC 自己选一个相对紧的约束, 如果
不加 -add_delay 的话, DC 会默认使用后面的约束覆盖掉前面的约束

set_ideal_network

如时钟复位这种超高扇出的网络如果不设成ideal network 的话, DC 综合的时候就会插入一些BUFFER 来增加它的驱动能力, 但是往往时钟和复位这些都是在后端CTS ( CLOCK TREE SYNTHESIZE ) 做的

如上图所示, 理想网络经过逻辑门之后如果有一个输入信号不是理想网络的话, 那么输出就不是理想网络

set_ideal_network 一般的设置对象是 ports 或者 pins, 如果要对nets 设置必须加上 -no_propagate 选项, 意思是说驱动该net 的信号是理想网络, 并不是真正的把net 设置成理想网络, 针对net 设置的话有专门的命令 set_ideal_net

Model ideal network Delay and Transition

set_ideal_xxx 可以设置一般的网络, 不像 set_clock_xxx 只能针对时钟网络

输入输出约束

Multiple inputs/outputs - same constraints

set_input_delay 对除CLK 外所有的input 路径做约束, 其中 -clock CLK 是指以CLK 为参考点
set_output_delay 对所有的outputs 路径做约束, 其中 -clock CLK 是指以CLK 为参考点

Different Port Constraint, remove_input_delay

如果是不小心设了 CLK 的input delay, 可以使用 remove_input_delay [get_port CLK] 来移除对时钟的input delay 约束

组合逻辑约束

通过设置B 的input_delay 以及D 的output_delay, 从而间接的约束了组合逻辑F

Time budget example ( 时间预算示例 )

对于KOBE’S DESIGN output_delay 设为6ns 那么CLK-> M 这条路径就有4ns
对于MY_DESIGN input_delay 设为6ns 那么N 这条路径就有4ns
合起来看 CLK->M + N => 8ns 还有2ns 的余量来给setup time hold time 这些
一般来说约束40% 即可

分组约束 (group_path)

Create user-defined path groups

分组的好处是, 每一组的最坏的路径都能得到优化

Enable Near-critical path optimization

使能优化在最差路径的0.2ns 范围内的所有路径
-critical_range 最好不要超过时钟周期的 10%, 这是Synopsys 官方文档建议的

Prioritizing path groups: -weight

权重最大是5, 默认值是1, 权重越大, DC 就会花更多精力去优势那个组

Multiple clock input delay

其中CLKA 是虚拟时钟, DC 会使用CLKA 和 CLKC 的上升沿之间的最小距离作为参考计算
如上图所示两个时钟的上升沿最小距离是1ns, TN = 1 - 0.55 - Tsetup

Mutiple clock output delay

其中CLKD 和 CLKE 都是虚拟时钟, 这里使用 -add_delay 可以使用两条约束对同一个输出端口OUT1 进行约束, DC 会各自计算并使用最紧的约束
相对于CLKD 的约束, Ts = 0.67 - 0.16 = 0.52ns
相对于CLKE 的约束, Ts = 1 - 0.52 = 0.48ns

Inter clock uncertainty

设置多时钟的uncertainty 一定要指定是哪两个时钟, 如上图所示, 使用 -from clk_xxx to clk_yyy

Generated clocks - instantiated register

使用 create_generated_clock -divide_by 2 约束设计中的二分频电路,
其中 [get_pins FF1/Q ] 里面的FF1/Q 是对应的分频后的时钟的输出, 这个东西直接在RTL 中是看不出来的, 因为要具体到寄存器的端口, 这个可以在综合前通过 write -format verilog -hier -output unmapped/xxx.v 保存的unmaped 下的GTECH 文件中找到, 因为DC 把RTL 代码读入内存时会把它转换成Synopsys 的中间格式 GTECH 格式, 这种格式可以看到寄存器端口

set_false_path or set_clock_groups

其中真实的路径只有两条, 即紫色的CLK1 -> CLK1
和蓝色的 CLK2 -> CLK2, 如果不加约束的话, DC 会认为有四条路径( CLK2 -> CLK1, CLK1 -> CLK2 )
使用 set_false_path 或者 set_clock_groups -logically_exclusive -group CLK1 -group CLK2 即可解决

Logically exclusive clocks

如上图左侧所示, CLK1 和 CLK2 的内部数据有交互, 因此单纯的set_false_path 会让DC 直接忽略掉CLK1 和 CLK2 的交互部分, 事实上我们仅仅需要把经过out 端口的路径设成false_path 即可, 如上图红色部分所示仅仅约束通过 out 端口的CLK 路径即可

Multiple clocks per register

如上图所示, 如果不加约束的话, DC 会认为有4 种可能:

  1. CLK1 产生数据 CLK1 去采样
  2. CLK1 产生数据 CLK2 去采样
  3. CLK2 产生数据 CLK1 去采样
  4. CLK2 产生数据 CLK2 去采样

实际上应该是只有两种情况: sel 为0 时使用CLK1, sel 为1 时使用CLK2

_20230101_123340screenshot.png 上图使用 set_app_var timing_enable_multiple_clocks_per_reg true 来使能同一个寄存器允许有多个时钟 给CLK1 和 CLK2 分组 并使用 -logically_exclusive 选项表示逻辑互斥, 即不会出现CLK1 -> CLK2 的情况( 虽然物理上是通的, 但是实际上通过门控信号可以控制永远不会通 ) 给CLK3 和 CLK4 分组 并使用 -logically_exclusive 选项表示逻辑互斥, 即不会出现CLK3 -> CLK4 的情况( 虽然物理上是通的, 但是实际上通过门控信号可以控制永远不会通 )

Asynchronous design constraints

1
set_clock_groups -asynchronous -group CLK1 -group CLK2

Multi-cycle Design (set_multicycle_path)

这种多周期路径, 在RTL 设计的时候就要加入移位寄存器来做使能信号, 只有当C 的En 为高的时候才把结果锁存到Y

-setup 6 的意思是允许6 个时钟周期之后再去采样
DC 默认会在 MH = 0 处检查HOLD Time ( 这样的话DC 会在中间插入一些复杂的结构 ), 实际上不需要这样, 只需要在采样数据的时候检查HOLD Time 就可以了, 通过设置hold time 的检查位置在MH=Msu-1, 即setup 设置的时钟周期 - 1, 如使用 -hold 5 的意思是在默认检查HOLD Time 的位置往前移5 个时钟周期, 即在MH=5 处检查 HOLD Time

上图所示约束了 Two-cycle path 那条蓝色的路径为多周期路径, 多周期为2

change_name

1
change_names -rules verilog -hierarchy -verbose

DC 完成了ASIC 设计流程中的RTL to NETLIST 过程, 后续需要有第三方工具完成netlist to GDS 的实现,
而第三方工具对于netlist 有字符语法上的限制, 所以需要使用 change_names 命令, 才能让第三方工具正确识别netlist

Model output capacitive load

set_input_transition

打散层次化 (ungroup -flatten -all)

1
ungroup -flatten -all

流水线及寄存器优化

Solution: pipeline or register retiming 优化流水线或者寄存器

compile_ultra -retime 是针对普通寄存器的优化, 并不针对流水线, 它主要是均衡寄存器前面的负载, 有可能会把寄存器前的一部分组合逻辑放到寄存器后, 或者反过来
set_optimize_registers_true 之后, 在compile_ultra 的时候, DC 就会对流水线的寄存器进行调整优化

如上图所示, 比如前一级寄存器处的延时比较大, DC 可能就会把前一级的一部分东西摞到后一级去, 以此来减小关键路径的延时
最终会把每一级流水线的延时都调整成差不多

Adaptive and register retiming flow

set_optimize_registers 后面可以根实例名表示只对某一个实例的流水线优化
-scan 的意思是会生成扫描链用的寄存器
-retime DC 会优化普通寄存器

Resulting pipeline

Maintaing registered outputs 指定不被优化的寄存器

指定不优化P3 寄存器, 因为这个是用来锁存输出的寄存器, 我们不希望被 -retime 选项优化, 从而导致输出引入组合逻辑

Multi-core optimization 设置多核跑综合

1
2
set_host_options -max_cores 8
compile_ultra ...

貌似需要License 支持

时序报告

report_timing

会报告时序违例, PVT ( power voltage temperature ) , 线负载模型等等

用法

-nets 用来报告连线上的负载个数
-significant_digits_number 这个选项用来指定报告中的小数点位数, 最高貌似是13 位小数

示例

  • report_timing Demo1

    图中f/r 代表 falling ( 下降延时 ) / rising ( 上升延时 ), 因为DC 计算延时时会计算两次,
    report_timing 的时候只使用延时较大的结果, 如果显示r 说明上升延时比较大
    查看工艺库的器件延时信息可以用 report_libs 命令

  • report_timing Demo2

  • report_timing Demo3

    • report_timing
      只报告一条最坏的路径, 即Slack = -0.3
    • report_timing -max_paths 2
      报告两个不同端点的最坏的两条路径, 即Slack = -0.3 和 Slack = -0.15
    • report_timing -nworst 2 -max_paths 2
      报告最坏的两条路径( 允许同一结束端点 ) 即 Slack = -0.3 和 Slack = -0.25
  • report_timing -loops

    可以检查设计中是否有产生 latch, 如果有就要解决掉

report_constraint

-all_violations
报告所有时序违例

Edge sensitive in path delays

DC 在计算延时信息时会计算两次, 比如经过一个反向器, 充电和放电时间是不一样的,
即经过上升的时间延时和经过下降沿的时间延时是不一样的, 体现在report_timing 时的
r/f 如果上升延时比较大就显示 r, 如果下降延时比较大就显示 f

Effect of driving cell on input delay

set_driving_cell 即模仿外部驱动, 添加之后会使驱动信号有斜坡,
即在set_input_delay 的基础上增加了延时, 这样的建模会更加贴近实际电路

设置set_driving_cell 一定要基于0 负载来设置

Always check for invalid exceptions (report_timing_requirements -ignored)

这条命令会报告出所有的时钟异常约束( 如set_false_path, set_clock_groups, set_multicycle_path 等约束 ), 方便用户检查设置得对不对

set_isolate_ports

1
set_isolate_ports -type buffer [all_outputs]

在所有的输出口上插入 buffer 用来和外部电路隔离

DC 输出工程文件

sdc

输出约束文件

1
write_sdc <my_design.sdc>

SCAN-DEF 输出扫描链信息

输出扫描链文件

1
write_scan_def -out <my_design.def>

ddc 文件

1
write -f ddc -hierarchy -output my_ddc.ddc

消除 verilog assign statements

多端口直连导致 assign (set_fix_multiple_port_nets)

设计代码不规范会导致DC 综合后的网表文件或者 .sdf 文件里出现assign 语句或者tri 声明, 这是不允许的

如上图所示的设计综合后就出现了assign 语句, 解决办法如下

使用

1
set_fix_multiple_port_nets -all -buffer_constants

意思是通过插入buffer 来修多端口直连的问题

tri 声明导致 assign (set_app_var verilogout_no_tri true)

通过设置 verilogout_no_tri 为true 即可解决

1
set_app_var verilogout_no_tri true

设置完之后再综合, DC 就会把tri 变成wire

change_names

DC 的输出文件中经常会有反斜杠\, 这在dc 中是没有问题的, 但在很多第三方工具上就会出问题, 为了兼容第三方工具, 需要在综合之后保存网表之前使用change_names 来解决

如上图所示, 使用 change_names -rules verilog -hier 把输出文件中的信号以verilog 的标准来命名

输出命令总结

1
2
3
4
write -f ddc -hierarchy -output <my_ddc.ddc>
write -f verilog -hierarchy -output <my.v>
write_sdc -version x.y <my_sdc.sdc>
write_sdf -version x.y <my_sdf.sdf>

write_sdc 和 write_sdf 命令都有 -version 版本信息, 可以通过 man 命令查看这两条命令的版本信息

DC 输出报告

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Get report file
set REPORT_PATH rpt
redirect -tee -file ${REPORT_PATH}/check_design.txt {check_design }
redirect -tee -file ${REPORT_PATH}/check_timing.txt {check_timing }
redirect -tee -file ${REPORT_PATH}/report_constraint.txt {report_constraint -all_violators }
redirect -tee -file ${REPORT_PATH}/check_setup.txt {report_timing -delay_type max }
redirect -tee -file ${REPORT_PATH}/check_hold.txt {report_timing -delay_type min }
redirect -tee -file ${REPORT_PATH}/report_area.txt {report_area }
# OR
check_design > ${REPORT_PATH}/check_design.txt
...

Tcl 编程

if else

1
2
3
4
5
6
set var 0
if {$var} {
    echo "Good!"
} else {
    echo "Bad!"
}

switch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
set NAME zhangsan
switch -regexp -exact $NAME {
    "zhangsan" {
        echo "$NAME come to my office";
    }
    "lisi" {
        echo "Good morning";
    }
    default {
        echo "Error! The option is not exist!"
    }
}

while

1
2
3
4
5
set i 0
while {$i < 10} {
    echo "Current value of i is $i";
    incr i 1
}

for

1
2
3
4
5
6
for {set idx 0} {$idx < 10} {incr idx 1} {
    if {$idx == 8} {
        continue;
    }
    echo "Current value of idx is $idx";
}

foreach

1
2
3
4
set NAME [list "zhangsan" "lisi" "wangwu" "zhaoliu"]
foreach NAME $NAMES {
    echo "$NAME is here!"
}

Create file and write info to it

1
2
3
4
5
set SRC "Good night!"
set file_wr_id [open data.txt w+]
puts $file_wr_id $SRC
flush $file_wr_id
close $file_wr_id

Read from a file

1
2
3
4
5
set DST ""
set file_rd_id [open data.txt r]
gets $file_rd_id DST
echo "Read from file is {$DST}"
close $file_rd_id

proc

1
2
3
4
5
6
7
8
proc max {a b} {
    if {$a > $b} {
        set y $a;
    } else {
        set y $b;
    }
    return $y;
}

先source 一下tcl 文件, 把max 函数读入内存, 之后可以直接在终端 max 1 2 调用

1
2
3
4
5
6
7
8
proc sum {args} {
    set num_list $args
    set sum 0
    foreach num $num_list {
    set sum [expr {$sum + $num}]
    }
    return $sum
}
1
2
3
4
5
set num 0
proc inc_one {num} {
    upvar $num local_var
    incr local_var 2
}

相当于创建一个指针指向外部的变量

Licensed under CC BY-NC-SA 4.0