C shell for EDA environments

Learning objectives
  • Read the csh and tcsh setup scripts that many EDA sites and tool vendors still ship
  • Explain the difference between set and setenv
  • Extend the search path with the path array
  • Test for variables with $?name and read exit status from $status
  • Recognise csh if, foreach and switch blocks
  • Translate a csh setup script into Bash
  • Import a csh environment into a Bash flow when the setup script cannot be changed
1 Objective→ 2 Learn→ 3 Example→ 4 Practice→ 5 Verify→ 6 Continue
How to study this lessonUnderstand the idea first. Use the example to see the behaviour, practise where provided, then make sure you can explain the result before continuing.

Explanation

Why you still meet the C shell

Bash is the right language for new scripts, but many design centres set up their tools with csh or tcsh. Vendor installation notes often say source /tools/<vendor>/setup.csh, team setup files are called .cshrc.project, and older run scripts start with #!/bin/csh -f. You do not need to write new csh, but you must be able to read it, change a path in it, and make its environment available to a Bash or Python flow.

tcsh is an extended csh with command-line editing. On Linux, /bin/csh is usually tcsh, and everything in this lesson works in both.

Shell variables and environment variables

csh / tcsh Bash Meaning
set top = fifo top=fifo Shell variable, not seen by child processes
setenv PROJ_ROOT ~/proj export PROJ_ROOT=~/proj Environment variable, inherited by tools
unset top, unsetenv PROJ_ROOT unset top, unset PROJ_ROOT Remove the variable
echo $?PROJ_ROOT [[ -v PROJ_ROOT ]] 1 if the variable is set, 0 if not
echo $status echo $? Exit status of the last command
set path = ( /eda/bin $path ) PATH=/eda/bin:$PATH Prepend a directory to the search path

set uses spaces around =; setenv has no = at all. A simulator started from the shell only sees setenv variables, so a licence or tool-root variable created with set is a common reason for "tool not found" or licence errors.

csh keeps the shell variable path (a word list) and the environment variable PATH (a colon-separated string) in step: changing one updates the other. Lists use parentheses, and $path[1] is the first element.

Control flow

if ($?LD_LIBRARY_PATH) then
    setenv LD_LIBRARY_PATH /eda/lib:${LD_LIBRARY_PATH}
else
    setenv LD_LIBRARY_PATH /eda/lib
endif

