Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

TCL/TK and SKILL Mix It Up


Feb02: Tcl/Tk and SKILL Mix It Up

Christopher is a senior software engineer at Pinebush Technologies and author of Tcl/Tk Programmer's Reference (http://www.purl.org/net/TclTkProgRef). He can be reached at [email protected].


It is well known that Tcl's roots are in design tools for integrated circuits. But while Tcl is common in that market, it is not pervasive. For example, products from Cadence Design Systems, a leading vendor of electronic design automation (EDA) tools for IC design, are scripted with SKILL, a proprietary dialect of LISP. Imagine my frustration, then, when Pinebush Technologies (where I work) gave me the job of coordinating a GUI written in Tcl/Tk with a Cadence system.

HyperCDS is Pinebush's add-in for Cadence's Virtuoso, which prints chip designs directly from the Cadence database. While the initial HyperCDS GUI was written entirely in SKILL, the language's UI features are limited and the interfaces it produces appear dated. To make matters worse, HyperCDS had grown to more than 10,000 lines of difficult-to-maintain SKILL code.

As it turns out, Pinebush had also developed a nonHyperCDS Tcl/Tk-based GUI for use in other environments. Because of our experience with Tcl/Tk's strong UI facilities, the Tcl/Tk-based GUI had acquired many more features than HyperCDS over time. Consequently, the decision was made to adapt the Tcl/Tk-based GUI for use with the Cadence tools.

An overview of the layered architecture of the hybrid HyperCDS system would include the following. At the uppermost level are standard SKILL functions for interacting with the Cadence environment. Below that are a limited number of special-purpose custom SKILL functions for adapting the standard functions to HyperCDS. Finally, there is a small core of general-purpose SKILL functions that manage communication with the Tcl/Tk GUI.

On the Tcl/Tk side, the uppermost layer is comprised mostly of existing code for the GUI, but with a few HyperCDS-specific functions. Below that, a limited number of special-purpose Tcl procs expose Cadence functions and data in a form compatible with the GUI's expectations. Finally, a core of general-purpose Tcl procs manage communication with the Cadence system.

The Devil Is in the Details

In truth, the aforementioned overview oversimplifies the lowest levels — it takes a leap of faith, for instance, to assume that SKILL and Tcl can exchange data across process boundaries. Fortunately, communication and integration with existing systems are among Tcl's strengths, while SKILL has several functions that can be used for communicating with other processes.

For instance, included in SKILL's communication options is hiSkillProcess (renamed uiSkillProcess in later CDS releases). Figure 1 illustrates dataflows for a child process started with hiSkillProcess communicating with the Cadence system. (A third process, the Cadence serv daemon, is involved as a go-between, but is transparent to SKILL and Tcl programs, and not addressed here.) There are SKILL functions for sending and receiving data through the client's standard channels. In addition to the usual stdin, stdout, and stderr, clients started with hiSkillProcess have two other channels open: The client can send SKILL code to the Cadence system on file descriptor 3 and get back code-evaluation results on fd4.

With the Cadence side of the interface awaiting SKILL to evaluate, it remains to build a mechanism for Tcl programmers to send SKILL to the Cadence interpreter and get a response back. Ideally, it would be as simple as Listing One.

Depending on how the channel file4 is configured, read either blocks waiting for data, or fails because no data is available. If it blocks, the UI locks up because no events can be processed. If it fails, you must poll for a response until one is received. However, even polling is insufficient because different parts of the Tcl application — perhaps the actions associated with different buttons — may require evaluation of different SKILL expressions at the same time. What you need to do is:

  1. Send the SKILL expression to Cadence for evaluation.

  2. Let the Tcl application get on with its business (handling UI events or whatever).

  3. Process the response when it comes.

If you could be assured that the Cadence side would evaluate expressions on a first-in/first-out basis, this might succeed without special bookkeeping code to match responses to requests. However, the Cadence SKILL server operates on expressions asynchronously, giving no assurance about the order of responses.

