The for loop generates n instances
of the rule. In any one instance, the
name x refers to the packet at the head
of the jth port in midports. The function computeRoute (not shown; a
simple numeric calculation) is applied
to get the index of the merge module
into which x should be forwarded,
which is done in the small conditional.
The rule also dequeues the packet.
Each rule expressing the switch’s
behavior hides much control detail:
˲ ˲ The rule is enabled only when a
packet is available on the rule’s upstream FIFO (because of guards on the
first and deq methods).
˲ ˲ The rule is enabled only when a
packet can be sent into its downstream
merge block (because of the guard on
enq). For example, it may be blocked
as a result of flow control.
˲ ˲ This last control condition is further nuanced by the fact that the downstream merge block is dynamically selected, depending on the packet, and,
therefore, only the enq guard of the selected block matters.
An even subtler control condition
arises out of conflict between two
rules. In the for loop, pairs of rules
enqueue into the same merge blocks.
This contention may require one of the
competing rules to be stalled (that is,
not enabled) to satisfy atomicity.
Why rules matter (continued). In
Verilog, all of the above control considerations manifest themselves as
explicit, visible control wires on the
physical input and output ports of
the merge modules, together with a
protocol on how to use those wires.
These are specified explicitly by the
designer, and require effort on a per-module basis. They are ad hoc in that
these control interfaces and protocols
vary from module to module, from
designer to designer, and even from
day to day for the same designer. The
protocol (semantics) is usually conveyed in accompanying text and timing diagrams and, as you might imagine, is often incomplete, ambiguous,
and sometimes wrong. When working
with rules, the compiler handles this
control complexity naturally, automatically, and formally.
This automation is especially important in accommodating change
(for fixing a bug, reacting to changing
specs, and so on). The mkMerge2×
1
module is a formal parameter, a black
box. One actual parameter may permit
both enq ports to be activated simultaneously, whereas another may not,
perhaps because one has separate
internal buffering, while the other
shares internal buffers. This affects
whether competing rules in mkXBar
can fire simultaneously or not (this
is another example of the transitivity
of control). With ad hoc control logic
(as in register-transfer level, or RTL), it
may not be able to accommodate such
a change without redesigning mkXBar, whereas with rules, the compiler
handles it.
Consider this analogy: in compiling software, imagine doing register
allocation by hand, and designing and
documenting an ad hoc calling convention for every function you write. It
is rather difficult and painful doing it
even once, but it’s much worse if the
source code changes because register
allocation has a transitive effect across
function boundaries, such as control
logic across hardware module boundaries. Just adding an argument to a
function may require redoing register
allocation for both the callers and the
callees, and this may have a ripple effect across multiple functions. Fortunately, compilers automate this, and
adding an argument to a function is
trivial for the programmer. Similarly,
Rules simplify writing source code and
modifying it; the compiler (synthesis)
figures out the required changes in
control logic.
Rules are part of a good DSL for
hardware description because they
are like state machine transitions,
familiar and intuitive to hardware
engineers. Rules, however, are more
profoundly powerful because they are
parallel and atomic.
Rules are part
of a good DsL
for hardware
description because
they are like state
machine transitions,
familiar and intuitive
to hardware
engineers. Rules,
however, are more
profoundly powerful
because they are
parallel and atomic.
synthesis into hardware
Historically, rules in programming
and specification languages were concerned just with functionality and not
performance; there is only an abstract
notion of time in the sense that one
rule may fire before another. In hardware design, performance is usually
a central requirement, not merely an
implementation detail; so the computation model must make this visible to
the designer.
Space does not permit a full de-