在 Cadence Virtuoso 中使用 SKILL 进行自动化开发时,常常需要调用 UNIX/Linux shell 命令来获取系统信息、处理文件或执行外部工具。虽然 SKILL 提供了 system() 函数来执行 shell 命令,但该函数的输出默认会打印到启动 Virtuoso 的终端窗口,而不是显示在 CIW (Command Interpreter Window) 中。

本文基于 Cadence 官方知识库文章 Article 1843362: “How to get the output of a UNIX command run from CIW in CIW?”,详细介绍如何在 SKILL 中调用 shell 命令并将其输出捕获到 CIW 中。


1. 问题背景

1.1 默认行为的局限性

当在 CIW 中直接使用 system() 函数时:

system("hostname")

命令会被执行,但其输出会打印到启动 Virtuoso 的终端窗口,而不是 CIW。这对于交互式调试和自动化脚本来说非常不便。

1.2 目标需求

我们希望实现:

  • 在 SKILL 代码中调用 shell 命令
  • 将命令的输出捕获到 SKILL 变量中
  • 在 CIW 窗口中显示输出结果
  • 可以对输出进行进一步处理

2. 解决方案概览

Cadence 提供了两种主要方法来实现这一目标:

  1. 重定向到文件法: 利用 UNIX 的 > 操作符将输出保存到临时文件,然后使用 SKILL 的文件读取函数读取并打印到 CIW
  2. IPC 进程法: 使用 ipcBeginProcess 开启进程,并通过 ipcReadProcess 直接在 SKILL 变量中捕获输出

3. 方法一:重定向到文件法

3.1 基础模板

这是最通用的模式,适用于大多数简单命令:

procedure( <procedure_name>()
  system("<unix_command> > <output_file_name>")
  let(( inPort nextLine )
    inPort = infile( "./<output_file_name>" )
    when( inPort
      while( gets( nextLine inPort )
        printf("%s" nextLine)
      )
      close( inPort )
    )
  )
)

工作原理:

  1. system() 执行命令并将输出重定向到文件
  2. infile() 打开文件并返回文件端口
  3. gets() 逐行读取文件内容
  4. printf() 将内容打印到 CIW
  5. close() 关闭文件端口

3.2 示例 1: 获取主机名

procedure( CCSgetHostName()
  system("hostname > myfile")
  let(( inPort nextLine )
    inPort = infile( "./myfile" )
    while( gets( nextLine inPort )
      printf("%s" nextLine)
    )
    close( inPort )
  )
)

使用方法:

CCSgetHostName()
; 输出示例: server01.example.com

3.3 示例 2: 获取操作系统版本

procedure( CCSgetOSVer()
  system("uname -s -r > osver")
  let(( inPort nextLine )
    inPort = infile( "./osver" )
    while( gets( nextLine inPort )
      printf("%s" nextLine)
    )
    close( inPort )
  )
)

使用方法:

CCSgetOSVer()
; 输出示例: Linux 3.10.0-1160.el7.x86_64

4. 方法二:IPC 进程法

4.1 使用 ipcBeginProcess 和文件重定向

这种方法结合了 IPC 进程管理和文件读取,并包含了文件清理步骤:

procedure( CCSgetShellEnvVars()
  let(( inPort nextLine cid )
    cid = ipcBeginProcess("env > ./tempEnvFile")
    ipcWait(cid)
    inPort = infile( "./tempEnvFile" )
    when( inPort
      while(gets( nextLine inPort )
        printf("\n %s" nextLine )
      )
      close(inPort)
      inPort = nil
    )
    deleteFile("./tempEnvFile")
  )
)

关键函数:

  • ipcBeginProcess(): 启动一个 IPC 进程并返回进程 ID
  • ipcWait(): 等待进程完成
  • deleteFile(): 删除临时文件

4.2 使用 ipcReadProcess 直接捕获

无需中间文件,直接将输出读入变量:

procedure( CCSgetEnvVar( cmd )
  let(( id val )
    id = ipcBeginProcess( cmd )
    ipcWait( id )
    val = ipcReadProcess(id 10) ; 10 为超时设置(秒)
    printf("%s" val)
  )
)

使用方法:

CCSgetEnvVar("env")
; 输出所有环境变量

重要参数:

  • ipcReadProcess(id timeout): 第二个参数是超时时间(秒)
  • 建议设置合理的超时值,避免进程挂起

4.3 示例:在文件中查找字符串

procedure( CCSFindStr()
  let(( str cid )
    cid = ipcBeginProcess("grep 'Elapsed time' ./PIPO.LOG")
    ipcWait(cid)
    str = ipcReadProcess(cid)
    printf("%s" str)
  )
)

使用场景: 从日志文件中提取特定信息。


5. 通用命令执行函数

文章提供了一个可以执行任意命令并可选指定输出文件名的通用函数:

procedure( CCSRunCmd( cmdName @optional (fileName "myFile") )
  let( (strCmd id inPort nextLine)
    strCmd = strcat(cmdName " > " fileName)
    id = ipcBeginProcess(strCmd)
    ipcWaitForProcess(id)
    inPort = infile(fileName)
    when( inPort
      while( gets( nextLine inPort )
        printf("%s" nextLine)
      )
      close( inPort )
    )
  )
)