Consequently, I turned to Section 7.6 of Effective Tcl/Tk Programming (Addison-Wesley, 1997), where Mark Harrison and Michael McLennan describe a client/server system that uses Tcl as a protocol. The client sends the server a Tcl command to evaluate, and includes a response template to be filled in and returned to the client. When the client receives the filled-in response, it evaluates the response to update its state as needed. In this way, the server can be used by a variety of clients with vastly different needs and the protocol is not hampered by fixed responses.

For instance, consider a server that accepts requests in the form of two-element lists. The first element of the list is a command to evaluate, the second element a response template. The server evaluates the first element, replaces the value for a token in the second element, and sends the second element back to the client. Neglecting issues about security, error recovery, and the like, the heart of the server might look like Listing Two. A test console might exercise the server as in Listing Three and a GUI interface might execute code like Listing Four. The server's actions are identical in both instances, but the client-side requirements and actions are very different.

While the HyperCDS server evaluates SKILL (not Tcl), this solution maps well onto the problem and satisfies the requirement for asynchronous evaluation. In other words, instead of a client application saying to the interface, "Give me this data now," it says, "Get this data and here is what I'm going to do with it when it arrives."

Refinement

Unfortunately, the SKILL evaluation channels provided by hiSkillProcess give little control over the format of responses; you have no opportunity to parse the command and response template from requests or to put values into the response template. However, there is another function for starting client processes: hiBeginProcess (later uiBeginProcess) provides robust client/server I/O while providing more programmer control over transactions.

Together with functions for evaluating SKILL functions explicitly, hiBeginProcess is the foundation of the communications core of HyperCDS. When a client is started with hiBeginProcess, you set up the event handlers as in Listing Five for the client's standard I/O channels. When starting a child process with hiBeginProcess, you specify handlers for the child's stdout and stderr channels. These handlers execute asynchronously from user interactions with the Cadence system and execution of SKILL programs. In our case, we use these handlers to provide a reporting mechanism for client messages and to perform interpretation of SKILL expressions.

tclEcho is a function that echoes its argument to the Cadence command interpreter window (CIW) so that the Tcl application can report errors by writing to stderr; see Listing Six. On the other hand, tclInterp is somewhat more complicated. Listing Ten (available electronically; see "Resource Center," page 5) is the function stripped of some error processing and limit handling (the complete function is also available electronically; see Listing Eleven). The client sends an expression to the server via stdout and receives the results of evaluating that expression on stdin, rather like a SKILL RPC mechanism. The server side breaks the received string into one or more requests, breaks each request into a command and response template, evaluates the command, fills in the template, and sends the filled-in response back to the client.

Supportability

As you'd hope, we did build a little insurance into the system. On the SKILL side, for instance, we allow for tracing transactions. At strategic points throughout the SKILL code, we include clauses like Listing Seven.

When the global variable serverTrace is set, its integer value determines how detailed a level of tracing is recorded in the CIW log. In retrospect, this might be more flexible if we'd done something like Listing Eight, which could be used like:

(serverTrace 1 "tclInterp got <<%s>>" t_data)

Still, the system is in place and works.

On the client side, we provide for tracing data exchange. Because we are more comfortable with Tcl, the facility is somewhat more sophisticated. First, when we initialize communication with the SKILL server, we specify a logging command to be invoked when interesting things happen in the interface. The log command can be straightforward:

skill::ilInit -logCmd [list puts stderr]

