A simple inverter

ACT is used to describe a hierarchical collection of processes that together implement a circuit. Each process is an independent entity, and operates concurrently with all the other processes in the system. Circuits are also processes, and the physical implementation of two circuits operate concurrently.

An inverter is a very simple process that has a one-bit digital input and one bit digital output. The following specifies a process that corresponds to one inverter.

defproc inverter (bool? i; bool! o)
{ 
  prs {
    i -> o-
   ~i -> o+
  }
} 

Let's start with the statements

 i -> o-
~i -> o+

These statements are two production rules. (prs documentation). If you have designed asynchronous circuits, then this terminology should be familiar. A production rule behaves in the following way:

  • The left hand side is a Boolean expression
  • The right hand side is a signal transition
  • There is an arrow between the two
  • If the left hand side is true, then (after some delay) the right hand side is executed eventually

The ~ symbol is used to indicate negation; the + means set the signal high; the - means set the signal low.

In this example, the variables i and o are Booleans. It should be clear that the combination of those two rules corresponds to an inverter.

The left hand side for o+ is the Boolean expression for the pull-up network for o; the left hand side for o- is the Boolean expression for the pull-down network for o.

This is a combinational gate, which means either the pull-up or pull-down network is always conducting (alternatively, the OR of the Boolean expressions for the pull-up and pull-down is always true). In this case, the pull-up and pull-down networks are complements of each other, so you only need specify one. The arrow can be used so that one rule can be used to specify both the pull-up and pull-down network as follows:

defproc inverter (bool? i; bool! o)
{ 
  prs {
    i => o-
  }
} 

The prs { … } specifies the production rule body for the process. There are other bodies that can also be included, and they use different keywords instead of prs but the same basic structure.

The entire process is wrapped in the process definition. defproc is used to define a process. Here, we name the process inverter. The signals i and o are in the port list for the process, which means they can be accessed from outside the process. They are declared as bools (Booleans), and the ? means that i is an input, and the ! means that o is an output.

This fragment of ACT simply defines a process named inverter. To actually create an inverter, we have to instantiate the process. The defproc can be viewed as defining the inverter type. We can use inverter just like we used bool, and create an inverter called inv using the following:

inverter inv;

Next, we can simulate the inverter using a production-rule simulator. We have provided prsim for production rule simulation as part of the ACT tools.

Simulating with prsim

The complete example is:

defproc inverter (bool? i; bool! o)
{ 
  prs {
    i => o-
  }
} 
 
inverter inv;

If the file above is called test_inv.act, a production rule file can be created from the ACT file by:

% aflat test_inv.act > test_inv.prs

The output file is the following:

"inv.i"->"inv.out"-
~"inv.i"->"inv.out"+

Note that ACT uses . (like standard programming languages) as a separator between the name of the instance and internal nodes within it. (Note that magic, irsim use / as a separator; Xyce uses colon).

If the file is saved as test_inv.prs, it can be simulated using the production rule simulator called prsim.

% prsim test_inv.prs    

(Prsim) initialize 
(Prsim) watch inv.i
(Prsim) watch inv.o
(Prsim) status X
(Prsim) set inv.i 0
(Prsim) cycle
(Prsim) set inv.i 1
(Prsim) cycle

prsim will check if the production rules being run are stable (i.e. hazard/glitch-free) and non-interfering (i.e. that pull-up and pull-down networks are not on simultaneously). It doesn't check all possible delay configurations, but just reports errors if it observes unstable or interfering production rules while the simulation is running. Try help as a prsim command to see the range of commands supported by prsim.

One of the useful features of prsim is that it can automatically randomize the delays of production rule firings. To do this, use

(Prsim) random

After this command, all delays are randomized. This is a useful test to see if your production rules are stable and non-interfering. If prsim finds that a production rule is unstable, it sets its output to X (for undefined). These Xs can propagate through the circuit.