Verity now with call-by-value

Call-by-name is generally seen as an annoyance by programmers. Fortunately, if your programming language has mutable state you can force evaluation at ground type by using assignment, so by writing

new v in v := x; f(!v)

you achieve the same effect as calling f(x) by-value. This is tedious, so ALGOL use

f(val x)

to achieve the same effect by using simpler syntax. We have added this feature to Verity now as well.

This was also an opportunity to me to get used to the compiler, which was written by my student Alex Smith. So I sat down with him and had him guide me in implementing this syntactic transformation in the GOS compiler. So that I don’t forget all this stuff I screencasted our session, which you can watch here.

Warning: it is not a very exciting video, other than the mild amusement generated by my constant fumbling.

On the plus side, the whole compiler change took as long as the video, i.e. about one hour including some simple testing. And it worked, no bugs, first time, which is yet another testament to what a nice programming OCaml is.

Posted in Geometry of Synthesis | Leave a comment

Back to the future

The programming language ALGOL was “a language so far ahead of its time, that it was not only an improvement on its predecessors, but also on nearly all its successors,” at least according to C.A.R. Hoare. Few programming languages have been more studied [this] or better understood semantically; perhaps a dozen variations on this language obtained by adding various features have known fully abstract semantic models, mostly using game semantics. Few programming languages have a better programming logic than Reynolds’s awesome ALGOL specification logic [this]. And Reynolds’s Syntactic Control of Interference type system for ALGOL is a very clever way to handle effects in functional programming languages, avoiding some of the complications of monadic type systems.

Yet nobody is actually programming in this amazing language. What a pity. Call-by-name is certainly a problem, but it is mainly a problem for compiler writers rather than programmers. If you stick with first order functions, which is most of the code one normally writes, it is straightforward to force evaluation. And who is smart enough to use higher-order functions should be smart enough to use them efficiently.

Our old-new programming language Verity is a pretty faithful version of ALGOL, with the syntax mildly modernized (type inference, etc). Right now we only use this language to do higher-level synthesis, but I think it deserves to grow into a more general purpose language.

Posted in Geometry of Synthesis | 2 Comments

The case for a system-level semantics

In an earlier post I was deploring the way research in programming language semantics fetishises syntax. Because adding to a language anything other than syntactic sugar changes contextual equivalence in the language, the semantic model induced by syntactic context is (sometimes fundamentally) different. Defining a language becomes a neat game of matching a sensible syntax with a sensible semantics; this meet-in-the-middle approach is the most productive in leading to full abstraction results. I cannot think of a better example than the many versions of Idealized ALGOL: with or without “snap-back state”, with or without side effects in expressions, with or without “bad variables”. The differences are tiny yet the models are incredibly different. This is not great.

But what is the alternative? Can we have a meaningful notion of context which does away with syntax?

Lets go back to the conventional definition of equality for terms:

M\equiv N iff \forall C[-].C[M]\equiv C[N].

This definition works because C[M], C[N] are programs, i.e. closed terms, which can be executed in the operational semantics. They will either reduce to some ground-type value which can be compared directly, or will diverge, a non-terminating computation. The quantification over all context is just a way to get around the fact that we cannot execute open terms M, N.

My claim is that we can execute open terms. Real-life software is rarely self-contained. We can certainly compile open terms, this is how we get libraries. Then we link open object code against other open object code to get executables–but this linking can be actually done at run time if we use dynamic libraries. So the system (compiler+linker+RTS+operating system+computer) has a way to run open terms. Why doesn’t the semantics match the behaviour of the system?

Curiously enough, the technical apparatus for “executing open terms” has been developed. Not one, but several such techniques exist: trace semantics, operational game semantics, environmental bisimulation and of course game semantics. What they all have in common is that they model the interaction between an open term and the environment, usually in an effective way — you can build an interpreter out of the description. These are all very powerful techniques and they can describe very powerful, expressive environments.

What usually happens in such papers (and it’s not meant to be a very harsh criticism because I’ve written a couple of papers just like that) is that a lot of technical effort is dedicated to restricting the power of the environment, by adding various rules about what it can and what it cannot do, so that its expressivity matches perfectly the discriminating power of the syntactic context: full abstraction!

