Editor's Note: Having just arrived home from last week's Synopsys User's
  Group Meeting (SNUG'97) at the Red Lion Inn in San Jose, CA, I thought I'd
  make this week's ESNUG the SNUG'97 "Best Paper" by Steve Golson on how to
  put a perl wrapper around dc_shell.  (Useful stuff!)

  The three other papers that users ranked very closely to Steve's paper
  for the "Best Paper" award were:

      "Synthesis Of A Million Gates"  by Don Mills of Lockheed-Martin

      "The Boa Methodology" by Wilson Snyder of Digital Equipment Corp.

      "Synthesis For High Performance Systems Using Behavioral Retiming"
          by Rajen S. Ramchandani of Digital Equipment Corportation

  Steve got a check for $2000 from Synopsys as part of the award.  Cool!

                                             - John Cooley
                                               the ESNUG guy

( ESNUG 259 Item 1 ) -------------------------------------------- [2/96]

From: sgolson@trilobyte.com (Steve Golson)
Subject:  dc_perl: Enhancing dc_shell Using a Perl Wrapper

John, as you requested, here's a copy of my dc_perl paper and the actual
script itself for publication on ESNUG.  As I said at SNUG'97, I'd like
to see what users can do with this; break it!, change it!, make it better
and share how you improve it withe the rest of us!

  - Steve Golson
    Trilobyte Systems

P.S.  My wife's already found a way to spend the $2000 prize money.  :^(


Abstract
--------

Is there a command that you wish dc_shell had?

By using the Perl interpreter as a "wrapper" around dc_shell, powerful
extensions to dc_shell can be created. dc_shell commands can be generated by
Perl, and the results analyzed by Perl in real time (not post-processed).
Further dc_shell commands can be algorithmically generated by Perl based on
the given results.

The user interface is just like dc_shell, but with user-defined extensions.
This approach is particularly suited for complex synthesis problems that
currently require lots of post-processing or tedious human analysis.

The problem
-----------

dc_shell provides a simple programming interface to Design Compiler (see
Figure 1). However for many complex synthesis tasks it has significant
limitations, including:

  - no subroutines
  - no variable scoping
  - limited arithmetic and logical operations
  - primitive list processing
  - inflexible extensibility (sh, execute)
  - primitive pattern matching (regular expressions)

What we need is a simple, flexible, and above all powerful way to extend
dc_shell. These extensions should work both within scripts and
interactively. All existing dc_shell scripts should work without
modification.

A solution
----------

What we need is for dc_shell to be more like Perl! Perl, the Practical
Extraction and Report Language, is (to quote the manpage) an interpreted
language optimized for scanning arbitrary text files, extracting information
from those text files, and printing reports based on that information. It's
also a good language for many system management tasks. Best of all, it's
freely available and freely redistributable.

A Perl program called dc_perl has been developed. It parses a stream of
commands entered interactively or via batch files, and determines which
commands are Perl and which are dc_shell. The Perl commands are evaluated
directly, and the dc_shell commands are passed via pipes to an actual
dc_shell process running as a child under Perl. Output is controlled by the
dc_perl wrapper, so dc_shell command output can be filtered and processed
before being printed to the main dc_perl logfile (see Figure 2).

dc_perl scripts can be thought of in several ways: as dc_shell scripts with
a few "extensions", or as Perl scripts that occasionally call dc_shell, or
anything in between.

Invoking dc_perl
----------------

dc_perl is invoked from your usual UNIX command prompt:

  unix% dc_perl
  dc_perl version 0.0
			  DC Professional (TM)
			     DC Expert (TM)
			    HDL Compiler (TM)

		      Version v3.4b -- Apr 01, 1996
		Copyright (c) 1988-1995 by Synopsys, Inc.
			   ALL RIGHTS RESERVED

  This program is proprietary and confidential information of Synopsys, I
  and may be used and disclosed only as authorized in a license agreement
  controlling such use and disclosure.

  Initializing...
  dc_perl>

This looks like a normal dc_shell session, except a dc_perl version number
is displayed and the user prompt is changed to dc_perl>.

The -f command-line switch can be used to specify a dc_perl batch script.
All other dc_shell command-line switches are passed through to the dc_shell
process.

dc_perl commands
----------------

All dc_shell commands work normally. These keywords are used to switch into
the Perl interpreter:

  &begin_perl;
  # all lines between these two are evaluated by the perl interpreter
  &end_perl;

The following Perl commands are predefined for interfacing with dc_shell:

  &get_dc_shell_variable("variable");

Gets the value of a dc_shell variable. Returns a list or scalar depending on
the type of dc_shell variable.

  &set_dc_shell_variable("variable", value);

Sets the value of the specified dc_shell variable. If value is a Perl list,
then variable is assigned as a dc_shell list.

  &dc_shell_cmd("command");

Executes the given dc_shell command string. The output is printed to
standard output.

  &get_dc_shell_cmd("command");

Executes the given dc_shell command string, and returns the output instead
of printing it. This is used when you wish to filter or parse the output of
the command.

Any non-comment line that has "&" as the first non-whitespace character, and
ends with a ";" is assumed to be a Perl function and is evaluated by the
Perl interpreter (i.e. if it looks like Perl it is assumed to be Perl). This
allows user-defined Perl subroutines to be invoked directly without using
the &begin_perl and &end_perl constructs.

Example: How to get the cell name when you have the pin name
------------------------------------------------------------

In dc_shell if you have a pin name (from the all_connected() command, for
example) you might want to get the corresponding cell name. This requires a
simple regular expression substitution to strip off the last hierarchical
element of the pin name. However it is awkward and difficult to do this in
dc_shell.

Here is a dc_perl script that generates a list of cell names from a list of
pin names.

  /* ...dc_shell commands... */
  /* the dc_shell variable is called mypins */

  &begin_perl;
  # these are perl commands
  @list = &get_dc_shell_variable("mypins");
  # strip off the trailing /... from each element in the list
  grep { s?/]*$?? } @list;
  # create the dc_shell variable
  &set_dc_shell_variable("mycells", @list);
  &end_perl;

  /* the dc_shell variable mycells has the list in it */
  /* ...more dc_shell commands... */