使用方法:

; 使用默认文件名
CCSRunCmd("ls -la")

; 指定文件名
CCSRunCmd("ps aux" "process_list.txt")

函数特点:

  • 使用 @optional 参数提供默认文件名
  • 使用 strcat() 拼接命令字符串
  • 灵活适用于各种 shell 命令

6. 最佳实践与注意事项

6.1 IPC 方法的优势

ipcBeginProcess 相比 system 提供了:

  • 更好的异步控制: 可以通过 ipcWait() 控制等待时间
  • 状态跟踪: 可以检查进程状态
  • 输出捕获: 通过 ipcReadProcess() 直接获取输出

6.2 超时处理

在使用 ipcReadProcess 时,建议指定合理的超时时间:

val = ipcReadProcess(id 10)  ; 10 秒超时

这样可以避免进程挂起导致 Virtuoso 无响应。

6.3 资源清理

如果使用了临时重定向文件,执行完毕后应删除:

deleteFile("./tempFile")

这有助于保持工作目录整洁,避免文件积累。

6.4 平台兼容性

UNIX/Linux 命令示例:

  • hostname: 获取主机名
  • uname -s -r: 获取操作系统信息
  • env: 查看环境变量
  • grep: 文本搜索

Windows 平台: 在 Windows 环境下需替换为对应的 DOS/PowerShell 命令:

  • hostname (通用)
  • ver: 查看 Windows 版本
  • set: 查看环境变量
  • findstr: 文本搜索

跨平台处理:

procedure( getPlatformSpecificCommand()
  if( getShellEnvVar("OS") == "Windows_NT" then
    ; Windows 命令
    system("ver > osver")
  else
    ; UNIX/Linux 命令
    system("uname -a > osver")
  )
)

6.5 安全注意事项

警告: 在生产环境使用前应进行充分测试。

  • 避免执行来自不可信来源的命令
  • 注意命令注入风险
  • 验证用户输入
  • 限制命令执行权限

7. 完整应用示例

7.1 获取磁盘空间信息

procedure( CCSgetDiskSpace()
  let(( id output )
    id = ipcBeginProcess("df -h")
    ipcWait(id)
    output = ipcReadProcess(id 10)
    printf("\n=== Disk Space Information ===\n%s" output)
  )
)

7.2 检查特定进程是否运行

procedure( CCSCheckProcess( processName )
  let(( id result )
    id = ipcBeginProcess(sprintf(nil "ps aux | grep %s | grep -v grep" processName))
    ipcWait(id)
    result = ipcReadProcess(id 5)
    if( result != "" then
      printf("\nProcess '%s' is running:\n%s" processName result)
    else
      printf("\nProcess '%s' is not running." processName)
    )
  )
)

使用方法:

CCSCheckProcess("virtuoso")

7.3 批量文件操作

procedure( CCSListDesignFiles( directory )
  let(( id fileList )
    id = ipcBeginProcess(sprintf(nil "find %s -name '*.oa'" directory))
    ipcWait(id)
    fileList = ipcReadProcess(id 30)
    printf("\n=== Design Files in %s ===\n%s" directory fileList)
  )
)

8. 错误处理

8.1 检查文件是否成功打开

procedure( CCSRunCmdSafe( cmdName fileName )
  let(( id inPort nextLine )
    id = ipcBeginProcess(strcat(cmdName " > " fileName))
    ipcWaitForProcess(id)
    
    inPort = infile(fileName)
    if( inPort then
      while( gets( nextLine inPort )
        printf("%s" nextLine)
      )
      close( inPort )
      deleteFile(fileName)
    else
      error("Failed to open output file: %s" fileName)
    )
  )
)

8.2 检查进程执行状态

procedure( CCSRunCmdWithStatus( cmdName )
  let(( id status output )
    id = ipcBeginProcess(cmdName)
    status = ipcWait(id)
    
    if( status == 0 then
      output = ipcReadProcess(id 10)
      printf("\nCommand executed successfully:\n%s" output)
    else
      printf("\nCommand failed with status: %d" status)
    )
  )
)

9. 总结

在 SKILL 中调用 shell 命令并获取输出有两种主要方法:

方法 优点 缺点 适用场景
文件重定向法 简单直观,容易理解 需要创建临时文件,需要手动清理 简单命令,输出量较小
IPC 进程法 更好的控制,无需临时文件 稍微复杂,需要理解 IPC 机制 复杂命令,需要进程管理

推荐实践:

  1. 对于简单的一次性命令,使用文件重定向法
  2. 对于需要重复调用或进程控制的场景,使用 IPC 进程法
  3. 始终进行错误处理和资源清理
  4. 考虑跨平台兼容性
  5. 在生产环境使用前充分测试

通过掌握这些技术,您可以在 SKILL 脚本中充分利用操作系统的强大功能,实现更加灵活和强大的自动化解决方案。


参考资源