As a technical tour de force, full abstraction is usually a great accomplishment. In terms of usefulness, not so much, for reasons already discussed.

An alternative approach is to take the semantic power of the environment at face value and consider that it is a model of the computational environment. This is what the Dolev-Yao model of security is: a purely semantic description of the environment, i.e. the attacker. Precisely the same idea is eminently suitable for a programming language system-level context. The model is unrestricted computationally, only informationally (or epistemically); it can do whatever it wants with the information at hand but you can still hide things from it.

So when you specify a programming language you must explain not only what computations occur, as is normally the case in operational semantic specifications, but also what information is leaked to the environment and what sanity checks are built in the language when handling input from the environment. A specification for a language must be tied in with a high-level specification for a compiler and run-time system! This may sound like a lot, and it does involve more work than the usual operational semantics, but the result is a realistic basis for verification of language properties, especially against possibly antagonistic environments. More details in our paper and in this lecture.

One final comment: is a syntactic context ever appropriate? Yes! If we are talking about a calculus, which is not meant to be compiled and executed, but just studied in the abstract, a syntactic context is a perfect way to study its equational theory. In fact I would go as far as to say that the key difference between a calculus and a programming language is that the right notion of context for the former is syntactic and for the latter is systemic.

Posted in system level semantics | 4 Comments

Talk at “Compiling Complete Programs into Circuits” 2012

I gave a contributed talk on GOS/Verity at the CCPC workshop, part of the ASPLOS conference. The focus of the talk was on the fact that using a semantic notion of function call it is possible to support separate compilation and a foreign function interface. This is very useful because it means we can use libraries and low-level things such as device drivers. The video is here.

Posted in Geometry of Synthesis | Leave a comment

A case against syntactic context

A program is what a program does, so if two programs do the same thing then they are the same thing. Semantics starts when you try to pin down what “doing the same thing” means. What is observable? Input and output are observable, but information such as duration of execution or memory usage tends to be ignored. If the program is non-deterministic then things complicate, because non-terminating behaviour is not observable (you cannot run a program until it doesn’t terminate). Depending on how you want to account for such behaviour, different notions of observation arise.

But these choices are deliberate, and the consequences of committing to one choice or another are well understood. At least the fact that there will be consequences is well understood. Here I want to talk about a different choice, a choice made so universally, a choice so ingrained in the way we do semantics of programming languages that it doesn’t even feel like a choice. It feels like “the right thing to do”. I want to explain why it is rarely actually so, and why it is mostly the wrong thing to do.

I am talking about terms and contexts. Programs that include free identifiers are not considered to be executable in the same sense in which closed programs are, i.e. programs in which all identifiers are locally defined. In fact we call the former terms and we only call the latter (proper) programs. So f(x) is a term and cannot be executed, whereas (\lambda f\lambda x.f(x))(\lambda x.x+x)1 is a program which we can run.

Semantics becomes really interesting when we talk about terms. And the main goal of semantics is establishing that two terms are the same, so that any use of one is equivalent with any use of the other. They can be substituted for each other with no observable effect (the notion of observation is assumed fixed). But if we cannot rely on running a term, what can we do?

The universal approach is this: we take two terms to be equivalent if for all contexts which together with the term form a program, these programs that we get are equivalent.

M\equiv N iff \forall C[-].C[M]\equiv C[N]

For a term such as f(x) above, a context could be (-)(\lambda x.x)1. The dash indicates where the hole in the context is, where the term is plugged in to make a program.

I am not sure where this definition of equivalence of terms was introduced first, but I have seen it in the oldest papers of Scott, Milner and Plotkin. It is a definition which seems to have been proposed and accepted by default from the beginning of semantics of programming languages.

And it is wrong.

At least, most of the time.

The reason why it is wrong is that the context in which a term is executed is rarely constrained by the syntax of a particular programming language. The undefined identifiers represent function calls, which can be defined in other parts of the program. But they can also be library calls, or calls to the runtime services of the operating system. Any reasonably mature compiler supports separate compilation and foreign function interface, and once the conventions of the application binary interface are obeyed the linker can connect any compiled term with any other piece of object code, written in any language. For runtime services it is even more difficult to conceive of a syntactic notion of context, since the function can interact directly with a hardware device which has a physical behaviour unspecified syntactically.

