• No results found

Communication primitives

In document A verified compiler for Handel-C (Page 104-108)

3.4 Chapter summary

4.2.3 Communication primitives

Input and output in Handel-C have a complex behaviour that implements blocking semantics by means of attempting the communication at the beginning of their execution. If the communication is possible, the value provided as input gets transmitted over internal buses and assigned to the variable waiting for it at the other end of the channel.

As expected, the transfer of values only takes place provided the processes at both ends of the channel are ready to communicate. The arbitration mechanism in charge of detecting that both processes are ready to communicate is defined in terms of dedicated wires that signal a process’ readiness to perform an input/output operation. In section 4.1 we took advantage of this operational intuition behind the communication primitives to define the semantics of the input and output constructs.

Our first intention when defining the reasoning language for these constructs was to use an approach based on the wires used to signal and control the input and output mechanisms. Unfortu- nately, the application of this idea to individual input/output commands in the program leads to a

non-functioning program. To see why, consider the case where we apply the replacement to one of the input commands in the program. Immediately after this, all other output commands in the pro- gram will stop being able to interact with it as they operate at a different level of abstraction (i.e., they do not refer to wires, they operate at the channel level). Furthermore, these transformations will have to be coordinated with the introduction of data refinement, as declarations of channels will have to be replaced by the underlying means used for the communication (i.e., wires used in the arbitration mechanism and buses for the transfer of information).

One possible solution to this problem is to perform all the above mentioned transformations (data and control refinement) at once to the program as a whole (in a way similar to the one proposed in Morgan’s refinement calculus [Morgan 1990]). Even though this approach produces the effect we want while preserving the functionality of the program, it has two disadvantages: (a) it is not compositional (as we need to address program as a whole); and (b) it is not possible to verify its correctness regarding our semantics for Handel-C (as we are replacing the constructs by the semantic expressions themselves!).

The fundamental problem with the approaches mentioned above is that we are being forced to perform too many transformations in a single step (i.e., data and control refinement) when we only intend to achieve a higher degree of operational control over the actions performed by input/output commands (i.e., we only intend to perform control refinement). The reason for this coupled refine- ment lies in the fact that our more detailed operational control over the communication primitives are formulated in terms of the wires and buses used in the implementation.

The solution is to formulate primitives that still operate over channels (rather than over the un- derlying wires and buses) but that address the different actions that comprise the whole behaviour of the input and output constructs. From this observation, the first operation we want to introduce represents the action of one side of the channel (either the reader or the writer) being ready to communicate. We capture these actions with the new commands: in-req(ch) and out-req(ch). Following the notation introduced in section 4.1, we define the semantics of these new constructs as follows:

Definition 4.2.11. Input request semantics 

in-req(ch) =df ch?snc:= true

Definition 4.2.12. Output request semantics 

out-req(ch) =df ch!snc:= true

On the other hand, we also introduce a way of checking whether the reader (writer) is present at the other end of a channel ch. We denote these new constructs with the boolean conditions rd(ch) and wr(ch) respectively. The following definitions provide a more precise semantics for these two constructs:

Definition 4.2.13. Input-ready condition ~rd(ch) =df ch?.inc= true Definition 4.2.14. Output-ready condition

~wr(ch) =df ch!.inc= true

Finally, we introduce a command that represents the two actions involved in the communi- cation over a channel ch. The operation in(ch) returns the value being transmitted over channel ch at the present clock cycle (i.e., once the circuit has reached a stable state) and the command out(ch, e) denotes the action of sending the value e over channel ch.

Definition 4.2.15. Primitive input ~in(ch) =df ch.inc

Definition 4.2.16. Primitive output ~out(ch, e) =df chsnc:= ~e

It is important to notice the differences between the standard Handel-C input construct ch?x and the new in(ch) primitive. The former is a complex command that is able to handle the cases where the communication is possible (effectively assigning the input value to x) as well as the case when there is no input over ch during the current clock cycle (by performing a one-clock cycle delay and attempting the communication again). The latter, on the other hand, captures the value over the channel regardless of whether there has been an input to the channel or not (retrieving an undefined value). Moreover, in(ch) only models the action of reading the current value held in the channel ch. There is no notion of assigning this value to a variable as in ch?x.

