Replies: 2 comments 5 replies
-
The set of possible solutions here reminds me of the discussion about For example, the policy that "any one undefined input means the entire cell is disabled" is manifestly false— |
Beta Was this translation helpful? Give feedback.
-
Reprising a previous discussion we had about this, our choices are: "par disallows any conflict" and "par allows every conflict". In the former, I am personally in favor of the second semantics and building a more restrictive version of the operator on top of it. |
Beta Was this translation helpful? Give feedback.
-
This has no immediate urgency, but with all this work on the interpreter, I got started thinking about nailing down Calyx's semantics. There are various "dark corners" that come up from time to time, and I thought it might be nice to collect them into one place so we can approach the difficult task of defining what we actually mean in each case (rigorously but informally).
Here are the questions that have been bouncing around:
add.left = reg0.out ? const0.out
. Imagine there are no other assignments toadd.left
(and thatadd.right
is connected to a constant). Whenreg0
holds1
, everything's fine, of course, and the adder invocation proceeds. But what happens whenreg0
holds0
?add.left
, that is never written? That seems way too limiting; surely we shouldn't be required to always have a meaningful value on both inputs to the adder.add.left
holds an all-zero bit pattern? That's a little more sensible, but it is not really realistic about what's going on in the hardware: it would require extra circuitry to enforce that unproven signals always go back to zero. And as a practical matter, we would like simulations to be able to distinguish between meaningless values and meaningful zeroes.add
cell to be "inactive" because one of its ports is undriven, and so theadd.out
port should also be considered undefined. But what is the policy for determining what happens to components in these situations—is it always "any one undefined input means the entire cell is disabled," or do components get to define their own policies? If the latter, do they observe a special "undefined" value on their port? Are we getting dangerously close to Verilog'sX
at this point?par
?par
?par
? If I write to a register in one branch and read from it in the other branch, when (if ever) does the read see the result of the write?go
anddone
required to be "high," etc.? The questions are already well summarized in Precise go/done interface #405.go
/done
interface is fundamentally sequential. This seems like a shame and implies that we either need (a) a special case for defining combinational components, or (b) a way to generalize thego
/done
contract to work for combinational logic. My intuition leans toward (a), i.e., there needs to be two different interfaces for the two kinds of logic.Surely there are more, so we can keep adding to this list.
Here's a very rough proposal for how to think about defining the semantics. There are lots of questions and sketched-out parts in here, but hopefully it's a useful starting point.
Setup
A component definition consists of port declarations, a set of cells, a set of groups, and a control program.
A cell is an instance of a component that includes state, which takes the form of a set of subcells (the prototypes for which come from the component definition). Cell state thus forms a tree. The leaves of the tree are primitive cells, whose state is an arbitrary object instead of a set of subcells. (We will use normal cell to mean a cell that is not a primitive cell.)
The semantics for component evaluation will involve an environment, which maps port names to bit-vector values. The domain of an environment for a given cell is (a subset of) the union of all ports on the component definition itself and on all subcells. For example, consider this Calyx component:
…where the
std_add
component has the portsleft
,right
, andout
. The environment for any cell that is an instance offoo
could contain the port namesinp
,outp
,add.left
,add.right
, andadd.out
. (We use dot notation to distinguish identically-named ports on different subcells.) The domain for any environment is a subset of this set, i.e., environments are partial maps for this domain.Program Semantics
There is a distinguished top-level (main) component, which must have no ports. Program execution begins with an initial state where all primitive (leaf) cells get their associated opaque initial state and we build up the rest of the component state tree normally from there up to the main component.
We then begin component execution with the main component.
Component Semantics
To execute a component, we need input port values. For the main component, however, there are no input ports. [TK: Should port values be allowed to change over time? Currently saying no; they are fixed at the start.]
Execution proceeds iteratively according to the control program. In the absence of
par
, this execution is a straightforward recursive traversal of the control statement tree.par
is more complicated. [TK: Actually say more aboutpar
.]At the leaves of the tree, there are group activations. Execution for those proceeds as listed in the next section, using the state rooted at the current component and the component's port values. (There is no environment as input to group evaluation, which always starts with a "fresh" environment. It is impossible to communicate between group invocations via the environment.)
Group Semantics
To run a group using a given state, we iterate cycle evaluation, which is described in the next section. Cycle evaluation produces an environment and possibly mutates the state tree.
Group execution repeatedly executes the cycle evaluation procedure until the resulting environment maps
<group>[done]
to 1.[TK: Somewhere in here, we need to "advance" the cycle-level execution of sequential subcells. The policy is something like "for every subcell
C
, ifC.go
is 1 in the result environment of a cycle evaluation, then run cycle evaluation forC
" but I'm not entirely sure yet...]Cycle Semantics
Evaluating a single cycle for a group consists of these steps:
x = c ? e
, evaluatec
in E. It must produce a 1-bit value. If the result is 1, then evaluatee
in E. Then assignx
in F to the result ofe
. If eitherc
ore
references a value that is undefined in E during this process, then the assignment "fails" and no change is made to F.out
value using the stored state and adders produce anout
value using their input values.When E stops changing (i.e., E equals F at the end of the inner evaluation loop), cycle evaluation has finished. (Legal Calyx programs must make this inner loop terminate in a finite number of iterations; divergence here indicates a "combinational cycle.")
Beta Was this translation helpful? Give feedback.
All reactions