Alternatively this can all be defined as a Perl subroutine:

  /* ...dc_shell commands... */
  &begin_perl;
  sub getcells {
    my ($cellvar, $pinvar) = @_;
    my @list = &get_dc_shell_variable($pinvar);
    grep { s?/]*$?? } @list;
    &set_dc_shell_variable($cellvar, @list);
    }
  &end_perl;
  /* ...more dc_shell commands... */

Now this subroutine can be invoked directly from dc_shell. Here is how you
might use this interactively:

  dc_perl> list mylist
  mylist = {"a/b/cde", "f/g/h/ijk", "l", "m/n/op"}
  1

  dc_perl> &getcells("newlist", "mylist");

  dc_perl> list newlist
  {"a/b", "f/g/h", "l", "m/n"}
  1

Example: Extracting the slack from a timing report
--------------------------------------------------

Analyzing timing reports is a common and sometimes tedious task. With
dc_perl we can automatically parse the timing report and extract values from
it. For example, if the slack is below a certain amount then we might wish
to do a further compile step on this module.

Here is a dc_perl subroutine which extracts the slack value from a timing
report:

  sub get_slack {
    $_ = &get_dc_shell_cmd("report_timing");
    m/ slack\s+  # keyword slack
       \S+\s+    # followed by one more word
       (\S+)     # then the value we want
       /x || warn "got no match";
    &set_dc_shell_variable("dc_shell_status",$1);
    }

Running report_timing directly we get

  ****************************************
  Report : timing
	  -path full
	  -delay max
	  -max_paths 1
  Design : gf_mult
  Version: v3.4b
  Date   : Fri Jan 17 09:22:11 1997
  ****************************************

  Operating Conditions: typical   Library: access05_5v
  Wire Loading Model Mode: top

    Startpoint: in_b[0] (input port)
    Endpoint: out[0] (output port)
    Path Group: default
    Path Type: max

    Point                                    Incr       Path
    -----------------------------------------------------------
    input external delay                     0.00       0.00 r
    in_b[0] (in)                             0.00       0.00 r
    U156/Y (NAND2X2)                         0.11       0.11 f
    -
    U162/Y (XOR2X1)                          0.39       3.07 f
    out[0] (out)                             0.00       3.07 f
    data arrival time                                   3.07

    max_delay                                8.00       8.00
    output external delay                    0.00       8.00
    data required time                                  8.00
    -----------------------------------------------------------
    data required time                                  8.00
    data arrival time                                  -3.07
    -----------------------------------------------------------
    slack (MET)                                         4.93

