Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Pull request Compare This branch is 164 commits ahead, 4904 commits behind master.

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.


 * Copyright (c) 2001 Stephen Williams (
 *  $Id: README.txt,v 2005/02/23 18:37:52 steve Exp $


The VVP simulator takes as input source code not unlike assembly
language for a conventional processor. It is intended to be machine
generated code emitted by other tools, including the Icarus Verilog
compiler, so the syntax, though readable, is not necessarily
convenient for humans.


The source file is a collection of statements. Each statement may have
a label, an opcode, and operands that depend on the opcode. For some
opcodes, the label is optional (or meaningless) and for others it is

Every statement is terminated by a semicolon. The semicolon is also
the start of a comment line, so you can put comment text after the
semicolon that terminates a statement. Like so:

	Label .functor and, 0x5a, x, y  ; This is a comment.

The semicolon is required, whether the comment is there or not.

Statements may span multiple lines, as long as there is no text (other
then the first character of a label) in the first column of the
continuation line.


Before any other non-commentary code starts, the source may contain
some header statements. These are used for passing parameters or
global details from the compiler to the vvp run-time. In all cases,
the header statement starts with a left-justified keyword.

* :module "name" ;

This header statement names a vpi module that vvp should load before
the rest of the program is compiled. The compiler looks in the
standard VPI_MODULE_PATH for files named "name.vpi", and tries to
dynamic load them.

* :vpi_time_precision [+|-]<value>;

This header statement specifies the time precision of a single tick of
the simulation clock. This is mostly used for display (and VPI)
purposes, because the engine itself does not care about units. The
compiler scales time values ahead of time.

The value is the size of a simulation tick in seconds, and is
expressed as a power of 10. For example, +0 is 1 second, and -9 is 1
nanosecond. If the record is left out, then the precision is taken to
be +0.


Labels and symbols consist of the characters:


Labels and symbols may not start with a digit or a '.', so that they
are easily distinguished from keywords and numbers. A Label is a
symbol that starts a statement. If a label is present in a statement,
it must start in the first text column. This is how the lexical
analyzer distinguishes a label from a symbol. If a symbol is present
in a statement, it is in the operand. Opcodes of statements must be a

Symbols are references to labels. It is not necessary for a label to
be declared before its use in a symbol, but it must be declared
eventually. When symbols refer to functors, the symbol represents the
vvp_ipoint_t pointer to the output. (Inputs cannot, and need not, be
references symbolically.)

If the functor is part of a vector, then the symbol is the
vvp_ipoint_t for the first functor. The [] operator can then be used
to reference a functor other then the first in the vector.

There are some special symbols that in certain contexts have special
meanings. As inputs to functors, the symbols "C<0>", "C<1>", "C<x>"
and "C<z>" represent a constant driver of the given value.



Parameters are named constants within a scope. These parameters have a
type and value, and also a label so that they can be referenced as VPI

The syntax of a parameter is:

	<label> .param <name>, <type>, <value>;

The <name> is a string that names the parameter. The name is placed in
the current scope as a vpiParameter object. The <type> is one of the

	real          -- The parameter has a real value
	string        -- The parameter has a string value
		      -- The parameter is a vector, with specified
		         indices. The <s> is s or u for signed or

The value, then, is appropriate for the data type. For example:

	P_123 .param "hello", string, "Hello, World.";


A functor statement is a statement that uses the ``.functor''
opcode. Functors are the basic structural units of a simulation, and
include a type (in the form of a truth table) and up to four inputs. A
label is required for functors.

The general syntax of a functor is:

	<label> .functor <type>, symbol_list ;

The symbol list is 4 names of labels of other functors. These connect
inputs of the functor of the statement to the output of other
functors. If the input is unconnected, use a C<?> symbol instead. The
type selects the truth lookup table to use for the functor
implementation. Most of the core gate types have built in tables.

The initial values of all the inputs and the output is x. Any other
value is passed around as run-time behavior. If the inputs have C<?>
symbols, then the inputs are initialized to the specified bit value,
and if this causes the output to be something other then x, a
propagation event is created to be executed at the start of run time.

The strengths of inputs are ignored by functors, and the output has
fixed drive0 and drive1 strengths. So strength information is
typically lost as it passes through functors.

Almost all of the structural aspects of a simulation can be
represented by functors, which perform the very basic task of
combining up to four inputs down to one output.


A UDP statement either defines a User Defined Primitive, or
instantiates a previously defined UDP by creating a UDP functor.  A
UDP functor has as many inputs as the UDP definition requires.

UDPs come in sequential and combinatorial flavors.  Sequential UDPs
carry an output state and can respond to edges at the inputs.  The
output of a combinatorial UDPs is a function of its current inputs

The function of a UDP is defined via a table.  The rows of the table
are strings which describe input states or edges, and the new output
state.	Combinatorial UDPs require one character for each input, and
one character at the end for the output state.	Sequential UDPs need
an additional char for the current state, which is the first char of
the row.

Any input transition or the new state must match at most one row (or
all matches must provide the same output state).  If no row matches,
the output becomes 1'bx.

The output state can be specified as "0", "1", or "x".	Sequential
UDPs may also have "-": no change.

An input or current output state can be

	"1": 1
	"0": 0
	"x": x
	"b": 1, 0
	"h": 1, x
	"l": 0, x
	"?": 1, 0, x

For Sequential UDPs, at most one input state specification may be
replaced by an edge specification.  Valid edges are:

	"*": (??)	"_": (?0)	"+": (?1)	"%": (?x)
	"P": (0?)			"r": (01)	"Q": (0x)
	"N": (1?)	"f": (10)			"M": (1x)
	"B": (x?)	"F": (x0)	"R": (x1)

	"n": (1?) | (?0)
	"p": (0?) | (?1)

A combinatorial UDP is defined like this:

	<type> .udp/comb "<name>", <number>, "<row0>", "<row1>", ... ;

<type> is a label that identifies the UDP.  <number> is the number of
inputs.	 "<name>" is there for public identification.  Sequential UDPs
need an additional initialization value:

	<type> .udp/sequ "<name>", <number>, <init>, "<row0>", "<row1>", ... ;

<init> is the initial value for all instances of the UDP.  We do not
provide initial values for individual instances.  <init> must be a
number 0, 1, or 2 (for 1'bx).

A UDP functor instance is created so:

	<label> .udp  <type>, <symbol_list> ;

Where <label> identifies the functor, <type> is the label of a UDP
defined earlier, and <symbol_list> is a list of symbols, one for each
input of the UDP.


A variable is a bit vector that can be written by behavioral code (so
has no structural input) and propagates its output to a functor. The
general syntax of a variable is:

	<label> .var   "name", <msb>, <lsb>;
	<label> .var/s "name", <msb>, <lsb>;

The "name" is the declared base name of the original variable, for the
sake of VPI code that might access it. The variable is placed in the
current scope. The variable also has a width, defined by the indices
for the most significant and lest significant bits. If the indices are
equal (normally 0) the vector has width of one. If the width is greater
then one, a contiguous array of functors is created and the value of
the label is the address of the least significant bit.

A variable does not take inputs, since its value is set behaviorally
by assignment events. It does have output, though, and its output is
propagated into the net of functors in the usual way.

Therefore, the .var statement implicitly also creates .functors
addressed by the label of the variable. It is in fact the functors
that behavioral code reads when the value of the variable (or net) is
read by behavioral code. If the .var represents a vector of .functors,
the index of the LSB is always, from the perspective of vvp, ZERO. The
<msb>,<lsb> details are there only for the benefit of VPI support.

The variable .functor implicitly has two inputs. The first is the
value that gets set by assignments.  The second input is connected to
the driving expression of a procedural continuous assignments.
Variable functors have an extra internal bit that tells if a
procedural continuous assignment is active.  The %cassign opcode
connects and activates the procedural continuous assignment.  The
%deassign opcode disconnects and deactivates it.

The variable statement also creates a VPI object of the appropriate
type. See the vpi.txt file for details about that object. The msb and
lsb values are set from the parameters of the .var or .var/s, and the
vpiReg is marked unsigned for .var, or signed for .var/s

Note that nets in a design do not necessarily have a specific functor
or object allocated to them. Nets are just something that behavioral
code can read, so it is enough to give to the behavioral code the
vvp_ipoint_t object of the .functor that drives the net.


A net is similar to a variable, except that a thread cannot write to
it (unless it uses a force) and it is given a different VPI type
code. The syntax of a .net statement is also similar to but not
exactly the same as the .var statement:

	<label> .net   "name", <msb>, <lsb>, <symbols_list>;
	<label> .net/s "name", <msb>, <lsb>, <symbols_list>;

Like a .var statement, the .net statement creates a VPI object with
the basename and dimensions given as parameters, but unlike a .var
statement, it creates no functors. The symbol list is a list of
functors that feed into each bit of the vector, and the vpiHandle
holds references to those functors that are fed it.

The <label> is required and is used to locate the net object that it
represents. This label does not map to a functor, so only references
that know they want to access .nets are able to locate the symbol. In
particular, this includes behavioral %load and %wait instructions. The
references to net and reg objects are done through the .net label
instead of a general functor symbol. The instruction stores the
functor pointer, though.


Three types of memory statement perform (1) creation of a memory, (2)
connecting a read port to an existing memory, and (3) initializing the
memory's contents.

       <label> .mem "name", <msb>,<lsb>, <last>,<first> ... ;

The pair of numbers <msb>,<lsb> defines the data port width.  The pair
<last>,<first> defines the address range.  Multiple address ranges are
allowed for multidimensional indexing.

Procedural access to the memory references the memory as single array
of bits.  For this purpose, the number of bits in a memory word is
rounded up to the next multiple of four.  That is, for an 18 bit wide
data bus, bit 0 is the lsb of the first word, bit 20 is the lsb of the
second word.

Structural read access is implemented in terms of address and data
ports.  The addresses applied to the address port are expected to be
within the ranges specified, not based at zero.

A read port is a vector of functors that is wide enough to accept all
provided address bits and at least as wide as the requested subset of
the data port.

	<label> .mem/port <memid>, <msb>,<lsb>, <aw>, <addr_bits> ;

<label> identifies the vector of output functors, to allow connections
to the data output.  <memid> is the label of the memory. <msb>,<lsb>
select a part of the data width.  These are not relative to the data
port range defined for the memory.  The LSB of the data word is here
referred to as 0, regardless to the range specified in the memory
definition.  <addr_bits> is a list of symbols, which connect to the
address inputs of this port.  There are <aw> address bits (<aw> may
become a list of numbers, when multi-index memory ports become

Any address change, or any change in the addressed memory contents is
immediately propagated to the port outputs.

A write port is a superset of a read port.  It is a vector of functors
that is wide enough to accept the address bits, an event input, a
write enable input, and the data inputs.

	<label> .mem/port <memid>, <msb>,<lsb>, <aw>, <addr_bits>,
                          <event>, <we>, <data> ;

<event> is an event functor that triggers a write, if the <we> input
is true.  <data> is a list of symbols that connect to the data input
port.  For asynchronous transparent write operation, connect
<event> to C<z>, the RAM will transparently follow any changes on
address and data lines, while <we> is true.

There is no Verilog construct that calls for a structural write port
to a memory, but synthesis may ask for lpm_ram_d[pq] objects.

To initialize a memory, use:

	        .mem/init <memid>[<start>],
			val val val ...

<memid> is the label of the memory.  [<start>] is optional,
identifying the bits location where the first value is loaded.
<start> must be a multiple of four, and defaults to zero, if omitted.

The values are decimal or hex numbers (0x prefix), which may be
optionally separated by comma ','.  Each number in the range 0..256
initializes four memory bits.  Two bits form each byte for each memory
bit, in the usual encoding.

Procedural access to the memory employs an index register to address a
bit location in the memory, via the commands:

	%load/m   <bit>, <memid> ;
	%set/m    <memid>, <bit> ;
	%assign/m <memid>, <delay>, <bit> ;

The memory bit is addressed by index register 3.  The value of
register 3 is the index in the memory's bit space, where each data
word occupies a multiple of four bits.


Threads need to interact with the functors of a netlist synchronously,
as well as asynchronously. There are cases where the web of functors
needs to wake up a waiting thread. The web of functors signals threads
through .event objects, that are declared like so:

	<label> .event <type>, <symbols_list>;
	<label> .event "name";

This event statement declares an object that a %wait instruction
can take as an operand. When a thread executes a %wait, it puts
itself in the notification list of the event and suspends. The
<symbols_list> is a set of inputs that can trigger the event.

The <type> describes the conditions needed to trigger the event. It
may be posedge, negedge or edge. If the type is instead a "name"
string, then this is a named event which receives events by the %set
instruction instead of from the output of a functor.

If the event has inputs (a requirement unless it is a named event)
then it has up to 4 symbols that address functors. The event then
detects the appropriate edge on any of the inputs and signals when the
event is true. Normally (in Verilog) a posedge or negedge event only
watches a single bit, so the generated code would only include a
single symbol for the addressed bit. However, if there are several
events of the same edge in an event OR expression, the compiler may
combine up to 4 into a single event.

If many more events need to be combined together (for example due to
an event or expression in the Verilog) then this form can be used:

	<label> .event/or <symbols_list>;

In this case, the symbols list all the events that are to be combined
to trigger this event. Only one of the input events needs to trigger
to make this one go.


Verilog includes some scalar word types available to the programmer,
including real variables, and possible extension types that the code
generator can transparently use. Variables of these special types are
declared with .word statements:

	<label> .word <type>, "vpi name";

The <type> values supported are listed below. The vpi name is the base
name given to the VPI object that is created.

        real  - represents a double precision real variable.


Resolver statements are strength-aware functors with 4 inputs, but
their job typically is to calculate a resolved output using strength
resolution. The type of the functor is used to select a specific
resolution function.

	<label> .resolv tri,  <symbols_list>;
	<label> .resolv tri0, <symbols_list>;
	<label> .resolv tri1, <symbols_list>;


A force statement creates functors that represent a Verilog force

	<label>	.force <signal>, <symbol_list>;

The symbol <signal> represents the signal which is to be forced.  The
<symbol_list> specifies the bits of the expression that is to be
forced on the <signal>.  The <label> identifies the force functors.
There will be as many force functors as there are symbols in the

To activate and deactivate a force on a single bit, use:

	%force	<label>, <width>;
	%release <signal>;

<label>/<width> is the label/width of a vector of force functors.
<signal> is the label of the functor that drives the signal that is
being forced.


The various Verilog arithmetic operators (+-*/%) are available to
structural contexts even though they do not fit into nice neat
functors. These operators are not in general bitwise, so special
measures are needed to make them work in a functor environment. We
create special statement types for the various arithmetic operators.

	<label> .arith/sub  <wid>, <symbols_list>;
	<label> .arith/sum  <wid>, <symbols_list>;
	<label> .arith/mult <wid>, <symbols_list>;
	<label> .arith/div  <wid>, <symbols_list>;
	<label> .arith/mod  <wid>, <symbols_list>;

Addition is represented by the .arith/sum statement. This creates an
array of functors based at the label. The width of the array is given
by <wid>, and the <symbols_list> connects the inputs.

The sum can add together up to 4 operands, specified in the
<symbols_list> one bit at a time. All the bits of the first operand
(lsb first) are listed, then the bits of the second, and so on. The
number of symbols must be an even multiple of the width of the operator.

Subtraction is similar to addition, except that the 2nd, 3rd and 4th
vectors are subtracted from the first.


The arithmetic statements handle various arithmetic operators that
have wide outputs, but the comparators have single bit output, so they
are implemented a bit differently. The syntax, however, is very

	<label> .cmp/eq  <wid>, <symbols_list>;
	<label> .cmp/ne  <wid>, <symbols_list>;
	<label> .cmp/ge  <wid>, <symbols_list>;
	<label> .cmp/gt  <wid>, <symbols_list>;
	<label> .cmp/ge.s <wid>, <symbols_list>;
	<label> .cmp/gt.s <wid>, <symbols_list>;

Whereas the arithmetic statements create an array of functor outputs,
there is only one useful functor output for the comparators. That
functor output is 1 if the comparison is true, 0 if false, and x
otherwise. The plain versions do unsigned comparison, but the ".s"
versions do signed comparison. (Equality doesn't need to care about


Variable shifts in structural context are implemented with .shift

	<label> .shift/sl <wid>, <symbols_list>;

The shifter has a width that defines the number of outputs and the
number of data inputs. The first <wid> symbols in the <symbols_list>
gives the input data. The remaining hold the shift value, lsb first.


The .ufunc statement defines a call to a user defined function.

	<label> .ufunc ;


Thread statements create the initial threads for a simulation. These
represent the initial and always blocks, and possibly other causes to
create threads at startup.

	.thread <symbol> [, <flag>]

This statement creates a thread with a starting address at the
instruction given by <symbol>. When the simulation starts, a thread is
created for the .thread statement, and it starts at the <symbol>
addressed instruction.

The <flag> modifies the creation/execution behavior of the
thread. Supported flags are:

	$push -- Cause the thread to be pushed in the scheduler. This
		 only effects startup (time 0) by arranging for pushed
		 threads to be started before non-pushed threads. This
		 is useful for resolving time-0 races.

* Threads in general

Thread statements create the initial threads of a design. These
include the ``initial'' and ``always'' statements of the original
Verilog, and possibly some other synthetic threads for various
purposes. It is also possible to create transient threads from
behavioral code. These are needed to support such constructs as
fork/join, named blocks and task activation.

A transient thread is created with a %fork instruction. When a
transient thread is created this way, the operand to the %fork gives
the starting address, and the new thread is said to be a child of the
forking thread. The children of a thread are pushed onto a stack of
children. A thread can have only one direct child.

A transient thread is reaped with a %join instruction. %join waits for
the top thread in the stack of children to complete, then
continues. It is an error to %join when there are no children.

As you can see, the transient thread in VVP is a cross between a
conventional thread and a function call. In fact, there is no %call
instruction in vvp, the job is accomplished with %fork/%join in the
caller and %end in the callee. The %fork, then is simply a
generalization of a function call, where the caller does not
necessarily wait for the callee.

For all the behavior of threads and thread parentage to work
correctly, all %fork statements must have a corresponding %join in the
parent, and %end in the child. Without this proper matching, the
hierarchical relationships can get confused. The behavior of erroneous
code is undefined.

* Threads and scopes

The Verilog ``disable'' statement deserves some special mention
because of how it interacts with threads. In particular, threads
throughout the design can affect (end) other threads in the design
using the disable statement.

In Verilog, the operand to the disable statement is the name of a
scope. The behavior of the disable is to cause all threads executing
in the scope to end. Termination of a thread includes all the children
of the thread. In vvp, all threads are in a scope, so this is how the
disable gains access to the desired thread.

It is obvious how initial/always thread join a scope. They become part
of the scope simply by being declared after a .scope declaration. (See
vvp.txt for .scope declarations.) The .thread statement placed in the
assembly source after a .scope statement causes the thread to join the
named scope.

Transient threads join a scope that is the operand to the %fork
instruction. The scope is referenced by name, and the thread created
by the fork atomically joins that scope. Once the transient thread
joins the scope, it stays there until it ends. Threads never change
scopes, not even transient threads.


The logic that a functor represents is expressed as a truth table. The
functor has four inputs and one output. Each input and output has one
of four possible values (0, 1, x and z) so two bits are needed to
represent them. So the input of the functor is 8 bits, and the output
2 bits. A complete lookup table for generating the 2-bit output from
an 8-bit input is 512 bits. That can be packed into 64 bytes. This is
small enough that the table should take less space then the code to
implement the logic.

To implement the truth table, we need to assign 2-bit encodings for
the 4-value signals. I choose, pseudo-randomly, the following

	1'b0  : 00
	1'b1  : 01
	1'bx  : 10
	1'bz  : 11

The table is an array of 64 bytes, each byte holding 4 2-bit
outputs. Construct a 6-bit byte address with inputs 1, 2 and 3 like

The input 0 2-bits can then be used to select which of the 4 2-bit
pairs in the 8-bit byte are the output:

	MSB -> zzxx1100 <- LSB

A complete truth table, then is described as 64 8-bit bytes.

The vvp engine includes truth tables for the primitive gate types, so
none needs to be given by the programmer. It is sufficient to name the
type to get that truth table.


Threads run executable code, much like a processor executes machine
code. VVP has a variety of opcodes for executable instructions. All of
those instructions start with '%' and go into a single address
space. Labels attached to executable instructions get assigned the
address of the instruction, and can be the target of %jmp instructions
and starting points for threads.

The opcodes.txt file has a more detailed description of all the
various instructions.


Given the above summary of the major components of vvp, some
description of their relationship is warranted. Functors provide a
structural description of the design (so far as it can be described
structurally) and these functors run independently of the threads. In
particular, when an input to a functor is set, it calculates a new
output value; and if that output is different from the existing
output, a propagation event is created. Functor output is calculated
by truth table lookup, without the aid of threads.

Propagation events are one of three kinds of events in vvp. They are
scheduled to execute at some time, and they simply point to the functor
that is to have its output propagated. When the event expires, the
output of the referenced functor is propagated to all the inputs that
it is connected to, and those functors in turn create new events if

Assignment events (the second of three types of events) are created
by non-blocking assignments in behavioral code. When the ``<='' is
executed (a %assign in vvp) an assign event is created, which includes
the vvp_ipoint_t pointer to the functor input to receive the value,
as well as the value. These are distinct from propagation events because:

	a) There is no functor that has as its output the value to be
	   assigned (this is how values get into the functor net in
	   the first place), and

	b) This allows for behavioral code to create waveforms of
	   arbitrary length that feed into a variable. Verilog allows
	   this of non-blocking assignments, but not of gate outputs.

The last type of event is the thread schedule event. This event simply
points to a thread to be executed. Threads are made up of a virtual
processor with a program counter and some private storage. Threads
can execute %assign instructions to create assignment events, and can
execute %set instructions to do blocking assignments. Threads can also
use %load to read the output of functors.

The core event scheduler takes these three kinds of events and calls
the right kind of code to cause things to happen in the design. If the
event is a propagate or assignment event, the network of functors is
tickled; if the event is a thread schedule, then a thread is run. The
implementation of the event queue is not important, but currently is
implemented as a ``skip list''. That is, it is a sorted singly linked
list with skip pointers that skip over delta-time events.

The functor net and the threads are distinct. They communicate through
thread instructions %set, %assign, %waitfor and %load. So far as a thread
is concerned, the functor net is a blob of structure that it pokes and
prods via certain functor access instructions.


The vvp program operates in a few steps:

	1) Initialization
		Data structures are cleared to empty, and tables are
		readied for compilation.

	2) Compilation
		The input file is read and compiled. Symbol tables are
		build up as needed, objects are allocated and linked

	3) Cleanup
		Symbol tables and other resources used only for
		compilation are released to reduce the memory

	4) Simulation
		Event simulation is run.

