Hardware Compilation of the ProCoS Gas Burner Case Study using Logic Programming Jonathan Bowen
The University of Reading, Department of Computer Science Whiteknights, PO Box 225, Reading, Berks RG6 6AY, England Tel: +44-1734-316544 Fax: +44-1734-751994 Email:
[email protected] URL: http://www.cs.reading.ac.uk/people/jpb/ May, 1996
Abstract
A hardware compiler may be speci ed by a description of how each construct of the source language is translated into a netlist of simple digital hardware components such as gates and latches. instructions. It is possible to produce a compiler prototype almost directly from this speci cation in the form of a logic program. This de nes a relation between allowed highlevel program constructs and matching low-level circuits. This document brie y presents such a compiler written in Prolog, together with an example compilation of the ProCoS II gas burner example. Gaps in the process are discussed.
1 Overview The original ESPRIT ProCoS project on \Provably Correct Systems" [1] concentrated almost exclusively on the veri cation of standard compilation of a high-level programming language (based on Occam [15]) down to a microprocessor (based on the Transputer). However, towards the end of the rst phase of the project, a new research group at the Oxford University Computing Laboratory, the Hardware Compilation Group led by Ian Page, made rapid advances in the development of hardware compilation techniques using an Occam-like language targeted towards Field Programmable Gate Arrays (FPGAs). This inspired the ProCoS project to investigate provably correct aspects of this approach. A major advantage of proving a compilation scheme correct is that this need only be done once for an unlimited number of designs compiled using the same scheme. Thus changes may easily be made to a given design without invalidating the veri cation. Information on the hardware compilation approach adopted on the ProCoS II project may be found successively in [14, 7, 8]. The normal form representation of the hardware produced by the various compiling schemes investigated has become progressively closer to a hardware netlist of basic digital hardware components such as gates and latches during successive attempts to capture and prove the essence of the hardware compilation technique used by the Hardware 1
Compilation Group. The low-level compiled hardware representation is now suciently close to a netlist representation to allow an `obvious' and direct mapping from one to the other. ProCoS provides a tower of techniques for the requirements capture, design and compilation of embedded systems in a linked and formal manner [8]. Several case studies have been studied including, most extensively, a gas burner example [6]. Hardware compilation provides an alternative route for the compilation of the ProCoS case studies, which may also be compiled onto the Transputer microprocessor architecture [18]. An algebraic approach has been used successfully, and has been shown to be amenable to mechanization, using the OBJ3 theorem prover for example [20]. A rapid prototype compiler [4] has been constructed using the logic programming language Prolog [10, 21] in as direct a manner as possible for the hardware compilation theorems presented at the FTRTFT'94 ProCoS tutorial [8]. The program of the gas burner in this case study has been compiled directly into hardware using this compiler in a normal form that is very close to a netlist of simple digital hardware components. Hardware compilation also allows the possibility of generating a specialized microprocessor (perhaps based on a subset of an existing microprocessor, such as the Transputer). This allows a standard compilation scheme to be based on such hardware if desired. Optimization [13] and decompilation [2, 9] have been investigated for software compilation and could be extended to hardware compilation. Provably correct optimization could be a particularly fruitful area for hardware. The compilation of hardware and software together, with consideration of the trade-os involved, also provides an exciting research area [19].
2 Compiler prototype In this section, a rapid prototype compiler based on the hardware compilation theorems in [8], using the logic programming language Prolog [10, 21], is presented. To aid readability, heavy use of the standard Prolog meta-predicate op(. . . ) is used in the actual source code. This allows the use of pre x, in x and post x operators of speci ed precedence and associativity, which means the source can be presented in a form quite close to the mathematical description, within the constraints of the ascii character set available in most programming languages like Prolog. For example, <= is used instead of v for the re nement symbol. Constraints are coded in curly brackets, following the syntax of the constraint logic programming language Prolog III [11]. The support clauses required for these constraints may be coded in Prolog in a compact manner. The netlist of components forming the target circuit is coded as a Prolog list (in square brackets, using the standard Prolog notation). (Skip) A skip waits for one clock cycle before proceeding using a latch to create the delay between the start signal S and the nish signal F:
Clause 0
skip <= (S,[F:delay(S)],F).
(Chaotic Process) For the chaotic process chaos, the start signal S, the circuit C, and the nish signal F may be chosen arbitrarily by the Prolog program. Thus there are no constraints between them:
Clause 1
2
chaos <= (S,C,F).
(Assignment) For assignment, a delay and a latch component are compiled as follows:
Clause 2
X:=B <= (S,[F:delay(S),X:latch(S,S/\B)],F).
Note that a Prolog list [. . . ] is compiled for the set of components because Prolog, like most other executable programming languages, does not provide sets as a standard data-type for eciency reasons. Because the nish signal is so important in the compilation of circuits, it is convenient to ensure that the component outputting this signal is always compiled as the rst element in the component list. This allows it to be easily found without searching the entire list, which makes the compilation process more ecient for some constructs. (Sequence) For sequential composition, the fact that the rst component in the second circuit being compiled contains the nal nish signal is explicitly coded as the head of the list of components denoted X in the clause below.
Clause 3
(P1;P2) <= (S,[X|C],F) :P1<=(S,C1,H), P2<=(H,[X|C2],F), {C=merge(C1,C2)}.
The nal circuit must be formed by merging the two sub-circuits, rather than just combining them using set union (or equivalently in Prolog, list concatenation). This involves adding multiplexing circuitry where appropriate, as previously discussed. In practice, this operation is very expensive to execute since it involves examining the entire sub-circuitry and nding the matching circuits which need to be multiplexed. Fortunately this operation can be undertaken just as well it the end of the entire compilation process as it can during compilation of each construct which requires merging of circuits. This results in a considerably more ecient compiler in practice. Thus the merge above may be simulated as a much more ecient concatenation of circuits for the moment, followed by an actual merge operation at the end of the entire compilation of a given program. (Conditional) For the compilation of the conditional construct, combinational logic chooses which circuit to `execute'. The nal delay component for each of the individual sub-circuits may be optimized into a single delay component in the resulting circuit. Clause 4
if B then P1 else P2 <= (S,[F:delay(F_1 \/ F_2), S1:comb(S /\ B), S2:comb(S /\ ~B)|C],F) :P1<=(S1,[F1:delay(F_1)|C1],F1), P2<=(S2,[F2:delay(F_2)|C2],F2), {C=merge(C1,C2)}.
3
(Iteration) For iteration, a nal delay latch is required to ensure that the construct takes at least one clock cycle to execute even if the loop is not entered.
Clause 5
while B do P <= (S,[F:delay((S\/F1) /\ ~B), S1:comb((S\/F1) /\ B)|C],F) :P<=(S1,C,F1).
(Input) Input requires various handshaking signals and takes at least a clock cycle to execute.
Clause 6
Ch?X <= (S,[F:delay((S\/L) /\ Ch:outready:in), L:delay((S\/L) /\ ~Ch:outready:in), Ch:inready:out(S\/L), Ch:synch:out((S\/L) /\ Ch:outready:in), X:latch((S\/L) /\ Ch:outready:in, (S\/L) /\ Ch:outready:in /\ Ch:in)|Idle],F) :{Idle = idle(chans\[Ch?])}.
(Output) Output requires matching handshaking and also takes a minimum of one clock cycle to execute.
Clause 7
Dh!E <= (S,[F:delay((S\/L) /\ Dh:synch:in), L:delay((S\/L) /\ ~Dh:synch:in), Dh:outready:out(S\/L), Dh:out((S\/L) /\ Dh:synch:in /\ E)|Idle],F) :{Idle = idle(chans\[Dh!])}.
(Deadlock process) The stop construct never produces a nish signal. All the I/O channels must be idle.
Clause 8
stop <= (S,[F:comb(0)|Idle],F) :{Idle = idle(chans)}.
Clause 2B
(Assignment)
X:=B <= (S,[F:delay(S),X:latch(S,S/\B)|Idle],F) :{Idle = idle(chans)}.
4
(Alternation) makes a choice of execution depending on the input guards.
Clause 9 alt
alt [B?X -> P1, Ch?Y -> P2] <= (S,[F:delay(F_1 \/ F_2), L:delay((S\/L) /\ ~B:outready:in /\ ~Ch:outready:in), S1:comb((S\/L) /\ B:outready:in), S2:comb((S\/L) /\ Ch:outready:in /\ ~S1)|C],F) :B?X <= (S1,Input1,H1), Ch?Y <= (S2,Input2,H2), P1 <= (H1,[F1:delay(F_1)|C1],F1), P2 <= (H2,[F2:delay(F_2)|C2],F2), {C=merge([Input1,C1,Input2,C2, [B:inready:out(S\/L),Ch:inready:out(S\/L)]])}.
(Time-in) Time-in is similar to the alt construct, but with delayed execution.
Clause 10
alt [wait M: B?X -> P1, wait N: Ch?Y -> P2] <= (S,[F:delay(F_1 \/ F_2), L1:delay((T:M \/ L1) /\ ~(S1/\S2)), L2:delay((T:N \/ L2) /\ ~(S1/\S2)), S1:comb((T:M\/L1) /\ B:outready:in), S2:comb((T:N\/L2) /\ Ch:outready:in /\ ~S1)|C],F) :B?X <= (S1,Input1,H1), Ch?Y <= (S2,Input2,H2), P1 <= (H1,[F1:delay(F_1)|C1],F1), P2 <= (H2,[F2:delay(F_2)|C2],F2), {K = max(M,N), [T:K:D|Timer] = timer(S,K,~(S1\/S2)), C = merge([[T:K:D|Timer],Input1,C1,Input2,C2, [B:inready:out(T:M\/L1),Ch:inready:out(T:N\/L2)]])}.
Clause 11
(Time-out)
alt [B?X -> P1, wait N: skip -> P2] <= (S,[F:delay(F_1 \/ F_2), L:delay((S\/L) /\ ~T:N /\ ~B:outready:in), S1:comb((S\/L) /\ B:outready:in), H2:comb(T:N /\ ~B:outready:in)|C],F) :B?X <= (S1,Input,H1), P1 <= (H1,[F1:delay(F_1)|C1],F1), P2 <= (H2,[F2:delay(F_2)|C2],F2), {[T:N:D|Timer] = timer(S,N,~S1), C = merge([[T:N:D|Timer],Input,C1,C2,[B:inready:out(S\/L)]])}.
5
(Parallel) In the parallel construct, the two sub-circuits are started simultaneously. Since this is only used at the outer level, the circuit need not produce a nish signal. Implication (=>) is implemented using the assert and retract clauses of Prolog in a controlled manner to add and remove clauses from the Prolog database as required. See [2] for further details.
Clause 12
chan[Ch1,Ch2] : par[P1,P2] <= (S,[F:delay(0),S1:comb(S),S2:comb(S)|C],F) :chans(Ch1) => P1<=(S1,C1,F1), chans(Ch2) => P2<=(S2,C2,F2), {C=C1^C2}.
Note that input and output channels are declared separately for each parallel program to avoid static checking of programs for valid I/O channels. (Wait) A construct to wait for a speci ed number of clock cycles may be de ned as follows:
Clause 13
wait 1 <= C :- skip <= C. wait N <= C :- N>1, {N_1==N-1}, (skip; wait N_1) <= C.
Macros
It is convenient to use macros to allow syntactic sugar in the high-level programming language. This allows the concrete syntax of the source program to be as near to the original syntax as possible and can be achieved using the following clause: X <= SCF :- (X =^= P), P <= SCF.
Then sequential composition can be formulated in the Occam style using the seq keyword, rather than the more normal in x ; operator: seq [] =^= skip. seq [P|R] =^= (P; seq R).
Input and output without an explicit variable or expression is allowed. A dummy value is substituted: (Ch?) =^= Ch?x. (Dh!) =^= Dh!0. alt [(B?) -> P1, (Ch?) -> P2] =^= alt [B?x -> P1, Ch?x -> P2].
3 Compilation Figure 1 shows the gas burner program as input to the Prolog compiler. Note that the concrete syntax has been changed slightly to allow the program to be input directly to Prolog without the necessity of a separate parser. Figure 2 shows the resulting output when the gas burner program is compiled. 6
system(maincontrol, chan [gason!, gasoff!, yesheat?, noheat?, noflame?, startidle!, startpurge!, startignite!, startburn!] : while true do seq[ yesheat ?, startpurge !, wait 30, startignite !, gason !, wait 1, startburn !, alt [(noheat ?) -> skip, (noflame ?) -> skip ], startidle !, gasoff ! ] ) <- SCF.
Figure 1: ProCoS gas burner control program.
7
SCF = _246 ',' [_250:delay _246\/_444/\ ~true, _444:delay _39548, _575:delay(_450\/_705/\yesheat:outready:in), _705:delay(_450\/_705/\ ~yesheat:outready:in), _1741:delay(_575\/_1871/\startpurge:synch:in), _1871:delay(_575\/_1871/\ ~startpurge:synch:in), _3323:delay _3449, _3449:delay _3575, _3575:delay _3701, _3701:delay _3827, _3827:delay _3953, _3953:delay _4079, _4079:delay _4205, _4205:delay _4331, _4331:delay _4457, _4457:delay _4583, _4583:delay _4709, _4709:delay _4835, _4835:delay _4961, _4961:delay _5087, _5087:delay _5213, _5213:delay _5339, _5339:delay _5465, _5465:delay _5591, _5591:delay _5717, _5717:delay _5843, _5843:delay _5969, _5969:delay _6095, _6095:delay _6221, _6221:delay _6347, _6347:delay _6473, _6473:delay _6599, _6599:delay _6725, _6725:delay _6851, _6851:delay _6977, _6977:delay _1741, _26571:delay(_3323\/_26701/\startignite:synch:in), _26701:delay(_3323\/_26701/\ ~startignite:synch:in), _28264:delay(_26571\/_28394/\gason:synch:in), _28394:delay(_26571\/_28394/\ ~gason:synch:in), _29180:delay _28264, _29692:delay(_29180\/_29822/\startburn:synch:in), _29822:delay(_29180\/_29822/\ ~startburn:synch:in), _31496:delay _31660\/_31661, _31665:delay(_29692\/_31665/\~noheat:outready:in/\~noflame:outready:in), _31660:delay(_31697\/_31908/\noheat:outready:in), _31908:delay(_31697\/_31908/\ ~noheat:outready:in), _31661:delay(_31716\/_32997/\noflame:outready:in), _32997:delay(_31716\/_32997/\ ~noflame:outready:in), _38077:delay(_31496\/_38207/\startidle:synch:in), _38207:delay(_31496\/_38207/\ ~startidle:synch:in), _39548:delay(_38077\/_39678/\gasoff:synch:in), _39678:delay(_38077\/_39678/\ ~gasoff:synch:in), _450:comb(_246\/_444/\true), _31697:comb(_29692\/_31665/\noheat:outready:in), _31716:comb(_29692\/_31665/\noflame:outready:in/\ ~_31697), x:latch((_450\/_705/\yesheat:outready:in)\/(_31697\/_31908/\noheat:outready:in)\/ (_31716\/_32997/\noflame:outready:in), (_450\/_705/\yesheat:outready:in/\yesheat:in)\/(_31697\/_31908/\ noheat:outready:in/\noheat:in)\/(_31716\/_32997/\ noflame:outready:in/\noflame:in)), gasoff:out(_38077\/_39678/\gasoff:synch:in/\0), gason:out(_26571\/_28394/\gason:synch:in/\0), startburn:out(_29180\/_29822/\startburn:synch:in/\0), startidle:out(_31496\/_38207/\startidle:synch:in/\0), startignite:out(_3323\/_26701/\startignite:synch:in/\0), startpurge:out(_575\/_1871/\startpurge:synch:in/\0), gasoff:outready:out(0\/(_38077\/_39678)), gason:outready:out(0\/(_26571\/_28394)), noflame:inready:out(0\/(_29692\/_31665)\/(_31716\/_32997)), noflame:synch:out(0\/(_31716\/_32997/\noflame:outready:in)), noheat:inready:out(0\/(_29692\/_31665)\/(_31697\/_31908)), noheat:synch:out(0\/(_31697\/_31908/\noheat:outready:in)), startburn:outready:out(0\/(_29180\/_29822)), startidle:outready:out(0\/(_31496\/_38207)), startignite:outready:out(0\/(_3323\/_26701)), startpurge:outready:out(0\/(_575\/_1871)), yesheat:inready:out(0\/(_450\/_705)), yesheat:synch:out(0\/(_450\/_705/\yesheat:outready:in))] ',' _250
Figure 2: ProCoS gas burner netlist. 8
4 Conclusions and gaps The Prolog code for main clauses of the compiler has been presented here, together with a hardware compilation of the ProCoS gas burner example program. The coding for each clause is of a very similar size and form to the original speci cation. However, support clauses are also required for constraints, although this can be coded compactly. Similarly, the concrete syntax of the source program used here is not identical to the actual source program. Transcription errors are possible because of all these encodings required for the executable compiler. The subset of Prolog used is relatively pure (e.g., there are no cuts and negation is used very sparingly and only when it is `safe' to do so to maintain the soundness properties). Thus the declarative semantics of Prolog [17] may be assumed to hold and it would be possible to perform a formal proof of the correctness of the Prolog compiler which is presented here. For example, the approach of program synthesis [16] could be (somewhat laboriously) applied. Less pure features used in the code presented here are used in a very restricted manner which is intended to maintain the logical semantics. The Prolog system itself is of course unproven, running on an proven operating system on proven hardware. Thus in practice, many gaps remain to produce a fully proven system. Whether this is worthwhile or even feasible is debatable. Formal methods should be applied in areas where most errors occur, rather than in all areas of a design in general, for good commercial reasons. The output from an optimizing compiler will be considerably more dicult to deal with than the simple example presented here. Much further optimization of the resulting circuits could be undertaken. Some of the compilation schemes presented here could be improved. In addition, subsequent optimization using Boolean laws could reduce the size of the circuit substantially (typically by up to a third in practice). This is an area where formal methods might usefully be used to verify more complicated optimization transformations, in which many compilation errors occur in practice. Further information relevant to the subject matter of this paper may be found under the following on-line World Wide Web page: http://www.comlab.ox.ac.uk/archive/procos/hwcomp.html
Acknowledgements Thanks are due to He Jifeng, Tony Hoare, Zheng Jianping, Ian Page and Wayne Luk for the inspiration of some of the ideas presented here. The author is also grateful to members of the ESPRIT Basic Research ProCoS project for their support. This work was partly funded by the UK Engineering and Physical Sciences Research Council (EPSRC) project Provably Correct Hardware/Software Codesign on grant no. GR/J15186.
9
References [1] D. Bjrner et al., A ProCoS Project Description: ESPRIT BRA 3104, Bulletin of the European Association for Theoretical Computer Science (EATCS), 39:60{73, October 1989. [2] J.P. Bowen. From Programs to Object Code and back again using Logic Programming: Compilation and Decompilation. Journal of Software Maintenance: Research and Practice, 5(4):205{234, December 1993. [3] J.P. Bowen (ed.). Towards Veri ed Systems. Elsevier Science, Real-Time Safety Critical Systems series, Volume 2, 1994. [4] J.P. Bowen. Rapid Compiler Implementation. Chapter 10 in [12], pp 141{169. [5] J.P. Bowen et al. A ProCoS II Project Description: ESPRIT Basic Research project 7071, Bulletin of the European Association for Theoretical Computer Science (EATCS), 50:128{137, June 1993. [6] J.P. Bowen, M. Franzle E-R. Olderog and A.P. Ravn. Developing Correct Systems. Proc. Fifth Euromicro Workshop on Real-Time Systems, Oulu, Finland, 22{24 June 1993. IEEE Computer Society Press, pp 176{187, 1993. [7] J.P. Bowen, He Jifeng and I. Page. Hardware Compilation, Chapter 10 in [3], pp 193{207. [8] J.P. Bowen, C.A.R. Hoare, M.R. Hansen, A.P. Ravn, E-R. Olderog, M. Schenke, M. Franzle, M. Muller-Ulm, He Jifeng and Zheng Jianping. Provably Correct Systems { FTRTFT'94 Tutorial. Third International School and Symposium, Formal Techniques in Real Time and Fault Tolerant Systems, 19{23 September 1994, Lubeck, Germany: School Material. Christian-Albrechts-Universitat, Germany, 1994. [9] P.T. Breuer and J.P. Bowen. Decompilation: The Enumeration of Types and Grammars. ACM Transactions on Programming Languages and Systems (TOPLAS), 16(5):1613{1647, September 1994. [10] W.F. Clocksin and C.S. Mellish. Programming in Prolog. Springer-Verlag, 4th edition, 1994. [11] A. Colmerauer. An Introduction to Prolog III. Communications of the ACM 33(7):69{90, 1990. [12] He Jifeng. Provably Correct Systems: Modelling of Communication Languages and Design of Optimized Compilers. McGraw-Hill International Series in Software Engineering, 1995. [13] He Jifeng and J.P. Bowen. Speci cation, Veri cation and Prototyping of an Optimized Compiler. Formal Aspects of Computing, 6(6):643{658, 1994. [14] He Jifeng, I. Page and J.P. Bowen. Towards a Provably Correct Hardware Implementation of Occam. In G.J. Milne and L. Pierre (eds.), Correct Hardware Design and Veri cation Methods, SpringerVerlag, LNCS 683, pp 214{225, 1993. [15] Inmos Limited. Occam 2 Reference Manual. Prentice Hall International Series in Computer Science, 1988. [16] Kung-Kiu Lau and T. Clement (eds.). Logic Program Synthesis and Transformation, Manchester 1992. Springer-Verlag, Workshops in Computing, 1993. [17] J.W. Lloyd. Foundations of Logic Programming. Springer-Verlag, 2nd edition, 1987. [18] M. Muller-Olm. Compiling the Gas Burner Case Study. ProCoS Technical Note [Kiel MMO 16/2], Kiel University, Germany, 12 September 1995. [19] C.A.R. Hoare and I. Page. Hardware and Software: Closing the Gap. Transputer Communications, 2(2):69{90, June 1994. [20] A. Sampaio. An Algebraic Approach to Compiler Design. DPhil Thesis, Oxford University Computing Laboratory. Technical Monograph PRG-110, 1993. [21] J.M. Spivey. An Introduction to Logic Programming through Prolog. Prentice Hall International Series in Computer Science, 1996.
10