Instead we can invoke &get_slack which will run report_timing for us and
parse the output, leaving the slack value in dc_shell_status:

  dc_perl> &get_slack();
  4.930000

  dc_perl> list dc_shell_status
  dc_shell_status = 4.930000
  1

A simple modification to get_slack would allow report_timing arguments to be
passed through.

Future work
-----------

A sophisticated analysis of timing reports would allow dc_perl to generate
true timing budgets for a hierarchical design, without the limitations of
characterize.

Complex automated synthesis techniques are made feasible with the powerful
combination of Perl and dc_shell.

Availability
------------

This program is free software; you can redistribute it and/or modify it
under the terms of either:

  a) the GNU General Public License as published by the Free Software
     Foundation; either version 1, or (at your option) any later version, or

  b) the "Artistic License" which comes with the dc_perl kit.

These are the same terms under which Perl itself is distributed.


#! /usr/local/bin/perl

##########################################################################
#
#    dc_perl
#
#    Copyright 1997 Steve Golson
#
#    You may distribute under the terms of either the GNU General Public
#    License or the Artistic License, as specified above.
#
##########################################################################

require 5.002 ;
require "chat2.pl";
use Getopt::Std;
use FileHandle;

########## catch signals

sub GotINT {
    print "dc_perl caught signal INT\n" ;
    goto LOOP ;
}

sub GotQUIT {
    print "dc_perl caught signal QUIT\n" ;
    exit;
}

sub GotTERM {
    print "dc_perl caught signal TERM\n" ;
    exit;
}

sub GotSig {
    exit;
}

$SIG{"INT"}  = "GotINT";
$SIG{"QUIT"} = "GotQUIT";
$SIG{"TERM"} = "GotTERM";

########## initialize variables etc.

STDOUT->autoflush(1);
STDERR->autoflush(1);

$dc_shell_prompt = "dc_shell> " ;
$dc_perl_prompt = "dc_perl> " ;
$perl_prompt = "perl> " ;

$dc_shell_tmp_var = "%%dc_perl_tmp_var%%" ;

$perl_mode = 0 ;
$is_batch = 0 ;

########## check options

getopt('f');

if ($opt_f) {
open(BATCHFILE,"<$opt_f") || die "Could not open batch file $opt_f : $!\n" ;
    }

########## invoke dc_shell

# would rather invoke dc_shell_exec directly, so we know the pid of
# the child -- but how do you do 'which' if not running csh?

$Handle = &chat::open_proc("stty raw ; dc_shell");

# read data until you get a prompt
&wait_for_dc_prompt(1) ;

########## quietly initialize dc_shell variables

$is_batch = 1 ;
&get_dc_shell_cmd('enable_page_mode = "false"') ;   # disable page mode

########## execute batch file, if any

$is_batch = 1 ;
if ($opt_f) {
    while (<BATCHFILE>) { &_parse_line($_) ; }
    close(BATCHFILE) ;
    }

########## begin interactive processing

$is_batch = 0 ;
print $dc_perl_prompt ;

LOOP: while (<STDIN>) { &_parse_line($_) ; }

die "How did I get here?\n" ;

###########################################################################
##
## dc_perl commands
##

########################################
## $return = &dc_shell_cmd($cmd) ;
##
## sends $cmd to child process
## prints and returns the results

sub dc_shell_cmd {
    return &_dc_shell_cmd(1, @_) ;
    }

########################################
## $return = &get_dc_shell_cmd($cmd) ;
##
## sends $cmd to child process
## returns the results without printing anything

sub get_dc_shell_cmd {
    return &_dc_shell_cmd(0, @_) ;
    }

########################################
## $value = &get_dc_shell_variable($variable);
## @value = &get_dc_shell_variable($variable);
##
## gets the value of the specified dc_shell variable
##
## if called in an array context, the value of $variable
## is parsed as a dc_shell list, and each element is returned
## as part of a list. double-quotes are removed from around
## each individual element
##
## if called in a scalar context, the value is returned
## double-quotes and brackets are removed from around the value