The initialization step is performed by the compile_init() function in This function in turn calls all the *_init() functions in
other parts of the source that need initialization for compile. All
the various sub-init functions are called <foo>_init().

Compilation is controlled by the parser, in parse.y. As the parser
reads and parses input, the compilation proceeds in the rules by
calling various compile_* functions. All these functions live in the file. Compilation calls other sections of the code as

When the parser completes compilation, compile_cleanup() is called to
finish the compilation process. Unresolved references are completed,
then all the symbol tables and other compile-time-only resources are
released. Once compile_cleanup() returns, there is no more use for the
parser for the function in

After cleanup, the simulation is started. This is done by executing
the schedule_simulate() function. This does any final setup and starts
the simulation running and the event queue running.


The vvp simulation engine is designed to be able to take as input a
compiled form of Verilog. That implies that there is a compiler that
compiles Verilog into a form that the vvp engine can read.

* Boolean logic gates

Gates like AND, OR and NAND are implemented simply and obviously by
functor statements. Any logic up to 4 inputs can be implemented with a
single functor. For example:

	and gate (out, i1, i2, i3);


	gate	.functor and, i1, i2, i3;

Notice the first parameter of the .functor is the type. The type
includes a truth table that describes the output with a given
input. If the gate is wider then four inputs, then cascade
functors. For example:

	and gate (out, i1, i2, i3, i4, i5, i6, i7, i8);


	gate.0	.functor and, i1, i2, i3, i4;
	gate.1	.functor and, i5, i6, i7, i8;
	gate	.functor and, gate.0, gate.1;

* reg and other variables

Reg and integer are cases of what Verilog calls ``variables.''
Variables are, simply put, things that behavioral code can assign
to. These are not the same as ``nets,'' which include wires and the

Each bit of a variable is created by a ``.var'' statement. For example:

	reg a;


	a	.var "a", 0, 0;

* named events

Events in general are implemented as functors, but named events in
particular have no inputs and only the event output. The way to
generate code for these is like so:

	a  .event "name";

This creates a functor and makes it into a mode-2 functor. Then the
trigger statement, "-> a", cause a ``%set a, 0;'' statement be
generated. This is sufficient to trigger the event.

 * Copyright (c) 2001 Stephen Williams (
 *    This source code is free software; you can redistribute it
 *    and/or modify it in source code form under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version.
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    GNU General Public License for more details.
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
Something went wrong with that request. Please try again.