Listing Twelve (available electronically) is cdsLog, the actual proc used as an interface logging command in HyperCDS. It manages logging to a file and a widget for display. The first part of the proc checks to see if a global variable has been set to specify file logging. If so, it checks to see if the file is already open; if not, it opens it. Finally, it records the interface event to the file. (While it would be possible to set up a proc to close the file on exit, we rely on Tcl's assurance that it will flush and close when the process terminates.)

The second part of cdsLog handles displaying the log in a Tk text widget. Because logging may begin a relatively long time before the UI is ready to display the log, the log lines are buffered in a global variable until the text widget exists. When the widget exists, the buffer's contents are inserted in the widget and then unset. Thereafter, each time a new line is logged, it is added to the end of the widget's contents.

Under normal circumstances, the bottom of the log should remain in view and the contents scrolled up as each line is added. However, the log can be quite long and it is not uncommon to scroll back and view contents early in the log. To keep from frustrating users by resetting the view to the end on each update, we check to see what part of the log's contents are visible and only update the view if the bottom of the log was already at the bottom of the window.

The log is really only a debugging aid and we don't want to bother users with it most of the time. We could add a control or key binding in the HyperCDS GUI to open the log window, but while we are more comfortable in Tcl, HyperCDS users are generally more comfortable in Cadence and SKILL. Ideally, there would be a SKILL command that could be invoked in the Cadence CIW, which would tell the Tcl/Tk-based UI to open the log.

Recall that there is a Tcl interpreter waiting in the client to evaluate responses to server requests. Because of the asynchronous nature of the interface, that Tcl interpreter doesn't know where the incoming command came from. You can take advantage of this by sending a command from SKILL to Tcl to open the log window. A small wrapper function (see Listing Nine) on the SKILL side takes care of hiding the details of the communication from users.

Finishing Touches

The tclNotify function wraps up to the SKILL-to-Tcl/Tk integration. The Cadence environment provides for a callback mechanism so SKILL-based tools can interact with — and even stop — the process of shutting down the Cadence system. Using tclNotify, you can register a callback that asks the Tcl/Tk GUI if it's okay to shut down. Listing Thirteen (available electronically) presents ptiExitBefore, which implements this technique. It begins by clearing a global variable used in the handshaking, sends the Tcl side of the interface a command to evaluate, and waits for the SKILL global to be set. The trick is that the arguments to the Tcl command are SKILL expressions. If the Tcl/Tk side determines that it is okay to exit, it sends back its first argument. If it's not okay, it sends back the second argument. When one or the other expression arrives, it is evaluated in a separate thread from the loop that's waiting for the global to be set. Evaluating the expression sets the global to true or false (t or nil). This, in turn, controls the return value of the exit callback. Voilá, the Tcl/Tk GUI controls the exit of the SKILL process.

By exploiting the common aspects of both languages and the strengths of each, we have a more user friendly, more functional, and more maintainable product.

DDJ

Listing One

proc ilEval { expression } {
    puts file3 $expression
    return [read file4]
}

Back to Article

Listing Two

# Get a request from the client
set request [read $clientIn]
# Parse the command and response template
set command [lindex $request 0]
set response [lindex $request 1]
# Fill in the response and return it to the client
puts $clientOut [fillTemplate $response [eval $command]]

Back to Article

Listing Three

# Set a value in the server, no response needed
puts $server [list {set a 1} {}]
# Get the value back to confirm
set responseAction "The value of a is <<%r>>"
puts $server [list {set a} $responseAction]

Back to Article

Listing Four

# When the update button is clicked, get the value of the named variable, 
# and update the value entry with it
set responseAction "$valueEntry select 0 end; $valueEntry insert end %r"
$updateButton configure -command \
        puts $server [list [list set [$variableEntry get]] $responseAction]

Back to Article

Listing Five

(setq tclProcess
                  (hiBeginProcess cmdLine     ;; Command
                                  ""          ;; Host name
                                  'tclInterp  ;; stdout handling
                                  'tclEcho    ;; stderr handling
                                  'tclDone    ;; post-func
                                  ))

Back to Article

Listing Six

;; Echo data received on stderr from child to CIW.
;; This allows Tcl clients to put messages in the CIW with
;;     [puts stderr ...].
(defun tclEcho (x_childID t_data)
    (printf "%s\n" t_data)
    );;tclEcho

Back to Article

Listing Seven

(if (and (boundp 'serverTrace) 
             (geqp serverTrace 0))
                (printf "\n$$$ tclInterp got <<%s>>\n" t_data))

Back to Article

Listing Eight

(defun serverTrace (level format @rest args)
            (setq format (strcat "\n$$$ " format "\n"))
            (if (and (boundp 'serverTraceLevel)
                         (geqp serverTraceLevel level))
                 (if args
                     (eval (append (list 'printf format) args))
                     (printf format)))
           )

Back to Article

Listing Nine

(defun tclNotify (s)
    (if (and (boundp 'tclProcess)
             tclProcess) 
            (hiWriteChild tclProcess
                          (sprintf nil "skill::IlNotify %s\n" s))
            );; if
    );; tclNotify







Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.