sub get_dc_shell_variable {
    my $variable = shift ;
    my @list ;

    $_ = &get_dc_shell_cmd("list $variable") ;

    # extract the value
    m/^\S+ = (.*)\n/ ;
    $_ = $1 ;

    if (wantarray) {    # parse as a list
        # remove surrounding brackets {}
        s// ;
        s// ;
        # split into a list
        @list = split / ;
        # remove quotes "" from around each element
        grep {s// ; s// ;} @list ;
        return @list ;
        }
    else {      # parse as a scalar
        # remove surrounding brackets {} and quotes ""
        s/^[{"]// ;
        s/[}"]$// ;
        return $_ ;
        }
    }

########################################
## &set_dc_shell_variable($variable, VALUE) ;
##
## sets the value of the specified dc_shell variable
## VALUE may be a scalar or a list
## if VALUE is a perl list, then $variable is assigned as a dc_shell list
##
## how do you create a dc_shell list with one element?
## make a string with {} around it:
##   &get_dc_shell_cmd("myvar", "{hello}");

sub set_dc_shell_variable {
    my ($variable) = shift ;
    my $num_of_elements = @_ ;
    my $value = "" ;
    my $tmp ;

    # use bogus variable name if you want VALUE left in dc_shell_status
    if ($variable eq "dc_shell_status") {
        &get_dc_shell_cmd("remove_variable $dc_shell_tmp_var") ;
        $variable = $dc_shell_tmp_var ;
        }

    if ($num_of_elements==1) {
        # scalar
        $value = $_[0] ;
        }

    else {
        # list
        $value = '{' ;
        foreach $tmp (@_) { $value .= $tmp.", " ; }
        chop $value ; chop $value ;
        $value .= '}' ;
        }

    &get_dc_shell_cmd("$variable = $value") ;
    # ?? should this always print ??
    # instead print last line from get_dc_shell_cmd
    print "$value\n" ;
                        

    return undef ;
    }

########################################
## &begin_perl(); or &begin_perl;
##
## indicates the beginning of perl commands
## all lines between this one and the next "&end_perl;"
## are evaluated by the perl interpreter

sub begin_perl {
    $perl_mode = 1 ;
    $eval_buffer = "" ;
    }

########################################
## &end_perl;
##
## indicates the end of perl commands
## never actually invoked, because "&end_perl;" is parsed as a token
## indicating that everything before it should be evaluated

sub end_perl {
    warn "&end_perl was called with perl_mode $perl_mode" ;
    $perl_mode = 0 ;
    }

##########################################################################
##
## utility subroutines
##

########################################
## &perl_cmd($cmd) ;
##
## parses and evals perl commands

sub perl_cmd {
    my $line = shift ;

    if ($line =~ /\s*&end_perl\s*;\s*$/) {
        # found "&end_perl;"
        eval $eval_buffer ; warn $@ if $@ ;
        $perl_mode = 0 ;
        }
    else {
        # add the line you got to the eval buffer
        $eval_buffer .= $line ;
        }
    }

########################################
## &_parse_line($line) ;
##
## $line contains a line read from STDIN or batch file
## parses and executes the line

sub _parse_line {
    my $line = shift ;
    chop $line ;
    if ($perl_mode) {           # perl commands
        &perl_cmd($line) ;
        }
    elsif (/^\s*&/) {           # single perl command
        eval $line ; warn $@ if $@ ;
        }
    else {                      # dc_shell command
        &dc_shell_cmd($line);
        }
    # print prompt
    print $perl_mode ? $perl_prompt : $dc_perl_prompt unless $is_batch
    }

########################################
## $return = &_dc_shell_cmd($doprint, $cmd) ;
##
## sends $cmd to child process
## returns the results
## prints where appropriate if enabled

sub _dc_shell_cmd {
    my $doprint = shift ;
    my $lines ;

    # send the command to the child
    &emit_dc_cmd(@_) ;

    # get the echoed command
    $lines = &get_line(1) ;

    # print echoed command if in batch mode and printing enabled
    print $lines if ($doprint and $is_batch) ;

    # get command output
    $lines = &wait_for_dc_prompt($doprint) ;

    # return everything you got
    return $lines ;
    }

########################################
## &emit_dc_cmd($cmd);
##
## sends $cmd to child

sub emit_dc_cmd {
    &chat::print($Handle, $_[0]."\n");
    }

########################################
## $return = &wait_for_dc_prompt($doprint);
##
## returns all output received from child, up to but not including
## a line received that contains only a dc_shell prompt and no newline
##
## if $doprint then prints everything that was returned (except the prompt)
##
## could be spoofed by an 'echo "dc_shell> "'
##
## this will hang if you never get a prompt

sub wait_for_dc_prompt {
    local $doprint = shift ;
    # exit immediately if OutBuffer already has the prompt
    # match as much as possible up to a newline
    if ($OutBuffer =~ /(''|[\s\S]*\n)($dc_shell_prompt$)/) {
        print $1 if $doprint ;
        $OutBuffer = "" ;
        return $1 ;
        }
    local $lines = $OutBuffer ;
    while (1) {
        &chat::select(undef,$Handle);       # wait until ready for IO
        &chat::expect($Handle,0,
            # match as much as possible up to a newline,
            # print buffer plus that match, rest goes in buffer
            '([\s\S]*\n)([^\n]*)',
                'print $OutBuffer.$1 if $doprint ;  # print
                 $lines .= $1 ;                     # append to buffer
                 $OutBuffer = $2 ;',                # save the rest
            # line contains no newline, so append everything to buffers
            '[\s\S]+',
                '$OutBuffer .= $& ;
                 $lines .= $& ;',
            # EOF means child exited
            'EOF',          'exit ;' ) ;
        # last if OutBuffer contains only dc_shell prompt
        last if ($OutBuffer eq $dc_shell_prompt) ;
        }
    $OutBuffer = "" ;
    return $lines ;
    }

########################################
## $return = &get_line($num);
##
## reads $num lines from child output, and returns them
## anything after the final newline is placed in $OutBuffer
##
## this will hang if you never get a newline

sub get_line {
    my $num = shift ;
    my $i ;
    my $line = "" ;
    for ($i=1; $i<=$num; $i++) {
        $line .= &_get_line() ;
        }
    return $line ;
    }

########################################
## $return = &_get_line();
##
## reads one line from child output, and returns it
## anything after the first newline is placed in $OutBuffer
##
## this will hang if you never get a newline

sub _get_line {
    local $last = undef ;
    local $line = $OutBuffer ;
    # exit immediately if OutBuffer already has a newline
    if ($OutBuffer =~ /([\s\S]*?\n)([\s\S]*)/) {
        $OutBuffer = $2 ;
        return $1 ;
        }
    while (1) {
        &chat::select(undef,$Handle);       # wait until ready for IO
        &chat::expect($Handle,0,
          # match up to the first newline
          '([\s\S]*?\n)([\s\S]*)', '$line .= $1 ;    # append to line buffer
                                   OutBuffer = $2 ; # rest to OutBuffer
                                   $last = 1 ;',     # end of while loop
            # line contains no newline, so append everything to line buffer
            '[\s\S]+',              '$line .= $& ;',
            # EOF means child exited
            'EOF',                  'exit ;' ) ;
        last if $last ;
        }
    return $line ;
    }

########################################

sub BEGIN {
    print "dc_perl version 0.001\n" ;
}

sub END {
    &chat::close($Handle);
    print "Thank you for using dc_perl.\n" ;
}



 Sign up for the DeepChip newsletter.
Email
 Read what EDA tool users really think.


Feedback About Wiretaps ESNUGs SIGN UP! Downloads Trip Reports Advertise

"Relax. This is a discussion. Anything said here is just one engineer's opinion. Email in your dissenting letter and it'll be published, too."
This Web Site Is Modified Every 2-3 Days
Copyright 1991-2024 John Cooley.  All Rights Reserved.
| Contact John Cooley | Webmaster | Legal | Feedback Form |

   !!!     "It's not a BUG,
  /o o\  /  it's a FEATURE!"
 (  >  )
  \ - / 
  _] [_     (jcooley 1991)