A similar difference holds between ch!e and out(ch, e). In the case of out(ch, e), the value e is forced into the channel ch without any verification of whether there is another process to receive the value or not (i.e., it implements a non-blocking, unbuffered channel). On the other hand, ch!e follows a protocol that ensures blocking semantics of communication over channel ch.

These differences are precisely characterised by the following equivalence laws relating Handel- C’s input and output commands to particular combinations of the primitives described above. Law 4.2.17. ch?x= µX • in-req(ch); ((x := in(ch))1 wr(ch)  delay; X)

Law 4.2.18. ch!e= µX • out-req(ch); out(ch, e); (delay  rd(ch)  delay; X)

The basic communication primitives described above satisfy a number of additional algebraic properties. Firstly, it is possible to commute the order between a request and its opposite condition: Law 4.2.19. (wr(ch))>S; in-req(ch)= in-req(ch); (wr(ch))>S

The out(ch, e) command does not affect the reading portion of the channel: Law 4.2.21. (¬rd(ch))>S; out(ch, e)= (¬rd(ch))>S; out(ch, e); (¬rd(ch))>S

Provided that a request or output operation is the first action on both branches of a conditional, it can be pulled out of the conditional construct (note that the condition in the selection construct does not depend on the requests being extracted from the branches).

Law 4.2.22. (in-req(ch); P) wr(ch)  (in-req(ch); Q) = in-req(ch); (P  wr(ch)  Q) Law 4.2.23. (out-req(ch); P) rd(ch)  (out-req(ch); Q) = out-req(ch); (P  rd(ch)  Q) Law 4.2.24. (out(ch, e); P) rd(ch)  (out(ch, e); Q) = out(ch, e); (P  rd(ch)  Q)

To keep the presentation compact, we will take advantage of the symmetry of input/output and for the remaining of this section DIR will represent a generic direction that remains constant for the whole extent of the law in which it appears.

Sequences of more than one request over the same channel may arise during the same clock cycle. Their joint effect is the same as the one corresponding to the last one of them.

Law 4.2.25. ¬DIR-req(ch); DIR-req(ch)= DIR-req(ch) Law 4.2.26. DIR-req(ch); ¬DIR-req(ch)= ¬DIR-req(ch)

A request for input/output over a channel ch followed by a one-clock-cycle assignment that does not depend on ch can be performed in parallel.

Law 4.2.27. in-req(ch); (v :=

snce)1= in-req(ch)1kMˆ (vsnc:= e)1

Law 4.2.28. out-req(ch); (vsnc:= e)1= out-req(ch)1kMˆ (vsnc:= e)1

The following laws allow us to eliminate the request for input (output) and its associated condition. Law 4.2.29. Provided there are no external requests for communication over ch we have that:

(s1∨ s2)snc−→in-req(ch)1kMˆ b −→ snc(v := snce)1 = b −→ snc(v := snce)1[(s1∨ s2)/rd(ch)]

Law 4.2.30. Provided there are no external requests for communication over ch we have that: (s1∨ s2)snc−→out-req(ch)1kMˆ b −→ snc(v := snce)1 = b −→ snc(v := snce)1[(s1∨ s2)/wr(ch)]

Law 4.2.31. Provided that ch= ARB, and there are no external requests for communication over ch we have that: s1−→sncout(ch, e1)1kMˆ s2 −→ sncout(ch, e2)1 kMˆ b −→ snc(vsnc:= e)1v b−→

At the hardware level, wires that are not latched acquire the false logical value at the end of the clock cycle. They remain in this state unless a process explicitly sets them to the true logical state. The following two laws capture this notion for the case where no process is setting the wires to the true value:

Law 4.2.32. Provided there are no external in-req(ch) events we have that: (v :=

snce)1= (v :=

snce)1[false/rd(ch)]

Law 4.2.33. Provided there are no external out-req(ch) events we have that: (v :=

snce)1= (v :=

snce)1[false/wr(ch)]

In document A verified compiler for Handel-C (Page 104-108)