Thinking of a context as a syntactic context is a dangerous over-simplification. Any property of a program proved relative to a syntactic context will be valid only in a syntactically valid context. For example if you prove the correctness of a library up to syntactic context, it will not be correct when used from a program which does not constitute a proper syntactic context, either because it is written in a different programming language or because it is malformed yet it passed by the compiler checks. This can actually happen in the case of C. Even worse, if you prove security properties of a piece of code up to syntactic context you only prove that an attacker which only uses that programming language will be unable to violate the property. But attackers rarely do that.

The use of syntactic contexts has led to a lot of intrinsically interesting work in programming languages, culminating in various full abstraction results, i.e. denotational models that perfectly capture the discriminating power of the syntactic context. But unless the properties of the syntactic context can be enforced in the object code and runtime system (and there is work on that) properties based on syntactic context will only be valid in a fictitious, highly idealised world.

What is a more realistic way? The context should be seen as the computational context, the combined capabilities of the language, the compiler, the operating system and the user. This may sound complicated but it is actually simpler than capturing the vagaries of syntax. And it is a scientific exercise in the sense that it should capture actual system behaviour. I like to call it system-level semantics. 

More about it in an upcoming post.

Note: If you are impatient you can read the paper or watch the video, but they are on the technical side.

Posted in system level semantics | 1 Comment

Three talks at “Logic and interactions” winter school, CIRM, Luminy

I recently gave a research tutorial on game semantics at Logic and interactions 2012, CIRM, Marseille. I was asked to emphasize the connection between game semantics and the Geometry of Interaction, which I was very happy to do. I didn’t try too hard to establish a formal connection but I tried to establish an intuitive and conceptual connection via token machines, although working in a more general setting than the conventional GoI; a setting which seems to be more appropriate if your concerns are computational rather than logical.

  1. Game semantics from a GoI perspective [video]
  2. Game semantics of Concurrent Algol: this part of the tutorial was given on the blackboard and I could’t record it. It was a vanilla description of the model in my paper with Andrzej Murawski [pdf].
  3. Applications of full abstraction: A brief conclusion where I mainly explore the usefulness of full abstraction [video]. I intended this mainly as a setting of scene for my invited talk on System-Level Semantics [pdf], recent work with Nikos Tzevelekos.
  4. Invited talk: A system-level semantics [video]. More about SLS in a forthcoming blog post. Stay tuned.

 

Posted in game semantics | Leave a comment

Global state considered a fiction

Here is what my colleague Uday Reddy says in his paper “Global State Considered Unnecessary” [pdf]:

The traditional semantics of imperative features is based on the notion of global store. A variable is an index into the store and the store maps such indices to classical static values. The problem with this approach is that the store becomes a conceptual bottleneck (recall the “von Neumann bottleneck” of [Backus'78]). Different portions of the program work on different parts of the store, possibly extending them in separate ways, and threading a single global store through all of them becomes a heavy burden. In our approach, we will decompose the store into tiny pieces (as small as individual variables) and these pieces can be manipulated independently.

This paper was ahead of its time and its view of the semantics of state visionary. This paper preceded game-semantic models of state, which perfected, conceptually, this very idea, producing fully abstract models of imperative computation [Abramsky, McCusker '96]. As an aside, Reddy’s model was in fact also fully abstract, although this was only proved to be so later [McCusker '02].

Note here that global state was considered “unnecessary”, and in fact cumbersome, as a concept: it was claimed to be difficult (or impossible) to prove full abstraction of imperative computation using global state. To some extent this consideration was mistaken, as explained by my other colleague Paul Levy in his paper “Global state considered helpful” [Levy '08], which shows a simplified game semantics for imperative computation using global state.

But “unnecessary” and “helpful” are two notions that don’t contradict each other, quite the opposite! Even though it is helpful to use global state as a concept, Reddy (and subsequently game semantics) show that we can do without it. And we must do without it! Here is why.

In contemporary computer architectures global state has disappeared, or rather disintegrated into a hierarchy of memories. To the extent it is intended, the hierarchical structure of the memory is meant to compensate for its horrible latency relative to the CPU (hundreds of clock cycles). But much of it comes contingently and accidentally, as a by-product of incremental evolution of devices and architectures. So what do we have on a normal desktop PC in the way of memory? We have the RAM and we have the CPU cache, this is obvious and perhaps not that interesting since caches are transparent to the programmer.  But we also have, usually, a massively parallel GPU and a substantial video memory for it. The GPU has its own local on-chip memory that, unlike the CPU, needs to be explicitly programmed. The memory transfer between the CPU RAM and the GPU RAM also needs to be explicitly programmed. So there is no single “global” memory.

But this is perhaps not compelling. We still have a coarse-grained division of the global state into a small number of disjoint global-ish states. This doesn’t seem to require the radical rethinking of state advocated by Reddy. But it is worth mentioning as a preliminary.

The absence of global state, in any meaningful form, becomes strikingly obvious in the context of hardware compilation: synthesizing circuits from conventional programming languages. When you create a circuit the last thing you want is to move all your memory off-chip in a global memory! On the contrary, you want to save the latency and the resource contention that a global memory and a bus to it mean. You want to distribute your memory throughout your design so that it can be accessed wherever it is needed. You want to realize precisely the vision of an unnecessary global state.

But there is another, deeper issue regarding global memory. A program uses memory (state) not only explicitly via assignments, but also implicitly as closures and activation records, to deal with function definition and call. This state also cannot be, in hardware compilation, global. In a heterogenous environment, such as the CPU+GPU architecture mentioned earlier, it is also unclear where in memory should these bookkeeping records for functions be kept. This is why GPU programming languages (e.g. CUDA) are so restrictive in the way they allow function definition and call.

The localization of the state as used for function calls was explained in my first paper on the Geometry of Synthesis [Ghica '07]. The idea is that linear function call on its own can be described in a purely static way, by managing the bureaucracy of the interconnect in a certain way. Diagrams of boxes and wires obey a certain mathematical structure called “closed monoidal category“; without getting into technicalities, the word closed here means just that: this mathematical structure can model function definition and application. The reasons are deep and fascinating. To understand better how higher-order computation has a mathematical structure which can be recovered from communicating processes represented as boxes and wires (or just boxes and wires!) I recommend Samson Abramsky‘s papers explaining Girard’s Geometry of Interaction. To get a grasp of monoidal categories I recommend Peter Selinger’s wonderful tutorial.

So linear function call to some extent is trivial. The key word here is linear, i.e. the function uses its argument precisely once. Moving from linear to affine, i.e. functions that use their arguments at most once is not a big deal. The point that I make in the GoS I paper is that sequentially reentrant functions, i.e. functions that don’t call themselves (as in f(f(x)) and don’t get called concurrently, can be managed using devices with local (and finite) state, which turn out to correspond precisely to the notion of a bus in hardware.

I think if we look forward into the future and we extrapolate the evolution of computing devices and architectures we can see how the global memory is becoming an abstraction farther and farther removed from the reality of computing architectures, which are increasingly heterogeneous and distributed aggregate devices. At some point in the very near future global state is bound to stop being an abstraction and will become a fiction.

Posted in game semantics, Geometry of Synthesis | Leave a comment

Separate compilation in higher-level synthesis

The Geometry of Synthesis [pdf] research programme aims to develop higher-level synthesis tools and techniques that bring full support for functions. On the language side this means things like higher-order functions [pdf] and recursion [pdf]. On the compiler side this means support for things like runtime services, libraries and separate or heterogeneous (i.e. “foreign function”) compilation. The language-side features are very cool to have, but the compiler-side features are essential if you want to handle any realistic projects. In this post I will explain how the GoS compiler (gosc) supports these features via an example.

The source code

Lets do an implementation of the Fibonacci numbers using recursion and memoization.

new r := (new mem(128) in
new i := 1 in
while !i > 0 do {mem(!i) := 0; i := !i + 1};
mem(0) := 0
# Fibonacci-by-value.
(fix \fib.\a.\n.
new n1 in
new n2 in
new n3 in
new n4 in
n1 := n;
if !n1 < 2 then 1$8
else if !a(!n1) > 0 then !a(!n1)
else (
n2 := fib(a)(!n1 - 2);
n3 := fib(a)(!n1 - 1);
n4 := !n2 + !n3;
a(!n1) := !n4;
!n4))(mem)(10))
in { print(!r) }

First a note about the programming language: it’s basically ALGOL, slightly modernized with type inference and variable data widths (1$8 means constant 1 on bits). I will discuss in a different post why this is a great language for hardware synthesis. The important point here is that Algol is a conventional programming language. At some point in time (the 60s) it was the conventional programming language. Even if you are not familiar with the details of the syntax it should be obvious where the local variable and array declarations are, how assignments and if statements work and that dereferencing (!) is explicit. There is nothing hardware-y about it. No explicit channels and signals, no timing, etc.

We have an ALGOL interpreter and we can run our program on the desktop:

Step 1: Generate System HDL files

I want to run this program on my FPGA-powered board, a Terasic DE3.

The easy way is to compile it as-is and peek-and-poke at the data using SignalTap [pdf]. The harder way is to use the segmented LED displays to report the output value.

The DE3 board comes with a tool for automatically generating the IP and pin assignments for the various things that are on the board such as LEDs, push-buttons, etc. It looks like this:

Note that most of the options are unselected because I won’t use them. I use the LEDs to display the output and a push-button for reset. The System Builder will generate then the right files as well as a Quartus project file with the right settings.

What we need to do now is to build two levels of wrappers around these IP cores so that we can use them from the Fibonacci program as if they are programming language functions.

Programming-level system calls

We are going to replace the call

{print(!r)}

which displays the result on the desktop with more suitable platform-specific calls. In this case I will do this:

 _de3_set_seg7led0_state(0);
 _de3_set_seg7led1_state(0);
 new j := (_de3_wait_button:exp$4) in
 _de3_set_seg7led0_state((!r)$$4);
 _de3_set_seg7led1_state(((!r)>>4)$$4)

These are functions that do not exist yet and things could be set up differently. The _de3_ prefix is just a convention to show that I am using DE3-specific functionality. The rest of the name of the functions should make it obvious what their role is. For example, _de3_set_seg7led0_state sets the state of one of the 7-segment LED display on the DE3 board. Note the (…:exp$4) type annotation, indicating that the result of that call is a 4-bit integer and the >>4 and $$4 operators corresponding to by-4 right-shift and truncation of an integer, seen as a bit vector.

Software-level wrappers

These functions above are a bit too “high level” to be easily implementable directly in hardware. We could do that, but it is more interesting to exhibit a mixed approach, were the interface between “software” and hardware is achieved using both software and hardware wrappers.

Let us call this file stdlib.ia, because it is a very simple standard library for the DE3 board functions we choose to use.  The full file is given below.

# This file contains drivers for the Terasic DE3 board

The first function converts hexadecimal digits in bit patterns needed to turn on or off the seven LED segments which form each display. Note that the bit pattern is a 7-bit value, as there are 7 segments.

# Bits of the number represent segments; 1 = off, 0 = on
let _de3_seg7_convert = \n:exp$4. (
                           # c tl bl bot br tr top
 if n == 0 then 64$7       # 1 0 0 0 0 0 0
 else if n == 1 then 121$7 # 1 1 1 1 0 0 1
 else if n == 2 then 36$7  # 0 1 0 0 1 0 0
 else if n == 3 then 48$7  # 0 1 1 0 0 0 0
 else if n == 4 then 25$7  # 0 0 1 1 0 0 1
 else if n == 5 then 18$7  # 0 0 1 0 0 1 0
 else if n == 6 then 2$7   # 0 0 0 0 0 1 0
 else if n == 7 then 120$7 # 1 1 1 1 0 0 0
 else if n == 8 then 0$7   # 0 0 0 0 0 0 0
 else if n == 9 then 16$7  # 0 0 1 0 0 0 0
 else if n == 10 then 8$7  # 0 0 0 1 0 0 0
 else if n == 11 then 3$7  # 0 0 0 0 0 1 1
 else if n == 12 then 70$7 # 1 0 0 0 1 1 0
 else if n == 13 then 33$7 # 0 1 0 0 0 0 1
 else if n == 14 then 6$7  # 0 0 0 0 1 1 0
 else 14$7                 # 0 0 0 1 1 1 0
 ) in                      # 64 32 16 8 4 2 1

The next two calls implement the programming-level API by calling the conversion helper function above and a function which achieves the lower-level communication with the hardware and sets the state of the register where the state of the LED segment is stored. The _ffi_ prefix is meant to emphasise that this call is truly a foreign function call, which goes directly to hardware, rather than to a genuine function.

let _de3_set_seg7led0_state = \n. {
 _ffi_HEX0_SET(_de3_seg7_convert n)}
and _de3_set_seg7led1_state = \n. {
 _ffi_HEX1_SET(_de3_seg7_convert n)} in

The next function is a convenience function we can use to set both LED displays using just one call, not actually used.

let _de3_set_seg7led_state = \n.\m. (
 _de3_set_seg7led0_state n;
 _de3_set_seg7led1_state m)
and _de3_set_seg7led0_state = _de3_set_seg7led0_state
and _de3_set_seg7led1_state = _de3_set_seg7led1_state in

Below is the implementation  of the function which waits for the button to be depressed, using a while loop. Again, note that _ffi_ function which interacts with the hardware.

let _de3_wait_button = (
 new bv := 0$4 in
 {while ( !bv == 0 ) do
 bv := _ffi_Button_get};
 !bv) in

The final thing in the file is a compiler directive indicating what functions are to be exposed, through the linker, to the programmer.

export (_de3_set_seg7led0_state, _de3_set_seg7led1_state,
        _de3_set_seg7led_state,  _de3_wait_button)

Hardware-level wrappers

The HDL files generated by the DE3 SystemBuilder are only stubs and need to be edited to:

  1. implement the desired behaviour
  2. create the right hooks for linking with the HDL generated by the compiler
  3. include metadata needed by the linker to make the several HDL files work together.

In our case the desired behaviour is quite simple, using one of the push-buttons to reset the LED segments before we proceed:

always @(posedge OSC1_50)
if (!Button[3]) begin
   BUF_LED0 <= 7'b0000000;
   BUF_LED1 <= 7'b0000000;
end else begin
   if (wire2) begin BUF_LED1 <= wire4; end
   if (wire5) begin BUF_LED0 <= wire7; end
end

There is also some code to connect the LED segments to registers.

The right hooks for connecting to the rest of the compiled program are constructed by defining a top-level instance with the right ports:

main main_Instance (
 .clock(OSC1_50),
 .reset(Button[3]),
 .v_0_0ffi0_0hex10_0set_0731_1_1(wire3),
 .v_0_0ffi0_0hex10_0set_0731_1_2c(wire2),
 .v_0_0ffi0_0hex10_0set_0731_1_2d(wire4),
 .v_0_0ffi0_0hex10_0set_0731_1_3(wire3),
 .v_0_0ffi0_0hex10_0set_0731_1_4(wire2c),
 .v_0_0ffi0_0hex00_0set_0731_1_1(wire6),
 .v_0_0ffi0_0hex00_0set_0731_1_2c(wire5),
 .v_0_0ffi0_0hex00_0set_0731_1_2d(wire7),
 .v_0_0ffi0_0hex00_0set_0731_1_3(wire6),
 .v_0_0ffi0_0hex00_0set_0731_1_4(wire5c),
 .v_0_0ffi0_0button0_0get_0400_1_1(wire1),
 .v_0_0ffi0_0button0_0get_0400_1_2c(wire1c),
 .v_0_0ffi0_0button0_0get_0400_1_2d(notButton)
);

Note in the interface the names of all the _ffi_ functions. They are mangled because the naming conventions of Verilog are stricter than those of Algol, so a naming convention applies. Ultimately, the ports are determined by the type signature of the library and are beyond the scope of this post. The library implementer needs to be aware of the way a programming signature is mapped into hardware interfaces, i.e. ports [pdf].

The file also needs to provide metadata to the linker, for connecting this module to module in the synthesised files:

///// BEGIN METADATA
// EXPORTPOS 0_0ffi0_0button0_0get_0400 0
// EXPORTTYPE 0_0ffi0_0button0_0get_0400 exp$4
// EXPORTGROUP 0_0ffi0_0button0_0get_0400 1
// EXPORTPOS 0_0ffi0_0hex00_0set_0731 0
// EXPORTTYPE 0_0ffi0_0hex00_0set_0731 (exp$7 -> com)
// EXPORTGROUP 0_0ffi0_0hex00_0set_0731 2
// EXPORTPOS 0_0ffi0_0hex10_0set_0731 0
// EXPORTTYPE 0_0ffi0_0hex10_0set_0731 (exp$7 -> com)
// EXPORTGROUP 0_0ffi0_0hex10_0set_0731 3
// LIBRARY _ true
// TOTALPORTS _ 2
// OUTERMOST _ true
///// END METADATA

The details of the linker naming conventions are also beyond the aim of the post.

Compiling, linking, running

We compile each Algol file individually, and corresponding VHDL files will be synthesised for us. We then link the HDL files:

linker fibonacci-memoized.vhdl stdlib.vhdl DE3.v > main.vhdl

The order of the files is important: the “programming files” need to first, then the software-level wrappers, then the hardware IP. The main file, using the available metadata, connects the various modules together in the correct way and establishes a top-level process for starting the overall execution: you may think of it as an extremely simple boot loader.

We can now open the Quartus project file created by System Builder and add all the HDL files plus compiler_lib.vhdl which includes some additional definitions used by the compiler. Follow the the normal design flow procedure (analysis, place & route, assembly). If we don’t set the clock values for TimeQuest we will receive warnings, which we can safely ignore (for now).

Programming the FPGA is next.

And it works! You can see fibonacci(10) which is 89:

This is 59 in hex. You can also see a video of the FPGA in action.

Credit for most of the implementation work on compiler and the example goes to my student Alex Smith.

Posted in Geometry of Synthesis | Leave a comment

Game Semantics, a gentle introduction

The first paper I ever read on game semantics was Samson Abramsky & Guy McCusker’s “Linearity, Sharing and State. It was included in a great collection of papers on Algol-like Languages by Bob Tennent, my PhD advisor, and Peter O’Hearn and it was the basic background reading for my research. The game semantics paper was intriguing for two reasons. First, it was provably the most precise model of Algol to date, solving a long-standing open problem in theoretical Computer Science. Second, I didn’t understand anything from the paper. Most of the papers in that book are of course highly technical, but beyond the technicalities you could grasp intuitively features of the model which captured elegantly various aspects of the language. The Game Semantics seemed to be a lot of technicalities leading to a technical result, bereft of computational or operational intuitions.

I was in Canada at the time but, luckily, I was about to travel to the UK and had a chance to meet Guy who agreed to meet me and teach me about game semantics, and it turned out it leads to incredibly intuitive models of programming languages. I became fascinated with it and I chose it as my main research topic despite the fact that Bob, my adviser, was not a researcher in the area. Luckily, he was amazingly supportive of my newfound interest.

Why is game semantics so great? The older and more established formalisms are denotational and operational semantics. A denotational model tells you what a program is, by interpreting it as a mathematical object. An operational model tells you what a program does by giving evaluation rules which tell you how the program executes, in the abstract. Each model has its uses. A denotational model is useful if you want to prove equivalences of programs, because equivalent programs have equal models. An operational model is useful as a specification for a compiler or interpreter. Technically, the main feature of a denotational model is its compositionality, which means that the meaning of a larger program is constructed mathematically from the meanings of its sub-programs. The main feature of an operational model is its effectiveness, which means that it can be encoded algorithmically — it is an executable specification for an interpreter. Conversely, a denotational semantics is not necessarily executable and an operational semantics is not usually compositional.

Game semantics manages to be both a denotational and an operational semantics: it is defined compositionally and it can be made executable. So it can tell you in one neat package both what a program is and what a program does. But the way in which describes the operation of a program is quite unusual. Conventional operational semantics shows how a program transforms in the course of execution. For example, the sequential composition of two commands will be modeled as follows.

Denotationally, a command is a function and sequential composition is compositions of functions, resulting in a new function modeling the composite command. This is written concisely like this:

[\![ C_1;C_2]\!] =[\![ C_2]\!]\circ[\![ C_1]\!]

Operationally, what a sequential composition does, is this: if the first command in the composition executes on computational step then the composite command executes a computational step.

 If {C_1\rightarrow C'_1} then {C_1;C_2\rightarrow C'_1;C_2}.

You will find a large number of variations on this general pattern, depending on the language. A great tutorial on operational semantics is Andy Pitts’s.

Game semantics gives an interactive account of computation which, for historical reasons, it is called a game. First we need to establish that for a command the observable actions are starting the execution and finishing the execution. Lets call these actions R and D an emphasise that there is nothing else that a command can do.

Sequential composition has the following signature: it takes two commands as arguments and produces a command as a result. Therefore there are six actions involved in its behaviour:

  1. R1 – running the first command
  2. D1 – finishing the first command
  3. R2 - running the second command
  4. D2 - finishing the second command
  5. R - running the sequential composition
  6. D - finishing the sequential composition

I have added the tags 1-2 to distinguish different actions of the same kind. It should be now quite clear that this particular interaction is the operational meaning of sequential composition:

[\![ C_1;C_2]\!] =R.R1.D1.R2.D2.D

It reads as follows:

  1. start the sequential composition
  2. start the first command
  3. wait for the first command to finish
  4. start the second command
  5. wait for the second command to finish
  6. report the end of the sequential composition operation.

This is denotational because meanings of larger programs are constructed compositionally and can be compared as objects. This is operational because these trace-like objects can be seen as specifications of automata, which can be derived from them.

For more information see my invited tutorial paper Applications of Game Semantics: From Software Analysis to Hardware Synthesis, which appeared at LICS’09.

Posted in game semantics | Leave a comment

FoC: Fundamentals – of Computing? or of What?

I’m going to examine the phrase “Fundamentals of Computing” (FoC) as brand-label for a research community. The research community is real and the label is convenient, but the label’s natural interpretation is really only a part of the dynamics of the research activity.

There’s an applied activity of “computing” that is essentially based on engineering, building the computer-based technology on which we now depend so utterly. There is a whole applied science and engineering of hardware inventions, programming languages, verification techniques, etc., etc., all directed with some immediacy towards building things.

The natural interpretation of the phrase “fundamentals of computing” is as the theoretical end of this activity, a mathematical theory on which the applied activity can be founded.

This is part of the story, but not at all the whole. Much of the research impetus lies in an idea that computation gives us a new perspective on other subjects. In Turing’s 1937 paper it was a new perspective on logic, and this interest in logic is still lively. Many logic conferences will have participants from the FoC community, and the same is also now true of conferences in other fields such as (in my own experience) category theory and physics.

In these fields, work under the FoC brand cannot remotely be seen as a theoretical underpinning of applied computing. What characterizes it as FoC is, of course, the computing perspective, but also a willingness to examine the rules (logic, mathematics, physics) for their appropriateness. To me this is trying to make the formal rules correctly match the reality they are intended to apply to, somewhat like the engineering process of programming a computer.

There is a consequence of this in terms of understanding general research activity. Mainstream research in maths or science – the research that is neatly characterizable by subject area – is generally about accepting the rules and investigating their consequences. Rule questioning is peripheral or even maverick. Thus the FoC brand research can be difficult to categorize and difficult to draw statistical concusions about.

My own current project, very much about questioning logical rules in relation to physics, was funded by the EPSRC as mathematics under Logic and Combinatorics. Even there it is completely atypical, since the essence of Logic and Combinatorics is finite, discrete mathematics, and the essence of the logic I study is topological, continuous. Whatever contribution this makes to general statistics about research activity in different areas is likely to be misleading.

Martin Escardo has a nice image in terms of tectonic plates. Mainstream research is at the centre of the plates. Much FoC-brand research is at the boundaries, where different plates rub up against each other, not mainstream to any of them. Yet it is precisely there that – from time to time – the earth quakes.

Steve Vickers

Posted in Uncategorized | 5 Comments