foreach f (rtl/*.v)
    echo "source: $f"
end

switch ($tool)
    case iverilog:
        set flags = "-g2012"
        breaksw
    default:
        echo "unknown tool $tool"
        exit 2
endsw

@ count = $count + 1

if conditions go in parentheses and blocks end with endif, end or endsw. Arithmetic uses @, with a space after it. Testing $?NAME before using $NAME matters: csh stops with Undefined variable if a script reads a variable that does not exist.

Sourcing, -f and redirection

source setup.csh runs the file in the current shell, so its setenv and set path changes stay after it finishes. Running it as csh setup.csh would change only a child shell, which then exits. The -f in #!/bin/csh -f skips the user's ~/.cshrc, so personal aliases and paths cannot change the behaviour of a shared script.

csh redirection is limited. cmd >& log sends standard output and standard error to the same file, and cmd |& tee log pipes both, but there is no direct way to send only standard error to a file. That limitation, together with weak error handling and quoting, is why new automation belongs in Bash or Python.

Environment modules

Many sites wrap their setup in Environment Modules or Lmod: module load iverilog/12.0 sets the same variables for csh, Bash and Python callers from one description. If module avail works on your machine, prefer module load in new scripts instead of sourcing a csh file.

Worked example

A shared project ships setup_eda.csh, which you are not allowed to change:

#!/bin/csh -f
# setup_eda.csh - use with: source setup_eda.csh
if (! $?PROJ_ROOT) then
    setenv PROJ_ROOT $HOME/project_workspace
endif
setenv EDA_ROOT /opt/eda
set path = ( $EDA_ROOT/bin $path )
if ($?LD_LIBRARY_PATH) then
    setenv LD_LIBRARY_PATH ${EDA_ROOT}/lib:${LD_LIBRARY_PATH}
else
    setenv LD_LIBRARY_PATH ${EDA_ROOT}/lib
endif
setenv SIM_DEFINES "-DSIM -DNO_ASSERT_STOP"
alias simdir 'cd $PROJ_ROOT/sim'

A Bash flow cannot source this file: Bash does not understand setenv, set path = (...) or endif. Instead, run it in tcsh, print the resulting environment, and import it. Save as csh_env.sh and use it with source csh_env.sh setup_eda.csh:

# csh_env.sh - import the environment produced by a csh setup script.
# Usage (from Bash): source csh_env.sh path/to/setup.csh
csh_setup=${1:?usage: source csh_env.sh setup.csh}
[[ -f "$csh_setup" ]] || { echo "csh_env.sh: no file $csh_setup" >&2; return 2; }

csh_env=$(tcsh -f -c 'source "$1" && env -0' "$csh_setup" | base64 -w0
          exit "${PIPESTATUS[0]}") \
    || { echo "csh_env.sh: $csh_setup failed" >&2; return 2; }

while IFS= read -r -d '' entry; do
    name=${entry%%=*}
    [[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
    case $name in SHLVL|_|PWD|OLDPWD) continue ;; esac
    export "$entry"
done < <(base64 -d <<<"$csh_env")
source csh_env.sh setup_eda.csh
echo "$EDA_ROOT"        # /opt/eda
echo "$SIM_DEFINES"     # -DSIM -DNO_ASSERT_STOP
command -v iverilog     # found through the new PATH

tcsh -f -c '... "$1" ...' file passes the file name as an argument, so a path with spaces is not re-parsed. env -0 separates entries with NUL bytes, so values containing spaces or newlines survive, and base64 carries those bytes safely through a Bash variable. exit "${PIPESTATUS[0]}" returns tcsh's status rather than base64's, and capturing the output first means a failing setup script stops the import instead of half-applying it. Only valid variable names are exported, and shell bookkeeping variables are skipped. Aliases such as simdir are not part of the environment and are not imported; that is expected, because scripts should not rely on aliases.

For a script you own, translate instead of importing. The same setup in Bash:

# setup_eda.sh - use with: source setup_eda.sh
export PROJ_ROOT=${PROJ_ROOT:-$HOME/project_workspace}
export EDA_ROOT=/opt/eda
export PATH="$EDA_ROOT/bin:$PATH"
export LD_LIBRARY_PATH="$EDA_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export SIM_DEFINES="-DSIM -DNO_ASSERT_STOP"

${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} adds the colon and old value only when the variable was already set, which replaces the whole csh if ($?LD_LIBRARY_PATH) block.

Check yourself

Why does a simulator not see a variable created with set in csh? What does $?PROJ_ROOT return, and why test it before reading $PROJ_ROOT? Why must a setup script be sourced rather than run? What does -f on the first line prevent? Why can a Bash script not source a csh file directly, and how does csh_env.sh work around that? What does >& do that > does not?

Engineering practice

Practice: C shell for EDA environments

Task→Run / reason→Check result→Explain why

Write a short csh script count_rtl.csh that loops over rtl/*.v with foreach, counts the files with @, and exits with status 2 if there are none; run it with tcsh -f count_rtl.csh and print $? from Bash. Then add setenv IVERILOG_FLAGS "-g2012 -Wall" to a copy of setup_eda.csh, import it with csh_env.sh, and show that a Bash run_sim.sh can use $IVERILOG_FLAGS. Finally, translate a setup file from your own site or tool installation into Bash and check that env | sort is the same both ways for the variables it sets.

Practice work is non-certified and is not stored by ASIC Campus.