A simple inverter

In this first example we will build and simulate an inverter and touch the basic language elements and tools needed for this (production rules, prs block, defproc incl. in/out, instantiation, aflat and prsim)

make sure you installed act and added it to your path please follow install

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.

input i output o
1 (TRUE) 0 (FALSE)
0 (FALSE) 1 (TRUE)
defproc inverter (bool? i; bool! o)
{ 
  prs {
    i -> o-
   ~i -> o+
  }
} 

production rules

Let's start with the statements

 i -> o-
~i -> o+

These statements are two production rules. (prs documentation). They are embedded into the ACT hirachy, therefor the file type is act. 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.

so in other words for i → o- if the expression/input i is evaluated true the output o will transmission to FALSE indicated by o- and for ~i → o+ if i is false the expression ~i is evaluated true and the output o will transmission to TRUE indicated by o.

This is a combinational gate, which means either the pull-up or pull-down network is always conducting, so that one and only one rule is always active (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, but there act saves you so you just have to write one. you just need to tell act that one rule is part of a complementary set and it will complete the pair for you. 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-
  }
} 

in this case act will generate the ~i → o+ for you. For c elements act gives you also a third way to specify a mirrored rule set with #> but we will see in a later tutorial how to use it and it is not important to understand or use it now.

as a quick recap you have (simple rule), (complementary rule / combinational logic) and #> (c-element rule, not important at the moment)

processes

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;

Simulating with prsim

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.

The complete above example is:

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

inverter inv;

the prsim simulator can only read pure production rule files, and not the hierarchical act files with embedded production rules we wrote before. So to simulate we need to convert our definition to plain and flat production rules. 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

aflat will now take you act definition and build it in pure production rules, so eg it will expand your as described earlier. If you made a syntax mistake in your act definition, aflat will tell you! So you can also use aflat in the beginning to check your files for mistakes.

The the pure production rule 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. So lets do that now.

% prsim test_inv.prs    

(Prsim) initialize 

(Prsim) shows you that you need to enter a command, the output of the tool is displayed without it. after you entered initialize the sim is set up, but we still want to see what happens so we need to add some watch <signal>

(Prsim) watch inv.i
(Prsim) watch inv.o

we can also use prsim to show us all signals that are 1 or X with status [0|1|X|…]

(Prsim) status 1

no output but for

(Prsim) status X
---------------------------------------
inv.o inv.i

it shows us that our signals are not set. so lets do this with set <signal> [0|1|…]

(Prsim) set inv.i 0

to see what will change in the circuit we now have to let the simulation run a cycle with

(Prsim) cycle
---------------------------------------
	         0 inv.i : 0
	        10 inv.o : 1  [by inv.i:=0]

so the first line 0 inv.i : 0 shows that out set 0 command (inv.i : 0) from above was applied at step 0 (0 in…), the second line 10 inv.o : 1 [by inv.i:=0] shows us that the inverter inverted the input: from left to right at timesept 10 the output inv.o switched to 1 : 1, this swich was initated/caused by the input beeing set to 0 [by inv.i:=0]

the standard gate delay in prsim (from input to output of the inverter) is on default 10 steps thats why the steps advance by 10.

lets also test the other direction:

(Prsim) set inv.i 1
(Prsim) cycle
---------------------------------------
	         10 inv.i : 1
	         20 inv.o : 0  [by inv.i:=1]

so again the first line 10 inv.i : 1 reads at timesept 10 the input inv.i switched to 1 : 1 and the second line 20 inv.o : 0 [by inv.i:=1] reads at timesept 20 the input inv.i switched to 0 : 0 , this switch was initated/caused by the input set to 1 [by inv.i:=1]

as a summery 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.