Built with Alectryon, running Coq+SerAPI v8.13.0+0.13.0. Bubbles () indicate interactive fragments: hover for details, tap to reveal contents. Use Ctrl+↑ Ctrl+↓ to navigate, Ctrl+🖱️ to focus. On Mac, use instead of Ctrl.

Tutorial

Welcome! This is a quick primer for designing circuits with the Cava DSL. This tutorial will not explain Coq syntax in depth, but will use the same few patterns throughout; you shouldn't need to be a Coq expert to follow along. We'll walk through a few small examples end-to-end, showing you how to define, simulate, and generate netlists for circuits in Cava.

This page (thanks to the Alectryon system) allows you to see the Coq output for each line that has output. Try hovering over the following line (if on mobile, tap the line):

= 3 : nat

See the banner at the top of the page for instructions on how to navigate the proofs.

Setup

If you are viewing this tutorial on the web and just trying to get a general idea of how Cava works, skip to the next section. If you are trying to write your own circuits or step through this tutorial locally, here are the quick-start instructions for installing Cava:

$ git clone https://github.com/project-oak/silveroak.git
$ cd silveroak
$ make update-third_party
$ make -j4 cava-coq

You can now make the Cava libraries visible to your project by adding the following lines to your project's _CoqProject file:

-R path/to/silveroak/cava/Cava Cava
-R path/to/silveroak/third_party/coq-ext-lib/theories ExtLib
-R path/to/silveroak/third_party/bedrock2/deps/coqutil/src/coqutil coqutil

If you don't have an existing project, you can set up a minimal one as follows:

  1. Create a new directory
  2. Create a file called _CoqProject in the new directory containing the three lines above, with path/to/silveroak replaced with the path to your clone of silveroak
  3. Create a new file with a .v extension and open it with your favorite Coq IDE option (coqide, Emacs's Proof General, Vim's coqtail, or VS Code, among others). The _CoqProject file should be detected automatically; try writing the imports below into the .v file, and step through them to check that everything's working.

The following lines import everything you need to define and simulate circuits, as well as some convenience notations:

Require Import Cava.Cava.
Import Circuit.Notations.

If you also want to do proofs about circuits, you'll need this import also:

Require Import Cava.CavaProperties.

Example 1 : Inverter

To start, let's define a 1-bit inverter.

Definition inverter
           {signal : SignalType -> Type}
           {semantics : Cava signal}
  : Circuit (signal Bit) (signal Bit) :=
  Comb inv.

A few things to notice here:

  • SignalType is Cava's type system. The inverter is parameterized over signal, which converts SignalTypes to Coq types. Bit is one example of a SignalType; we'll see more examples later on.
  • Comb is short for "combinational"; our inverter has no loops, registers, or timing requirements, so it is a purely combinational circuit.
  • The inverter is also paramterized over semantics, an instance of the typeclass Cava. This instance provides implementations of circuit primitives, such as 1-bit logic gates. One primitive gate is a 1-bit inverter inv, so our inverter is just a simple invocation of the primitive.

Normally, we'd write circuit definitions a little more concisely by writing them inside a Section that contains signal and semantics as context variables, like this:

Section WithCava.
  Context {signal} {semantics : Cava signal}.

  Definition inverter_concise : Circuit (signal Bit) (signal Bit)
    := Comb inv.

For the rest of the circuit definitions in this tutorial, you can assume that we're inside the section and that every definition is parameterized over the signal and semantics context variables.

Back to our inverter. Let's take a closer look at the inv primitive.

  
inv : signal Bit -> cava (signal Bit)

You can see in the type signature signal Bit -> cava (signal Bit) that inv is defined as a pure Coq function in terms of a monad called cava. The cava monad, like inv, is provided by semantics. The monad is used to capture sharing; it's semantically different in Cava to write:

x <- inv zero ;;
y <- inv zero ;;
xor2 x y

than it is to write:

x <- inv zero ;;
xor2 x x

Both expressions have the same meaning, and if we were using Gallina let binders there would be no difference. But the generated circuit can use two wires for the first definition, and fork the same wire in the second. As circuit diagrams, this is the difference between:

       +-----+      +-----+
0 -----| inv |------|     |
       +-----+      | xor |----- out
       +-----+      |     |
0 -----| inv |------|     |
       +-----+      +-----+

and:

                      +-----+
                  +---|     |
                  |   | xor |---- out
       +-----+    |   |     |
0 -----| inv |----+---|     |
       +-----+        +-----+

This difference isn't significant in determining what the value of out will be, but it can be very useful when trying to exercise fine-grained control over circuit layout and area! At a first approximation, you can think of a monadic bind (_ <- _ ;; ...) as naming a wire in the circuit graph.

If the monad notations are unfamiliar, the reference has more information on those.

We could have represented sharing by describing circuit graphs with a list of nodes and edges. However, this is essentially the "machine code" of structural hardware descriptions, and is far too tedious a representation for humans to work with. The monadic-function abstraction allows human engineers to think about the functional behavior and composition of circuits at a more intuitive level.

Parameterizing over the cava monad and primitive implementations allows us to use different instances of Cava to interpret the same circuit definition in different ways. One Cava instance generates netlists by adding and connecting wires in the background using a state monad. For circuit simulations and proofs of functional correctness, on the other hand, we don't care about sharing at all; these use no-op identity monad that acts the same as a let binder.

Let's use our inverter definition to see these two interpretations in action.

First, let's generate a netlist. We need to define an interface that describes the circuit's input and output ports and behavior relative to the (global) clock and reset signals. Then we can compute a netlist (type CavaState), which describes the full layout of the circuit in a way that can be easily translated to SystemVerilog (we do this using an unverified but small Haskell program).

(* netlist-generating semantics *)
Existing Instance CavaCombinationalNet.

Definition inverter_interface
  := sequentialInterface "inverter_interface"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "i" Bit]
     [mkPort "o" Bit].

= {| netNumber := 1; vectorNumber := 0; vectorDeclarations := []; externalDeclarations := []; clock := Some (mkTaggedEdge (NamedWire "clk") PositiveEdge); reset := Some (mkTaggedEdge (NamedWire "rst") PositiveEdge); module := {| moduleName := "inverter_interface"; netlist := [AssignSignal (NamedWire "o") (Wire 0); Not (NamedWire "i") (Wire 0)]; inputs := [{| port_name := "i"; port_type := Bit |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Bit |}] |}; libraryModules := [] |} : CavaState
(* A closer look at the circuit body *)
= {| moduleName := "inverter_interface"; netlist := [AssignSignal (NamedWire "o") (Wire 0); Not (NamedWire "i") (Wire 0)]; inputs := [{| port_name := "i"; port_type := Bit |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Bit |}] |} : Module

You may notice that we're using something called sequentialInterface here, and referring to clock and reset signals, even though our inverter is a purely combinational circuit. We introduce timing in the netlist interface here in order to drive the circuit with multiple inputs over time, and to plug it in as a subcomponent of circuits that are not combinational.

Now, let's simulate the circuit, which can be useful for testing and proving functional correctness. Here, we use the identity-monad interpretation. The signal for this Cava instance is combType, which interprets a Bit simply as a Coq bool. If we provide the three inputs [true; false; true] to the circuit simulation function simulate, we'll get [false; true; false]:

(* identity-monad semantics *)
Existing Instance CombinationalSemantics.

= [false; true; false] : list (combType Bit)
= [false; true; false; false; false; true] : list (combType Bit)

In a later example, we will demonstrate how to additionally validate the SystemVerilog output by creating a test and checking that a Verilog simulation or even a running FPGA implementation of the circuit agrees with the Coq simulation above.

We can use the simulation to write proofs about the circuit. For instance, we can prove that inverter obeys a natural Coq specification:

input:list bool

simulate inverter input = map negb input
input:list bool

simulate inverter input = map negb input
(* inline the circuit definition *)
input:list bool

simulate (Comb inv) input = map negb input
(* simplify simulate to create an expression in terms of Coq lists *)
input:list bool

map inv input = map negb input
(* assert that the two List.map functions are equivalent *)
input:list bool

forall a : bool, inv a = negb a
input:list bool
a:bool

inv a = negb a
(* inline the inv primitive (fun x => ret (negb x)) *)
input:list bool
a:bool

ret (negb a) = negb a
(* simplify the identity monad expressions *)
input:list bool
a:bool

negb a = negb a
reflexivity. Qed.

We can even prove that composing two inverters is the same as doing nothing. Here, >==> is circuit composition (a Kleisli arrow). The proof structure is pretty similar.

input:list bool

simulate (inverter >==> inverter) input = input
input:list bool

simulate (inverter >==> inverter) input = input
input:list bool

simulate (Comb inv >==> Comb inv) input = input
input:list bool

map inv (map inv input) = input
input:list bool

map (fun x : combType Bit => inv (inv x)) input = input
input:list bool

forall a : combType Bit, inv (inv a) = a
input:list bool
a:combType Bit

inv (inv a) = a
input:list bool
a:combType Bit

ret (negb (ret (negb a))) = a
input:list bool
a:combType Bit

negb (negb a) = a
apply Bool.negb_involutive. Qed.

A note about reading Coq proofs: in general, it's more important to understand the lemma statement (the part before Proof) than it is to understand the proof body. The lemma statement shows what is being proven, and the proof body contains an "argument" to Coq that the statement is true.

To summarize, there are three things you can do with Cava circuits:

  1. Define them (parameterized over an abstract Cava instance)
  2. Generate netlists for them using the CavaCombinationalNet instance and the makeCircuitNetlist function. These netlists can then be translated into SystemVerilog.
  3. Simulate them using simulate, and prove things about the simulations, by plugging in the CombinationalSemantics instance.

In the following examples, we'll use this exact same three-part pattern to explore more complex circuits.

Example 2 : Byte xor

Our next example is a circuit that xors two bytes:

  Definition xor_byte :
    Circuit (signal (Vec Bit 8) * signal (Vec Bit 8))
            (signal (Vec Bit 8)) :=
    Comb (Vec.map2 xor2).

This circuit maps a 1-bit xor (xor2) over the two input vectors. xor2 is one of the primitives provided by the Cava instance, like inv. Once again, this is a combinational circuit, so we define it by wrapping a monadic function with Comb.

The Vec here is another SignalType, with a slightly more complicated construction than Bit. A Vec Bit 8 is a vector of 8 bits: a byte. Vectors can be formed from any other SignalType, including other vectors; Vec (Vec (Vec Bit 8) 4) 2) is a valid construction representing a two-dimensional array of bytes (equivalently, a three-dimensional array of bits).

The Vec.map2 definition is from Cava's vector library. It's important not to confuse Vec, the SignalType in Cava's type system, with Vector.t, Coq's standard library vector type. In simulation, Vec is translated into Vector.t, so you may see both in the codebase. You can also convert back and forth between Vec and Vector.t using the Cava primitives packV and unpackV. However, Cava's vector library mirrors most of the definitions available for Coq standard library vectors, so it's usually best to use those definitions instead: use Vec.map2 instead of unpackV, Vector.map2, and packV.

To see more definitions from Cava's core library, try taking a look at the Cava reference, which documents its contents.

To generate a netlist for this circuit, we use mostly the same procedure as for the inverter, except that we change the input and output port types to match the circuit's type signature.

Definition xor_byte_interface
  := sequentialInterface "xor_byte_interface"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "v1" (Vec Bit 8); mkPort "v2" (Vec Bit 8)]
     [mkPort "o" (Vec Bit 8)].

= {| moduleName := "xor_byte_interface"; netlist := [AssignSignal (NamedVector Bit 8 "o") (LocalVec Bit 8 0); AssignSignal (LocalVec Bit 8 0) (VecLit [Wire 16; Wire 17; Wire 18; Wire 19; Wire 20; Wire 21; Wire 22; Wire 23]); Xor (Wire 7) (Wire 15) (Wire 23); Xor (Wire 6) (Wire 14) (Wire 22); Xor (Wire 5) (Wire 13) (Wire 21); Xor (Wire 4) (Wire 12) (Wire 20); Xor (Wire 3) (Wire 11) (Wire 19); Xor (Wire 2) (Wire 10) (Wire 18); Xor (Wire 1) (Wire 9) (Wire 17); Xor (Wire 0) (Wire 8) (Wire 16); AssignSignal (Wire 15) (IndexConst (NamedVector Bit 8 "v2") 7); AssignSignal (Wire 14) (IndexConst (NamedVector Bit 8 "v2") 6); AssignSignal (Wire 13) (IndexConst (NamedVector Bit 8 "v2") 5); AssignSignal (Wire 12) (IndexConst (NamedVector Bit 8 "v2") 4); AssignSignal (Wire 11) (IndexConst (NamedVector Bit 8 "v2") 3); AssignSignal (Wire 10) (IndexConst (NamedVector Bit 8 "v2") 2); AssignSignal (Wire 9) (IndexConst (NamedVector Bit 8 "v2") 1); AssignSignal (Wire 8) (IndexConst (NamedVector Bit 8 "v2") 0); AssignSignal (Wire 7) (IndexConst (NamedVector Bit 8 "v1") 7); AssignSignal (Wire 6) (IndexConst (NamedVector Bit 8 "v1") 6); AssignSignal (Wire 5) (IndexConst (NamedVector Bit 8 "v1") 5); AssignSignal (Wire 4) (IndexConst (NamedVector Bit 8 "v1") 4); AssignSignal (Wire 3) (IndexConst (NamedVector Bit 8 "v1") 3); AssignSignal (Wire 2) (IndexConst (NamedVector Bit 8 "v1") 2); AssignSignal (Wire 1) (IndexConst (NamedVector Bit 8 "v1") 1); AssignSignal (Wire 0) (IndexConst (NamedVector Bit 8 "v1") 0)]; inputs := [{| port_name := "v2"; port_type := Vec Bit 8 |}; {| port_name := "v1"; port_type := Vec Bit 8 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 8 |}] |} : Module

Tuples in the input or output types become lists of ports for the netlist interface, so signal (Vec Bit 8) * signal (Vec Bit 8) becomes [mkPort "v1" (Vec Bit 8); mkPort "v2" (Vec Bit 8)]. The names of the ports ("v1", "v2", and "o") are just for readability and potentially for reference by other netlists; they can be named however you prefer.

We can also, as before, simulate the circuit.

= [[true; false; true; true; false; false; false; false]%vector]%list : list (combType (Vec Bit 8))

Literal bit vectors are not especially readable, though; it's not immediately clear that this simulation is 7 xor 10 = 13. For simulations with bitvectors, it's often clearer to use natural-number-to-bitvector conversions from the Coq standard library :

= [13%N]%list : list N

Finally, we can prove that the circuit is correct. In this case, we prove that the circuit's behavior matches the BVxor definition from the standard library, specialized to bit-vectors of length 8.

i:list (Vector.t bool 8 * Vector.t bool 8)

simulate xor_byte i = map (fun '(v1, v2) => BVxor 8 v1 v2) i
i:list (Vector.t bool 8 * Vector.t bool 8)

simulate xor_byte i = map (fun '(v1, v2) => BVxor 8 v1 v2) i
i:list (Vector.t bool 8 * Vector.t bool 8)

simulate (Comb (Vec.map2 xor2)) i = map (fun '(v1, v2) => BVxor 8 v1 v2) i
i:list (Vector.t bool 8 * Vector.t bool 8)

map (Vec.map2 xor2) i = map (fun '(v1, v2) => BVxor 8 v1 v2) i
i:list (Vector.t bool 8 * Vector.t bool 8)
a:(Vector.t bool 8 * Vector.t bool 8)%type

Vec.map2 xor2 a = (let '(v1, v2) := a in BVxor 8 v1 v2)
i:list (Vector.t bool 8 * Vector.t bool 8)
a:(Vector.t bool 8 * Vector.t bool 8)%type

Vec.map2 xor2 (fst a, snd a) = BVxor 8 (fst a) (snd a)
i:list (Vector.t bool 8 * Vector.t bool 8)
a:(Vector.t bool 8 * Vector.t bool 8)%type

Vec.map2 xor2 (fst a, snd a) = Vector.map2 xorb (fst a) (snd a)
i:list (Vector.t bool 8 * Vector.t bool 8)
a:(Vector.t bool 8 * Vector.t bool 8)%type

Vector.map2 (fun x y : combType Bit => xorb x y) (fst a) (snd a) = Vector.map2 xorb (fst a) (snd a)
i:list (Vector.t bool 8 * Vector.t bool 8)
a:(Vector.t bool 8 * Vector.t bool 8)%type

forall a b : combType Bit, xorb a b = xorb a b
reflexivity. Qed.

Again, no need to focus too much on the body of the proof here; understanding the lemma statement is the most important part. However, one interesting thing to note is that the proof is not computational; we don't analyze the 2^16 possible inputs separately. In fact, we never destruct the vectors or refer to the length at all, which leads us to our next example.

Example 3: Bit-vector xor

As it turns out, we can define xor_byte over arbitrary-length bitvectors with very little modification. The circuit is virtually identical, except that it takes a length argument n and all the 8s are replaced with n:

  Definition xor_bitvec (n : nat) :
    Circuit (signal (Vec Bit n) * signal (Vec Bit n))
            (signal (Vec Bit n)) :=
    Comb (Vec.map2 xor2).

We can define an interface for this circuit that also takes n as an argument, and then compute a netlist for any number of bits we want.

Definition xor_bitvec_interface {n : nat}
  := sequentialInterface "xor_bitvec_interface"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "v1" (Vec Bit n); mkPort "v2" (Vec Bit n)]
     [mkPort "o" (Vec Bit n)].

(* Netlist for a 2-bit xor *)
= {| moduleName := "xor_bitvec_interface"; netlist := [AssignSignal (NamedVector Bit 2 "o") (LocalVec Bit 2 0); AssignSignal (LocalVec Bit 2 0) (VecLit [Wire 4; Wire 5]); Xor (Wire 1) (Wire 3) (Wire 5); Xor (Wire 0) (Wire 2) (Wire 4); AssignSignal (Wire 3) (IndexConst (NamedVector Bit 2 "v2") 1); AssignSignal (Wire 2) (IndexConst (NamedVector Bit 2 "v2") 0); AssignSignal (Wire 1) (IndexConst (NamedVector Bit 2 "v1") 1); AssignSignal (Wire 0) (IndexConst (NamedVector Bit 2 "v1") 0)]; inputs := [{| port_name := "v2"; port_type := Vec Bit 2 |}; {| port_name := "v1"; port_type := Vec Bit 2 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 2 |}] |} : Module
(* Netlist for a 100-bit xor! *)
= {| moduleName := "xor_bitvec_interface"; netlist := [AssignSignal (NamedVector Bit 100 "o") (LocalVec Bit 100 0); AssignSignal (LocalVec Bit 100 0) (VecLit [Wire 200; Wire 201; Wire 202; Wire 203; Wire 204; Wire 205; Wire 206; Wire 207; Wire 208; Wire 209; Wire 210; Wire 211; Wire 212; Wire 213; Wire 214; Wire 215; Wire 216; Wire 217; Wire 218; Wire 219; Wire 220; Wire 221; Wire 222; Wire 223; Wire 224; Wire 225; Wire 226; Wire 227; Wire 228; Wire 229; Wire 230; Wire 231; Wire 232; Wire 233; Wire 234; Wire 235; Wire 236; Wire 237; Wire 238; Wire 239; Wire 240; Wire 241; Wire 242; Wire 243; Wire 244; Wire 245; Wire 246; Wire 247; Wire 248; Wire 249; Wire 250; Wire 251; Wire 252; Wire 253; Wire 254; Wire 255; Wire 256; Wire 257; Wire 258; Wire 259; Wire 260; Wire 261; Wire 262; Wire 263; Wire 264; Wire 265; Wire 266; Wire 267; Wire 268; Wire 269; Wire 270; Wire 271; Wire 272; Wire 273; Wire 274; Wire 275; Wire 276; Wire 277; Wire 278; Wire 279; Wire 280; Wire 281; Wire 282; Wire 283; Wire 284; Wire 285; Wire 286; Wire 287; Wire 288; Wire 289; Wire 290; Wire 291; Wire 292; Wire 293; Wire 294; Wire 295; Wire 296; Wire 297; Wire 298; Wire 299]); Xor (Wire 99) (Wire 199) (Wire 299); Xor (Wire 98) (Wire 198) (Wire 298); Xor (Wire 97) (Wire 197) (Wire 297); Xor (Wire 96) (Wire 196) (Wire 296); Xor (Wire 95) (Wire 195) (Wire 295); Xor (Wire 94) (Wire 194) (Wire 294); Xor (Wire 93) (Wire 193) (Wire 293); Xor (Wire 92) (Wire 192) (Wire 292); Xor (Wire 91) (Wire 191) (Wire 291); Xor (Wire 90) (Wire 190) (Wire 290); Xor (Wire 89) (Wire 189) (Wire 289); Xor (Wire 88) (Wire 188) (Wire 288); Xor (Wire 87) (Wire 187) (Wire 287); Xor (Wire 86) (Wire 186) (Wire 286); Xor (Wire 85) (Wire 185) (Wire 285); Xor (Wire 84) (Wire 184) (Wire 284); Xor (Wire 83) (Wire 183) (Wire 283); Xor (Wire 82) (Wire 182) (Wire 282); Xor (Wire 81) (Wire 181) (Wire 281); Xor (Wire 80) (Wire 180) (Wire 280); Xor (Wire 79) (Wire 179) (Wire 279); Xor (Wire 78) (Wire 178) (Wire 278); Xor (Wire 77) (Wire 177) (Wire 277); Xor (Wire 76) (Wire 176) (Wire 276); Xor (Wire 75) (Wire 175) (Wire 275); Xor (Wire 74) (Wire 174) (Wire 274); Xor (Wire 73) (Wire 173) (Wire 273); Xor (Wire 72) (Wire 172) (Wire 272); Xor (Wire 71) (Wire 171) (Wire 271); Xor (Wire 70) (Wire 170) (Wire 270); Xor (Wire 69) (Wire 169) (Wire 269); Xor (Wire 68) (Wire 168) (Wire 268); Xor (Wire 67) (Wire 167) (Wire 267); Xor (Wire 66) (Wire 166) (Wire 266); Xor (Wire 65) (Wire 165) (Wire 265); Xor (Wire 64) (Wire 164) (Wire 264); Xor (Wire 63) (Wire 163) (Wire 263); Xor (Wire 62) (Wire 162) (Wire 262); Xor (Wire 61) (Wire 161) (Wire 261); Xor (Wire 60) (Wire 160) (Wire 260); Xor (Wire 59) (Wire 159) (Wire 259); Xor (Wire 58) (Wire 158) (Wire 258); Xor (Wire 57) (Wire 157) (Wire 257); Xor (Wire 56) (Wire 156) (Wire 256); Xor (Wire 55) (Wire 155) (Wire 255); Xor (Wire 54) (Wire 154) (Wire 254); Xor (Wire 53) (Wire 153) (Wire 253); Xor (Wire 52) (Wire 152) (Wire 252); Xor (Wire 51) (Wire 151) (Wire 251); Xor (Wire 50) (Wire 150) (Wire 250); Xor (Wire 49) (Wire 149) (Wire 249); Xor (Wire 48) (Wire 148) (Wire 248); Xor (Wire 47) (Wire 147) (Wire 247); Xor (Wire 46) (Wire 146) (Wire 246); Xor (Wire 45) (Wire 145) (Wire 245); Xor (Wire 44) (Wire 144) (Wire 244); Xor (Wire 43) (Wire 143) (Wire 243); Xor (Wire 42) (Wire 142) (Wire 242); Xor (Wire 41) (Wire 141) (Wire 241); Xor (Wire 40) (Wire 140) (Wire 240); Xor (Wire 39) (Wire 139) (Wire 239); Xor (Wire 38) (Wire 138) (Wire 238); Xor (Wire 37) (Wire 137) (Wire 237); Xor (Wire 36) (Wire 136) (Wire 236); Xor (Wire 35) (Wire 135) (Wire 235); Xor (Wire 34) (Wire 134) (Wire 234); Xor (Wire 33) (Wire 133) (Wire 233); Xor (Wire 32) (Wire 132) (Wire 232); Xor (Wire 31) (Wire 131) (Wire 231); Xor (Wire 30) (Wire 130) (Wire 230); Xor (Wire 29) (Wire 129) (Wire 229); Xor (Wire 28) (Wire 128) (Wire 228); Xor (Wire 27) (Wire 127) (Wire 227); Xor (Wire 26) (Wire 126) (Wire 226); Xor (Wire 25) (Wire 125) (Wire 225); Xor (Wire 24) (Wire 124) (Wire 224); Xor (Wire 23) (Wire 123) (Wire 223); Xor (Wire 22) (Wire 122) (Wire 222); Xor (Wire 21) (Wire 121) (Wire 221); Xor (Wire 20) (Wire 120) (Wire 220); Xor (Wire 19) (Wire 119) (Wire 219); Xor (Wire 18) (Wire 118) (Wire 218); Xor (Wire 17) (Wire 117) (Wire 217); Xor (Wire 16) (Wire 116) (Wire 216); Xor (Wire 15) (Wire 115) (Wire 215); Xor (Wire 14) (Wire 114) (Wire 214); Xor (Wire 13) (Wire 113) (Wire 213); Xor (Wire 12) (Wire 112) (Wire 212); Xor (Wire 11) (Wire 111) (Wire 211); Xor (Wire 10) (Wire 110) (Wire 210); Xor (Wire 9) (Wire 109) (Wire 209); Xor (Wire 8) (Wire 108) (Wire 208); Xor (Wire 7) (Wire 107) (Wire 207); Xor (Wire 6) (Wire 106) (Wire 206); Xor (Wire 5) (Wire 105) (Wire 205); Xor (Wire 4) (Wire 104) (Wire 204); Xor (Wire 3) (Wire 103) (Wire 203); Xor (Wire 2) (Wire 102) (Wire 202); Xor (Wire 1) (Wire 101) (Wire 201); Xor (Wire 0) (Wire 100) (Wire 200); AssignSignal (Wire 199) (IndexConst (NamedVector Bit 100 "v2") 99); AssignSignal (Wire 198) (IndexConst (NamedVector Bit 100 "v2") 98); AssignSignal (Wire 197) (IndexConst (NamedVector Bit 100 "v2") 97); AssignSignal (Wire 196) (IndexConst (NamedVector Bit 100 "v2") 96); AssignSignal (Wire 195) (IndexConst (NamedVector Bit 100 "v2") 95); AssignSignal (Wire 194) (IndexConst (NamedVector Bit 100 "v2") 94); AssignSignal (Wire 193) (IndexConst (NamedVector Bit 100 "v2") 93); AssignSignal (Wire 192) (IndexConst (NamedVector Bit 100 "v2") 92); AssignSignal (Wire 191) (IndexConst (NamedVector Bit 100 "v2") 91); AssignSignal (Wire 190) (IndexConst (NamedVector Bit 100 "v2") 90); AssignSignal (Wire 189) (IndexConst (NamedVector Bit 100 "v2") 89); AssignSignal (Wire 188) (IndexConst (NamedVector Bit 100 "v2") 88); AssignSignal (Wire 187) (IndexConst (NamedVector Bit 100 "v2") 87); AssignSignal (Wire 186) (IndexConst (NamedVector Bit 100 "v2") 86); AssignSignal (Wire 185) (IndexConst (NamedVector Bit 100 "v2") 85); AssignSignal (Wire 184) (IndexConst (NamedVector Bit 100 "v2") 84); AssignSignal (Wire 183) (IndexConst (NamedVector Bit 100 "v2") 83); AssignSignal (Wire 182) (IndexConst (NamedVector Bit 100 "v2") 82); AssignSignal (Wire 181) (IndexConst (NamedVector Bit 100 "v2") 81); AssignSignal (Wire 180) (IndexConst (NamedVector Bit 100 "v2") 80); AssignSignal (Wire 179) (IndexConst (NamedVector Bit 100 "v2") 79); AssignSignal (Wire 178) (IndexConst (NamedVector Bit 100 "v2") 78); AssignSignal (Wire 177) (IndexConst (NamedVector Bit 100 "v2") 77); AssignSignal (Wire 176) (IndexConst (NamedVector Bit 100 "v2") 76); AssignSignal (Wire 175) (IndexConst (NamedVector Bit 100 "v2") 75); AssignSignal (Wire 174) (IndexConst (NamedVector Bit 100 "v2") 74); AssignSignal (Wire 173) (IndexConst (NamedVector Bit 100 "v2") 73); AssignSignal (Wire 172) (IndexConst (NamedVector Bit 100 "v2") 72); AssignSignal (Wire 171) (IndexConst (NamedVector Bit 100 "v2") 71); AssignSignal (Wire 170) (IndexConst (NamedVector Bit 100 "v2") 70); AssignSignal (Wire 169) (IndexConst (NamedVector Bit 100 "v2") 69); AssignSignal (Wire 168) (IndexConst (NamedVector Bit 100 "v2") 68); AssignSignal (Wire 167) (IndexConst (NamedVector Bit 100 "v2") 67); AssignSignal (Wire 166) (IndexConst (NamedVector Bit 100 "v2") 66); AssignSignal (Wire 165) (IndexConst (NamedVector Bit 100 "v2") 65); AssignSignal (Wire 164) (IndexConst (NamedVector Bit 100 "v2") 64); AssignSignal (Wire 163) (IndexConst (NamedVector Bit 100 "v2") 63); AssignSignal (Wire 162) (IndexConst (NamedVector Bit 100 "v2") 62); AssignSignal (Wire 161) (IndexConst (NamedVector Bit 100 "v2") 61); AssignSignal (Wire 160) (IndexConst (NamedVector Bit 100 "v2") 60); AssignSignal (Wire 159) (IndexConst (NamedVector Bit 100 "v2") 59); AssignSignal (Wire 158) (IndexConst (NamedVector Bit 100 "v2") 58); AssignSignal (Wire 157) (IndexConst (NamedVector Bit 100 "v2") 57); AssignSignal (Wire 156) (IndexConst (NamedVector Bit 100 "v2") 56); AssignSignal (Wire 155) (IndexConst (NamedVector Bit 100 "v2") 55); AssignSignal (Wire 154) (IndexConst (NamedVector Bit 100 "v2") 54); AssignSignal (Wire 153) (IndexConst (NamedVector Bit 100 "v2") 53); AssignSignal (Wire 152) (IndexConst (NamedVector Bit 100 "v2") 52); AssignSignal (Wire 151) (IndexConst (NamedVector Bit 100 "v2") 51); AssignSignal (Wire 150) (IndexConst (NamedVector Bit 100 "v2") 50); AssignSignal (Wire 149) (IndexConst (NamedVector Bit 100 "v2") 49); AssignSignal (Wire 148) (IndexConst (NamedVector Bit 100 "v2") 48); AssignSignal (Wire 147) (IndexConst (NamedVector Bit 100 "v2") 47); AssignSignal (Wire 146) (IndexConst (NamedVector Bit 100 "v2") 46); AssignSignal (Wire 145) (IndexConst (NamedVector Bit 100 "v2") 45); AssignSignal (Wire 144) (IndexConst (NamedVector Bit 100 "v2") 44); AssignSignal (Wire 143) (IndexConst (NamedVector Bit 100 "v2") 43); AssignSignal (Wire 142) (IndexConst (NamedVector Bit 100 "v2") 42); AssignSignal (Wire 141) (IndexConst (NamedVector Bit 100 "v2") 41); AssignSignal (Wire 140) (IndexConst (NamedVector Bit 100 "v2") 40); AssignSignal (Wire 139) (IndexConst (NamedVector Bit 100 "v2") 39); AssignSignal (Wire 138) (IndexConst (NamedVector Bit 100 "v2") 38); AssignSignal (Wire 137) (IndexConst (NamedVector Bit 100 "v2") 37); AssignSignal (Wire 136) (IndexConst (NamedVector Bit 100 "v2") 36); AssignSignal (Wire 135) (IndexConst (NamedVector Bit 100 "v2") 35); AssignSignal (Wire 134) (IndexConst (NamedVector Bit 100 "v2") 34); AssignSignal (Wire 133) (IndexConst (NamedVector Bit 100 "v2") 33); AssignSignal (Wire 132) (IndexConst (NamedVector Bit 100 "v2") 32); AssignSignal (Wire 131) (IndexConst (NamedVector Bit 100 "v2") 31); AssignSignal (Wire 130) (IndexConst (NamedVector Bit 100 "v2") 30); AssignSignal (Wire 129) (IndexConst (NamedVector Bit 100 "v2") 29); AssignSignal (Wire 128) (IndexConst (NamedVector Bit 100 "v2") 28); AssignSignal (Wire 127) (IndexConst (NamedVector Bit 100 "v2") 27); AssignSignal (Wire 126) (IndexConst (NamedVector Bit 100 "v2") 26); AssignSignal (Wire 125) (IndexConst (NamedVector Bit 100 "v2") 25); AssignSignal (Wire 124) (IndexConst (NamedVector Bit 100 "v2") 24); AssignSignal (Wire 123) (IndexConst (NamedVector Bit 100 "v2") 23); AssignSignal (Wire 122) (IndexConst (NamedVector Bit 100 "v2") 22); AssignSignal (Wire 121) (IndexConst (NamedVector Bit 100 "v2") 21); AssignSignal (Wire 120) (IndexConst (NamedVector Bit 100 "v2") 20); AssignSignal (Wire 119) (IndexConst (NamedVector Bit 100 "v2") 19); AssignSignal (Wire 118) (IndexConst (NamedVector Bit 100 "v2") 18); AssignSignal (Wire 117) (IndexConst (NamedVector Bit 100 "v2") 17); AssignSignal (Wire 116) (IndexConst (NamedVector Bit 100 "v2") 16); AssignSignal (Wire 115) (IndexConst (NamedVector Bit 100 "v2") 15); AssignSignal (Wire 114) (IndexConst (NamedVector Bit 100 "v2") 14); AssignSignal (Wire 113) (IndexConst (NamedVector Bit 100 "v2") 13); AssignSignal (Wire 112) (IndexConst (NamedVector Bit 100 "v2") 12); AssignSignal (Wire 111) (IndexConst (NamedVector Bit 100 "v2") 11); AssignSignal (Wire 110) (IndexConst (NamedVector Bit 100 "v2") 10); AssignSignal (Wire 109) (IndexConst (NamedVector Bit 100 "v2") 9); AssignSignal (Wire 108) (IndexConst (NamedVector Bit 100 "v2") 8); AssignSignal (Wire 107) (IndexConst (NamedVector Bit 100 "v2") 7); AssignSignal (Wire 106) (IndexConst (NamedVector Bit 100 "v2") 6); AssignSignal (Wire 105) (IndexConst (NamedVector Bit 100 "v2") 5); AssignSignal (Wire 104) (IndexConst (NamedVector Bit 100 "v2") 4); AssignSignal (Wire 103) (IndexConst (NamedVector Bit 100 "v2") 3); AssignSignal (Wire 102) (IndexConst (NamedVector Bit 100 "v2") 2); AssignSignal (Wire 101) (IndexConst (NamedVector Bit 100 "v2") 1); AssignSignal (Wire 100) (IndexConst (NamedVector Bit 100 "v2") 0); AssignSignal (Wire 99) (IndexConst (NamedVector Bit 100 "v1") 99); AssignSignal (Wire 98) (IndexConst (NamedVector Bit 100 "v1") 98); AssignSignal (Wire 97) (IndexConst (NamedVector Bit 100 "v1") 97); AssignSignal (Wire 96) (IndexConst (NamedVector Bit 100 "v1") 96); AssignSignal (Wire 95) (IndexConst (NamedVector Bit 100 "v1") 95); AssignSignal (Wire 94) (IndexConst (NamedVector Bit 100 "v1") 94); AssignSignal (Wire 93) (IndexConst (NamedVector Bit 100 "v1") 93); AssignSignal (Wire 92) (IndexConst (NamedVector Bit 100 "v1") 92); AssignSignal (Wire 91) (IndexConst (NamedVector Bit 100 "v1") 91); AssignSignal (Wire 90) (IndexConst (NamedVector Bit 100 "v1") 90); AssignSignal (Wire 89) (IndexConst (NamedVector Bit 100 "v1") 89); AssignSignal (Wire 88) (IndexConst (NamedVector Bit 100 "v1") 88); AssignSignal (Wire 87) (IndexConst (NamedVector Bit 100 "v1") 87); AssignSignal (Wire 86) (IndexConst (NamedVector Bit 100 "v1") 86); AssignSignal (Wire 85) (IndexConst (NamedVector Bit 100 "v1") 85); AssignSignal (Wire 84) (IndexConst (NamedVector Bit 100 "v1") 84); AssignSignal (Wire 83) (IndexConst (NamedVector Bit 100 "v1") 83); AssignSignal (Wire 82) (IndexConst (NamedVector Bit 100 "v1") 82); AssignSignal (Wire 81) (IndexConst (NamedVector Bit 100 "v1") 81); AssignSignal (Wire 80) (IndexConst (NamedVector Bit 100 "v1") 80); AssignSignal (Wire 79) (IndexConst (NamedVector Bit 100 "v1") 79); AssignSignal (Wire 78) (IndexConst (NamedVector Bit 100 "v1") 78); AssignSignal (Wire 77) (IndexConst (NamedVector Bit 100 "v1") 77); AssignSignal (Wire 76) (IndexConst (NamedVector Bit 100 "v1") 76); AssignSignal (Wire 75) (IndexConst (NamedVector Bit 100 "v1") 75); AssignSignal (Wire 74) (IndexConst (NamedVector Bit 100 "v1") 74); AssignSignal (Wire 73) (IndexConst (NamedVector Bit 100 "v1") 73); AssignSignal (Wire 72) (IndexConst (NamedVector Bit 100 "v1") 72); AssignSignal (Wire 71) (IndexConst (NamedVector Bit 100 "v1") 71); AssignSignal (Wire 70) (IndexConst (NamedVector Bit 100 "v1") 70); AssignSignal (Wire 69) (IndexConst (NamedVector Bit 100 "v1") 69); AssignSignal (Wire 68) (IndexConst (NamedVector Bit 100 "v1") 68); AssignSignal (Wire 67) (IndexConst (NamedVector Bit 100 "v1") 67); AssignSignal (Wire 66) (IndexConst (NamedVector Bit 100 "v1") 66); AssignSignal (Wire 65) (IndexConst (NamedVector Bit 100 "v1") 65); AssignSignal (Wire 64) (IndexConst (NamedVector Bit 100 "v1") 64); AssignSignal (Wire 63) (IndexConst (NamedVector Bit 100 "v1") 63); AssignSignal (Wire 62) (IndexConst (NamedVector Bit 100 "v1") 62); AssignSignal (Wire 61) (IndexConst (NamedVector Bit 100 "v1") 61); AssignSignal (Wire 60) (IndexConst (NamedVector Bit 100 "v1") 60); AssignSignal (Wire 59) (IndexConst (NamedVector Bit 100 "v1") 59); AssignSignal (Wire 58) (IndexConst (NamedVector Bit 100 "v1") 58); AssignSignal (Wire 57) (IndexConst (NamedVector Bit 100 "v1") 57); AssignSignal (Wire 56) (IndexConst (NamedVector Bit 100 "v1") 56); AssignSignal (Wire 55) (IndexConst (NamedVector Bit 100 "v1") 55); AssignSignal (Wire 54) (IndexConst (NamedVector Bit 100 "v1") 54); AssignSignal (Wire 53) (IndexConst (NamedVector Bit 100 "v1") 53); AssignSignal (Wire 52) (IndexConst (NamedVector Bit 100 "v1") 52); AssignSignal (Wire 51) (IndexConst (NamedVector Bit 100 "v1") 51); AssignSignal (Wire 50) (IndexConst (NamedVector Bit 100 "v1") 50); AssignSignal (Wire 49) (IndexConst (NamedVector Bit 100 "v1") 49); AssignSignal (Wire 48) (IndexConst (NamedVector Bit 100 "v1") 48); AssignSignal (Wire 47) (IndexConst (NamedVector Bit 100 "v1") 47); AssignSignal (Wire 46) (IndexConst (NamedVector Bit 100 "v1") 46); AssignSignal (Wire 45) (IndexConst (NamedVector Bit 100 "v1") 45); AssignSignal (Wire 44) (IndexConst (NamedVector Bit 100 "v1") 44); AssignSignal (Wire 43) (IndexConst (NamedVector Bit 100 "v1") 43); AssignSignal (Wire 42) (IndexConst (NamedVector Bit 100 "v1") 42); AssignSignal (Wire 41) (IndexConst (NamedVector Bit 100 "v1") 41); AssignSignal (Wire 40) (IndexConst (NamedVector Bit 100 "v1") 40); AssignSignal (Wire 39) (IndexConst (NamedVector Bit 100 "v1") 39); AssignSignal (Wire 38) (IndexConst (NamedVector Bit 100 "v1") 38); AssignSignal (Wire 37) (IndexConst (NamedVector Bit 100 "v1") 37); AssignSignal (Wire 36) (IndexConst (NamedVector Bit 100 "v1") 36); AssignSignal (Wire 35) (IndexConst (NamedVector Bit 100 "v1") 35); AssignSignal (Wire 34) (IndexConst (NamedVector Bit 100 "v1") 34); AssignSignal (Wire 33) (IndexConst (NamedVector Bit 100 "v1") 33); AssignSignal (Wire 32) (IndexConst (NamedVector Bit 100 "v1") 32); AssignSignal (Wire 31) (IndexConst (NamedVector Bit 100 "v1") 31); AssignSignal (Wire 30) (IndexConst (NamedVector Bit 100 "v1") 30); AssignSignal (Wire 29) (IndexConst (NamedVector Bit 100 "v1") 29); AssignSignal (Wire 28) (IndexConst (NamedVector Bit 100 "v1") 28); AssignSignal (Wire 27) (IndexConst (NamedVector Bit 100 "v1") 27); AssignSignal (Wire 26) (IndexConst (NamedVector Bit 100 "v1") 26); AssignSignal (Wire 25) (IndexConst (NamedVector Bit 100 "v1") 25); AssignSignal (Wire 24) (IndexConst (NamedVector Bit 100 "v1") 24); AssignSignal (Wire 23) (IndexConst (NamedVector Bit 100 "v1") 23); AssignSignal (Wire 22) (IndexConst (NamedVector Bit 100 "v1") 22); AssignSignal (Wire 21) (IndexConst (NamedVector Bit 100 "v1") 21); AssignSignal (Wire 20) (IndexConst (NamedVector Bit 100 "v1") 20); AssignSignal (Wire 19) (IndexConst (NamedVector Bit 100 "v1") 19); AssignSignal (Wire 18) (IndexConst (NamedVector Bit 100 "v1") 18); AssignSignal (Wire 17) (IndexConst (NamedVector Bit 100 "v1") 17); AssignSignal (Wire 16) (IndexConst (NamedVector Bit 100 "v1") 16); AssignSignal (Wire 15) (IndexConst (NamedVector Bit 100 "v1") 15); AssignSignal (Wire 14) (IndexConst (NamedVector Bit 100 "v1") 14); AssignSignal (Wire 13) (IndexConst (NamedVector Bit 100 "v1") 13); AssignSignal (Wire 12) (IndexConst (NamedVector Bit 100 "v1") 12); AssignSignal (Wire 11) (IndexConst (NamedVector Bit 100 "v1") 11); AssignSignal (Wire 10) (IndexConst (NamedVector Bit 100 "v1") 10); AssignSignal (Wire 9) (IndexConst (NamedVector Bit 100 "v1") 9); AssignSignal (Wire 8) (IndexConst (NamedVector Bit 100 "v1") 8); AssignSignal (Wire 7) (IndexConst (NamedVector Bit 100 "v1") 7); AssignSignal (Wire 6) (IndexConst (NamedVector Bit 100 "v1") 6); AssignSignal (Wire 5) (IndexConst (NamedVector Bit 100 "v1") 5); AssignSignal (Wire 4) (IndexConst (NamedVector Bit 100 "v1") 4); AssignSignal (Wire 3) (IndexConst (NamedVector Bit 100 "v1") 3); AssignSignal (Wire 2) (IndexConst (NamedVector Bit 100 "v1") 2); AssignSignal (Wire 1) (IndexConst (NamedVector Bit 100 "v1") 1); AssignSignal (Wire 0) (IndexConst (NamedVector Bit 100 "v1") 0)]; inputs := [{| port_name := "v2"; port_type := Vec Bit 100 |}; {| port_name := "v1"; port_type := Vec Bit 100 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 100 |}] |} : Module

Simulations are the same; just plug in any size.

(* 7 xor 10 = 13 (n=8) (same as xor_byte) *)
= [13%N]%list : list N
(* 1 xor 3 = 2 (n=2) *)
= [2%N]%list : list N
(* 1000 xor 3 = 1003 (n=10) *)
= [1003%N]%list : list N

The correctness proof has is exactly the same as the xor_byte proof, except with n instead of 8; the proof body is completely unchanged.

n:nat
i:list (Vector.t bool n * Vector.t bool n)

simulate (xor_bitvec n) i = map (fun '(v1, v2) => BVxor n v1 v2) i
n:nat
i:list (Vector.t bool n * Vector.t bool n)

simulate (xor_bitvec n) i = map (fun '(v1, v2) => BVxor n v1 v2) i
n:nat
i:list (Vector.t bool n * Vector.t bool n)

simulate (Comb (Vec.map2 xor2)) i = map (fun '(v1, v2) => BVxor n v1 v2) i
n:nat
i:list (Vector.t bool n * Vector.t bool n)

map (Vec.map2 xor2) i = map (fun '(v1, v2) => BVxor n v1 v2) i
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 a = (let '(v1, v2) := a in BVxor n v1 v2)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 (fst a, snd a) = BVxor n (fst a) (snd a)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 (fst a, snd a) = Vector.map2 xorb (fst a) (snd a)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vector.map2 (fun x y : combType Bit => xorb x y) (fst a) (snd a) = Vector.map2 xorb (fst a) (snd a)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

forall a b : combType Bit, xorb a b = xorb a b
reflexivity. Qed.

We can also easily prove that, for 8-bit vectors, xor_bitvec is equivalent to xor_byte:

i:list (Vector.t bool 8 * Vector.t bool 8)

simulate (xor_bitvec 8) i = simulate xor_byte i
i:list (Vector.t bool 8 * Vector.t bool 8)

simulate (xor_bitvec 8) i = simulate xor_byte i
reflexivity. Qed.

This example demonstrates the advantage of using a proof assistant instead of a more computational method. The xor_bitvec_correct proof checks essentially instantly and holds for all values of n. With one circuit definition, and one proof, you have defined every single length of bit-vector xor you'll ever need. The same principle can apply to more complicated structures as well.

Example 4: Tree of xors

To take the last circuit a step further, let's consider xoring not just two n-length vectors, but an arbitrary number m of n-length vectors!

We could write a definition that chains the xors together one by one:

xor (xor (xor (xor (xor a b) c) d) e f)

However, since there are no data dependencies, the circuit will have better timing properties for possibly large m if it is a tree, e.g.:

xor (xor (xor a b) c) (xor (xor d e) f)

Luckily, Cava's standard library contains a tree combinator for exactly this kind of situation.

  Definition xor_tree {n m : nat} :
    Circuit (signal (Vec (Vec Bit n) m))
            (signal (Vec Bit n)) :=
    Comb (tree (Vec.map2 xor2)).

Now, we can just plug in any sequence of same-size vectors and compute the results!

One note for those less familiar with Coq: the curly braces {} around the n and m arguments are standard Coq syntax for "implicit" arguments; Coq will try to guess their values rather than requiring them to be passed explicitly. So we can actually write xor_tree vec instead of e.g. xor_tree 2 3 vec, and Coq will try to infer n and m from the type of vec. If Coq struggles to infer them, we can also plug in these arguments manually by referencing their names, e.g. xor_tree (m:=3) vec.

(* 7 xor 10 = 13 (n=8, m=2)*)
= [13%N]%list : list N
(* 1000 xor 3 = 1003 (n=10, m=2) *)
= [1003%N]%list : list N
(* 1 xor 2 xor 4 xor 8 xor 16 xor 32 xor 64 xor 128 = 255 (n=8, m=8) *)
= [255%N]%list : list N

To prove the xor tree circuit correct, we prove that it's equivalent to a fold_left, which is a native Coq loop. Essentially, this proof says that the circuit, even with the tree structure, is equivalent to just chaining BVxor over the input in order (starting with 0, which is the identity for xor).

n, m:nat
i:list (Vector.t (Vector.t bool n) m)

m <> 0 -> simulate xor_tree i = map (fun vs : Vector.t (Vector.t bool n) m => Vector.fold_left (BVxor n) (N2Bv_sized n 0) vs) i
n, m:nat
i:list (Vector.t (Vector.t bool n) m)

m <> 0 -> simulate xor_tree i = map (fun vs : Vector.t (Vector.t bool n) m => Vector.fold_left (BVxor n) (N2Bv_sized n 0) vs) i
n, m:nat
i:list (Vector.t (Vector.t bool n) m)

m <> 0 -> simulate (Comb (tree (Vec.map2 xor2))) i = map (fun vs : Vector.t (Vector.t bool n) m => Vector.fold_left (BVxor n) (N2Bv_sized n 0) vs) i
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0

simulate (Comb (tree (Vec.map2 xor2))) i = map (fun vs : Vector.t (Vector.t bool n) m => Vector.fold_left (BVxor n) (N2Bv_sized n 0) vs) i
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0

map (tree (Vec.map2 xor2)) i = map (fun vs : Vector.t (Vector.t bool n) m => Vector.fold_left (BVxor n) (N2Bv_sized n 0) vs) i
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m

tree (Vec.map2 xor2) a = Vector.fold_left (BVxor n) (N2Bv_sized n 0) a
(* this rewrite produces side conditions; we'll handle them later *)
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m

Vector.fold_left (fun a b : combType (Vec Bit n) => Vec.map2 (fun '(x, y) => xorb x y) (a, b)) (N2Bv_sized n 0) a = Vector.fold_left (BVxor n) (N2Bv_sized n 0) a
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) a0 = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) a0 (N2Bv_sized n 0) = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m

Vector.fold_left (fun a b : combType (Vec Bit n) => Vec.map2 (fun '(x, y) => xorb x y) (a, b)) (N2Bv_sized n 0) a = Vector.fold_left (BVxor n) (N2Bv_sized n 0) a
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m

Vector.fold_left (fun a b : combType (Vec Bit n) => Vec.map2 (fun '(x, y) => xorb x y) (a, b)) (N2Bv_sized n 0) a = Vector.fold_left (Vector.map2 xorb) (N2Bv_sized n 0) a
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
b:Vector.t bool n
a0:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) b a0 = Vector.map2 xorb b a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
b:Vector.t bool n
a0:combType (Vec Bit n)

forall a b : combType Bit, xorb a b = xorb a b
reflexivity.
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) a0 = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) a0 (N2Bv_sized n 0) = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c
(* now, solve the tree_equiv side conditions *)
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) a0 = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) a0) = Bv2N a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

N.lxor 0 (Bv2N a0) = Bv2N a0
apply N.lxor_0_l.
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) a0 (N2Bv_sized n 0) = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)
Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) a0 (N2Bv_sized n 0) = a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) a0 (N2Bv_sized n 0)) = Bv2N a0
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0:combType (Vec Bit n)

N.lxor (Bv2N a0) 0 = Bv2N a0
apply N.lxor_0_r.
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) a0 (Vector.map2 (fun x y : combType Bit => xorb x y) b c)) = Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) a0 b) c)
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

N.lxor (Bv2N a0) (N.lxor (Bv2N b) (Bv2N c)) = N.lxor (N.lxor (Bv2N a0) (Bv2N b)) (Bv2N c)
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

N.lxor (N.lxor (Bv2N a0) (Bv2N b)) (Bv2N c) = N.lxor (Bv2N a0) (N.lxor (Bv2N b) (Bv2N c))
n, m:nat
i:list (Vector.t (Vector.t bool n) m)
H:m <> 0
a:Vector.t (Vector.t bool n) m
a0, b, c:combType (Vec Bit n)

N.lxor (N.lxor (Bv2N a0) (Bv2N b)) (Bv2N c) = N.lxor (Bv2N a0) (N.lxor (Bv2N b) (Bv2N c))
apply N.lxor_assoc. } Qed.

It's worth taking a moment here again to point out just how broad the proof of correctness is. This proof applies to a circuit that xors two bits, and also applies to a circuit that xors 1000 1000-bit bitvectors.

As a final touch, we can also prove that, when applied to just two bitvectors (m = 2), xor_tree is equivalent to xor_bitvec:

n:nat
i:list (Vector.t bool n * Vector.t bool n)

simulate (xor_bitvec n) i = simulate xor_tree (map (fun '(v1, v2) => [v1; v2]) i)
n:nat
i:list (Vector.t bool n * Vector.t bool n)

simulate (xor_bitvec n) i = simulate xor_tree (map (fun '(v1, v2) => [v1; v2]) i)
n:nat
i:list (Vector.t bool n * Vector.t bool n)

map (Vec.map2 xor2) i = map (tree (Vec.map2 xor2)) (map (fun '(v1, v2) => [v1; v2]) i)
n:nat
i:list (Vector.t bool n * Vector.t bool n)

map (Vec.map2 xor2) i = map (fun x : Vector.t bool n * Vector.t bool n => tree (Vec.map2 xor2) (let '(v1, v2) := x in [v1; v2])) i
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 a = tree (Vec.map2 xor2) (let '(v1, v2) := a in [v1; v2])
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 (fst a, snd a) = tree (Vec.map2 xor2) [fst a; snd a]
(* The tree lemma produces the same side conditions as before, but we solve them here in a more concise way *)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 (fst a, snd a) = Vector.fold_left (fun a b : combType (Vec Bit n) => Vec.map2 xor2 (a, b)) (N2Bv_sized n 0) [fst a; snd a]
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vec.map2 xor2 (fst a, snd a) = Vec.map2 xor2 (Vec.map2 xor2 (N2Bv_sized n 0, fst a), snd a)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Vector.map2 (fun x y : combType Bit => xorb x y) (fst a) (snd a) = Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) (fst a)) (snd a)
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) (fst a) (snd a)) = Bv2N (Vector.map2 (fun x y : combType Bit => xorb x y) (Vector.map2 (fun x y : combType Bit => xorb x y) (N2Bv_sized n 0) (fst a)) (snd a))
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

N.lxor (Bv2N (fst a)) (Bv2N (snd a)) = N.lxor (N.lxor 0 (Bv2N (fst a))) (Bv2N (snd a))
n:nat
i:list (Vector.t bool n * Vector.t bool n)
a:(Vector.t bool n * Vector.t bool n)%type

N.lxor (Bv2N (fst a)) (Bv2N (snd a)) = N.lxor (Bv2N (fst a)) (Bv2N (snd a))
reflexivity. Qed.

At this point, we've covered pretty much everything you need to start building combinational circuits in Cava -- circuits that don't have any timing-dependent elements like loops or registers. In the next example, we'll show how to build sequential circuits.

Example 5 : Delay for Three Timesteps

The simplest sequential element is a unit delay (register). The delay takes in a value at the end of the clock cycle, and then outputs the same value at the start of the next clock cycle. So if we want to write a circuit that delays the input stream by three timesteps, we can write three delays in a row:

  Definition three_delays {t : SignalType}
    : Circuit (signal t) (signal t) :=
    Delay >==> Delay >==> Delay.

Note that this circuit definition will delay a signal of any type. The t argument can be anything, although to generate a concrete netlist or simulation it will need to be plugged in. We'll do simulations and netlist generations with a few different types.

The >==> notation means "compose these circuits", i.e connect the output ports of the left-hand circuit to the input ports of the second. It's short for Compose, which can also be used directly.

  
Notation "x >==> y" := (Compose x y) (default interpretation)
(* Exactly the same thing as three_delays, just without notation *) Definition three_delays_verbose {t : SignalType} : Circuit (signal t) (signal t) := Compose (Compose Delay Delay) Delay.

Compose and Delay are like Comb; they are definitions that create Circuits. You can find a full list of Circuit constructors in the reference.

Here's the netlist for three_delays, generated for two different signal types:

Definition three_delays_interface {t : SignalType}
  := sequentialInterface "three_delays_interface"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "i" t]
     [mkPort "o" t].

(* delay a stream of bits *)
= {| moduleName := "three_delays_interface"; netlist := [AssignSignal (NamedWire "o") (Wire 2); DelayEnable Bit false Vcc (Wire 1) (Wire 2); DelayEnable Bit false Vcc (Wire 0) (Wire 1); DelayEnable Bit false Vcc (NamedWire "i") (Wire 0)]; inputs := [{| port_name := "i"; port_type := Bit |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Bit |}] |} : Module
(* delay a stream of bytes *)
= {| moduleName := "three_delays_interface"; netlist := [AssignSignal (NamedVector Bit 8 "o") (LocalVec Bit 8 2); DelayEnable (Vec Bit 8) [false; false; false; false; false; false; false; false]%vector Vcc (LocalVec Bit 8 1) (LocalVec Bit 8 2); DelayEnable (Vec Bit 8) [false; false; false; false; false; false; false; false]%vector Vcc (LocalVec Bit 8 0) (LocalVec Bit 8 1); DelayEnable (Vec Bit 8) [false; false; false; false; false; false; false; false]%vector Vcc (NamedVector Bit 8 "i") (LocalVec Bit 8 0)]; inputs := [{| port_name := "i"; port_type := Vec Bit 8 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 8 |}] |} : Module

Let's simulate the circuit, first using a sequence of 1s:

= [false; false; false; true; true; true; true; true; true; true] : list (combType Bit)

You can see that we get three false outputs before getting the stream of true values. The initial state of Delay is always "zeroed out"; for a custom initial state, you can use the alternative constructor DelayInit, which takes an initial value.

We can also simulate the circuit with bytes. To make the simulations a little more interesting, we'll use a small convenience definition that creates a list of bytes counting up in sequence.

(* convenience definition for a sequence of numbers as bytes *)
Definition byte_seq start len : list (combType (Vec Bit 8)) :=
  map (nat_to_bitvec_sized 8) (seq start len).

= [1%N; 2%N; 3%N; 4%N; 5%N; 6%N; 7%N; 8%N; 9%N; 10%N] : list N

Now, when we run the simulations, it's easier to follow the timesteps:

= [0%N; 0%N; 0%N; 1%N; 2%N; 3%N; 4%N; 5%N; 6%N; 7%N] : list N

We can also compose three_delays with itself to get six delays:

= [0%N; 0%N; 0%N; 0%N; 0%N; 0%N; 1%N; 2%N; 3%N; 4%N] : list N

Finally, the correctness proof for three_delays says that it prepends three defaultSignal values (the generic name for "a zeroed-out value of the correct signal type") to the input, then truncates the new list to the length of the original input.

t:SignalType
input:list (combType t)

simulate three_delays input = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

simulate three_delays input = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

firstn (Datatypes.length (firstn (Datatypes.length (firstn (Datatypes.length input) (defaultSignal :: input))) (defaultSignal :: firstn (Datatypes.length input) (defaultSignal :: input)))) (defaultSignal :: firstn (Datatypes.length (firstn (Datatypes.length input) (defaultSignal :: input))) (defaultSignal :: firstn (Datatypes.length input) (defaultSignal :: input))) = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

firstn (Datatypes.length input) (defaultSignal :: firstn (Datatypes.length input) (defaultSignal :: firstn (Datatypes.length input) (defaultSignal :: input))) = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

firstn (Datatypes.length input) (firstn (S (Datatypes.length input)) (firstn (S (S (Datatypes.length input))) (defaultSignal :: defaultSignal :: defaultSignal :: input))) = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

firstn (Init.Nat.min (Init.Nat.min (Datatypes.length input) (S (Datatypes.length input))) (S (S (Datatypes.length input)))) (defaultSignal :: defaultSignal :: defaultSignal :: input) = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
t:SignalType
input:list (combType t)

firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input) = firstn (Datatypes.length input) (defaultSignal :: defaultSignal :: defaultSignal :: input)
reflexivity. Qed.

Example 6 : Sum the Input Stream

This example will introduce Loop, a circuit constructor that connects the output port of a circuit to its own input port with a delay in the middle. This creates internal state values, which can be referenced from inside the loop but are not visible outside it. Visually, a loop looks like this:

Circuit diagram showing a loop.

The following circuit gets a stream of bit-vectors as input, and uses Loop to provides the rolling sum as output:

  Definition sum {n : nat}
    : Circuit (signal (Vec Bit n)) (signal (Vec Bit n)) :=
    Loop
      (* The combinational circuit that makes up the loop body *)
      (Comb
         (fun '(input, state) =>
            sum <- addN (input, state) ;;
            (* return output and new state (the same in our case) *)
            ret (sum, sum))).

The body of this loop is a combinational circuit whose input is the loop input signal and the internal state, and whose output is the loop output signal and the new state.

As discussed in the very first example, the _ <- _ ;; _ notation is a monadic bind; it's like a let binder or variable assignment, except that it helps Cava track resource sharing. ret means "return". You can read in much more detail about monad notations in the reference.

For the purposes of the tutorial, we'll introduce just one more monad notation: monad composition, represented by >=>. Assuming f and g are monadic functions, writing f >=> g is the same as writing fun x => y <- f x ;; g y. This is very similar to the notation for Compose (>==>) shown earlier, except that it works for the bodies of combinational circuits rather than for sequential circuits.

Using >=>, we can rewrite sum as:

  (* Means exactly the same thing as sum *)
  Definition sum_concise {n : nat}
    : Circuit (signal (Vec Bit n)) (signal (Vec Bit n)) :=
    Loop (Comb (addN >=> fork2)).

The fork2 combinator simply duplicates its input (like a fork in a wire).

As written, the sum and sum_concise circuits will start with an initial state of zero (or defaultSignal). If we want to pull in a specific initial value, we can use LoopInit instead and plug in a compile-time constant:

  Definition sum_init {n : nat} (init : combType (Vec Bit n)) :=
    LoopInit init (Comb (addN >=> fork2)).

Here's the netlist for sum. You can see that no "loop" appears in the final version, just a delay connecting the loop's output to its own input.

Definition sum_interface {n : nat}
  := sequentialInterface "sum8"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "i" (Vec Bit n)]
     [mkPort "o" (Vec Bit n)].

= {| moduleName := "sum8"; netlist := [AssignSignal (NamedVector Bit 8 "o") (LocalVec Bit 8 1); DelayEnable (Vec Bit 8) [false; false; false; false; false; false; false; false]%vector Vcc (LocalVec Bit 8 1) (LocalVec Bit 8 0); AssignSignal (LocalVec Bit 8 1) (VecLit [Wire 18; Wire 23; Wire 28; Wire 33; Wire 38; Wire 43; Wire 48; Wire 53]); Or (Wire 52) (Wire 54) (Wire 55); And (Wire 51) (Wire 50) (Wire 54); Xor (Wire 51) (Wire 50) (Wire 53); And (Wire 7) (Wire 15) (Wire 52); Xor (Wire 7) (Wire 15) (Wire 51); Or (Wire 47) (Wire 49) (Wire 50); And (Wire 46) (Wire 45) (Wire 49); Xor (Wire 46) (Wire 45) (Wire 48); And (Wire 6) (Wire 14) (Wire 47); Xor (Wire 6) (Wire 14) (Wire 46); Or (Wire 42) (Wire 44) (Wire 45); And (Wire 41) (Wire 40) (Wire 44); Xor (Wire 41) (Wire 40) (Wire 43); And (Wire 5) (Wire 13) (Wire 42); Xor (Wire 5) (Wire 13) (Wire 41); Or (Wire 37) (Wire 39) (Wire 40); And (Wire 36) (Wire 35) (Wire 39); Xor (Wire 36) (Wire 35) (Wire 38); And (Wire 4) (Wire 12) (Wire 37); Xor (Wire 4) (Wire 12) (Wire 36); Or (Wire 32) (Wire 34) (Wire 35); And (Wire 31) (Wire 30) (Wire 34); Xor (Wire 31) (Wire 30) (Wire 33); And (Wire 3) (Wire 11) (Wire 32); Xor (Wire 3) (Wire 11) (Wire 31); Or (Wire 27) (Wire 29) (Wire 30); And (Wire 26) (Wire 25) (Wire 29); Xor (Wire 26) (Wire 25) (Wire 28); And (Wire 2) (Wire 10) (Wire 27); Xor (Wire 2) (Wire 10) (Wire 26); Or (Wire 22) (Wire 24) (Wire 25); And (Wire 21) (Wire 20) (Wire 24); Xor (Wire 21) (Wire 20) (Wire 23); And (Wire 1) (Wire 9) (Wire 22); Xor (Wire 1) (Wire 9) (Wire 21); Or (Wire 17) (Wire 19) (Wire 20); And (Wire 16) Gnd (Wire 19); Xor (Wire 16) Gnd (Wire 18); And (Wire 0) (Wire 8) (Wire 17); Xor (Wire 0) (Wire 8) (Wire 16); AssignSignal (Wire 15) (IndexConst (LocalVec Bit 8 0) 7); AssignSignal (Wire 14) (IndexConst (LocalVec Bit 8 0) 6); AssignSignal (Wire 13) (IndexConst (LocalVec Bit 8 0) 5); AssignSignal (Wire 12) (IndexConst (LocalVec Bit 8 0) 4); AssignSignal (Wire 11) (IndexConst (LocalVec Bit 8 0) 3); AssignSignal (Wire 10) (IndexConst (LocalVec Bit 8 0) 2); AssignSignal (Wire 9) (IndexConst (LocalVec Bit 8 0) 1); AssignSignal (Wire 8) (IndexConst (LocalVec Bit 8 0) 0); AssignSignal (Wire 7) (IndexConst (NamedVector Bit 8 "i") 7); AssignSignal (Wire 6) (IndexConst (NamedVector Bit 8 "i") 6); AssignSignal (Wire 5) (IndexConst (NamedVector Bit 8 "i") 5); AssignSignal (Wire 4) (IndexConst (NamedVector Bit 8 "i") 4); AssignSignal (Wire 3) (IndexConst (NamedVector Bit 8 "i") 3); AssignSignal (Wire 2) (IndexConst (NamedVector Bit 8 "i") 2); AssignSignal (Wire 1) (IndexConst (NamedVector Bit 8 "i") 1); AssignSignal (Wire 0) (IndexConst (NamedVector Bit 8 "i") 0)]; inputs := [{| port_name := "i"; port_type := Vec Bit 8 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 8 |}] |} : Module

Since this circuit has more complex timing behavior than previous examples, it's a good model for introducing a new technique: validating the SystemVerilog output by creating test cases based on the Coq semantics. This serves as an extra layer of assurance that nothing went wrong in the unverified translation from our netlist layer to SystemVerilog, and that our semantics are correct.

First, we create a definition for the netlist, as well as the inputs and expected outputs for our test. We generate the expected outputs using simulate, to see what the Coq semantics expect the output to be. Then we can use testBench to automatically create an extractable test.

Definition sum8Netlist := makeCircuitNetlist (sum_interface (n:=8)) sum.
Definition sum8_tb_inputs := map (N2Bv_sized 8) [3; 5; 7; 2; 4; 6].
Definition sum8_tb_expected_outputs := simulate sum sum8_tb_inputs.

(* print out the expected outputs according to the Coq semantics *)
= [3; 8; 15; 17; 21; 27] : list N
Definition sum8_tb := testBench "sum8_tb" (sum_interface (n := 8)) sum8_tb_inputs sum8_tb_expected_outputs.

The circuit netlist and testbench can then be converted in SystemVerilog and simulated using a SystemVerilog simulator like Verilator:

clang++ -L/usr/local/opt/sqlite/lib    sum8_tb.o verilated.o verilated_vcd_c.o Vsum8_tb__ALL.a    -o Vsum8_tb -lm -lstdc++ obj_dir/Vsum8_tb
                10: tick = 0,  i = 3,  o = 3
                20: tick = 1,  i = 5,  o = 8
                30: tick = 2,  i = 7,  o = 15
                40: tick = 3,  i = 2,  o = 17
                50: tick = 4,  i = 4,  o = 21
                60: tick = 5,  i = 6,  o = 27

which produces the expected results that were predicted by the model in Coq. The testbench generates a VCD waveform that we can use to observe graphically using a VCD waveform viewer like gtkwave:

Simulation waveform for the sum8 circuit.

We can also synthesize a version of this testbench and the sum8 circuit into gates using the Xilinx Vivado FPGA tools to produce a bitstream that can be usd to program an FPGA chip. We can hook up this circuit with another circuit that acts as a logic analyzer (ILA) then then run and observe this actually running on an FPGA and capture its output:

Logic analyzer trace capture for the sum8 circuit.

Reassuringly, the actual circuit behaves as predicted by the Cava model in Coq and the SystemVerilog simulation.

The netlist for sum_init can use the same interface as sum, but needs an extra argument for the initial value:

= {| moduleName := "sum8"; netlist := [AssignSignal (NamedVector Bit 8 "o") (LocalVec Bit 8 1); DelayEnable (Vec Bit 8) [false; true; false; true; false; false; false; false]%vector Vcc (LocalVec Bit 8 1) (LocalVec Bit 8 0); AssignSignal (LocalVec Bit 8 1) (VecLit [Wire 18; Wire 23; Wire 28; Wire 33; Wire 38; Wire 43; Wire 48; Wire 53]); Or (Wire 52) (Wire 54) (Wire 55); And (Wire 51) (Wire 50) (Wire 54); Xor (Wire 51) (Wire 50) (Wire 53); And (Wire 7) (Wire 15) (Wire 52); Xor (Wire 7) (Wire 15) (Wire 51); Or (Wire 47) (Wire 49) (Wire 50); And (Wire 46) (Wire 45) (Wire 49); Xor (Wire 46) (Wire 45) (Wire 48); And (Wire 6) (Wire 14) (Wire 47); Xor (Wire 6) (Wire 14) (Wire 46); Or (Wire 42) (Wire 44) (Wire 45); And (Wire 41) (Wire 40) (Wire 44); Xor (Wire 41) (Wire 40) (Wire 43); And (Wire 5) (Wire 13) (Wire 42); Xor (Wire 5) (Wire 13) (Wire 41); Or (Wire 37) (Wire 39) (Wire 40); And (Wire 36) (Wire 35) (Wire 39); Xor (Wire 36) (Wire 35) (Wire 38); And (Wire 4) (Wire 12) (Wire 37); Xor (Wire 4) (Wire 12) (Wire 36); Or (Wire 32) (Wire 34) (Wire 35); And (Wire 31) (Wire 30) (Wire 34); Xor (Wire 31) (Wire 30) (Wire 33); And (Wire 3) (Wire 11) (Wire 32); Xor (Wire 3) (Wire 11) (Wire 31); Or (Wire 27) (Wire 29) (Wire 30); And (Wire 26) (Wire 25) (Wire 29); Xor (Wire 26) (Wire 25) (Wire 28); And (Wire 2) (Wire 10) (Wire 27); Xor (Wire 2) (Wire 10) (Wire 26); Or (Wire 22) (Wire 24) (Wire 25); And (Wire 21) (Wire 20) (Wire 24); Xor (Wire 21) (Wire 20) (Wire 23); And (Wire 1) (Wire 9) (Wire 22); Xor (Wire 1) (Wire 9) (Wire 21); Or (Wire 17) (Wire 19) (Wire 20); And (Wire 16) Gnd (Wire 19); Xor (Wire 16) Gnd (Wire 18); And (Wire 0) (Wire 8) (Wire 17); Xor (Wire 0) (Wire 8) (Wire 16); AssignSignal (Wire 15) (IndexConst (LocalVec Bit 8 0) 7); AssignSignal (Wire 14) (IndexConst (LocalVec Bit 8 0) 6); AssignSignal (Wire 13) (IndexConst (LocalVec Bit 8 0) 5); AssignSignal (Wire 12) (IndexConst (LocalVec Bit 8 0) 4); AssignSignal (Wire 11) (IndexConst (LocalVec Bit 8 0) 3); AssignSignal (Wire 10) (IndexConst (LocalVec Bit 8 0) 2); AssignSignal (Wire 9) (IndexConst (LocalVec Bit 8 0) 1); AssignSignal (Wire 8) (IndexConst (LocalVec Bit 8 0) 0); AssignSignal (Wire 7) (IndexConst (NamedVector Bit 8 "i") 7); AssignSignal (Wire 6) (IndexConst (NamedVector Bit 8 "i") 6); AssignSignal (Wire 5) (IndexConst (NamedVector Bit 8 "i") 5); AssignSignal (Wire 4) (IndexConst (NamedVector Bit 8 "i") 4); AssignSignal (Wire 3) (IndexConst (NamedVector Bit 8 "i") 3); AssignSignal (Wire 2) (IndexConst (NamedVector Bit 8 "i") 2); AssignSignal (Wire 1) (IndexConst (NamedVector Bit 8 "i") 1); AssignSignal (Wire 0) (IndexConst (NamedVector Bit 8 "i") 0)]; inputs := [{| port_name := "i"; port_type := Vec Bit 8 |}; {| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 8 |}] |} : Module

Let's run a few simulations to see the circuit in action:

(* sum of 10 1s = 1,2,3,...10 *)
= [1%N; 2%N; 3%N; 4%N; 5%N; 6%N; 7%N; 8%N; 9%N; 10%N] : list N
(* sum of 1..10 = 1, 3, 6, 10, 15, 21, 28, 36, 45, 55 *)
= [1%N; 3%N; 6%N; 10%N; 15%N; 21%N; 28%N; 36%N; 45%N; 55%N] : list N
(* sum of 10 1s starting at 10 = 11,12,13,...20 *)
= [11%N; 12%N; 13%N; 14%N; 15%N; 16%N; 17%N; 18%N; 19%N; 20%N] : list N

To write a correctness proof for sum, we first need to describe its behavior. There are many ways to do this, but one way is shown below.

(* computes the sum of a list of numbers (as a single number, not the
   rolling sum) *)
Definition sum_list_N (input : list N) : N :=
  fold_left N.add input 0%N.

(* computes the *rolling* sum; the nth element of the output represents
   the sum of the input up to index n *)
Definition rolling_sum (input : list N) : list N :=
  map (fun i => sum_list_N (firstn (S i) input)) (seq 0 (length input)).

(* example to show the behavior of rolling_sum *)
= [5%N; 11%N; 18%N] : list N
(* specification for the sum circuit : convert to N, get rolling_sum, convert back to bit-vectors *) Definition spec_of_sum {n} (input : list (combType (Vec Bit n))) : list (combType (Vec Bit n)) := map (N2Bv_sized n) (rolling_sum (map Bv2N input)).

To reason about loops, it's often helpful to use loop invariants. The invariant of a circuit loop takes three arguments: the timestep (a nat), the current circuit state (any state information needed for the loop body, plus the value held by the delay at this timestep), and the output accumulator (a list of the outputs generated so far). Because the sum circuit has a purely combinational body, it has no internal state, so the body state in our case is just Coq's unit type. Here's the invariant statement:

Definition sum_invariant {n} (input : list (combType (Vec Bit n)))
           (t : nat)
           (loop_state : unit * combType (Vec Bit n))
           (output_accumulator : list (combType (Vec Bit n))) : Prop :=
  (* at timestep t... *)
  (* ...the loop state holds the sum of the inputs so far (that is,
     the first t inputs) *)
  loop_state = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))
  (* ... and the output accumulator matches the rolling-sum spec
     applied to the inputs so far *)
  /\ output_accumulator = spec_of_sum (firstn t input).

Now, we can use the invariant to prove a correctness lemma. This proof could certainly be a little more elegant and automated, but the steps are left explicit here for those who are curious to follow the reasoning in detail.

(* This lemma is helpful for sum_correct *)
n:nat
l:list N
v:Vector.t bool n

N2Bv_sized n (sum_list_N (l ++ [Bv2N v])) = N2Bv_sized n (Bv2N v + Bv2N (N2Bv_sized n (sum_list_N l)))
n:nat
l:list N
v:Vector.t bool n

N2Bv_sized n (sum_list_N (l ++ [Bv2N v])) = N2Bv_sized n (Bv2N v + Bv2N (N2Bv_sized n (sum_list_N l)))
n:nat
l:list N
v:Vector.t bool n

N2Bv_sized n (fold_left N.add (l ++ [Bv2N v]) 0%N) = N2Bv_sized n (Bv2N v + Bv2N (N2Bv_sized n (fold_left N.add l 0%N)))
n:nat
l:list N
v:Vector.t bool n

N2Bv_sized n (fold_left N.add l 0%N + Bv2N v) = N2Bv_sized n (Bv2N v + Bv2N (N2Bv_sized n (fold_left N.add l 0%N)))
(* use Bv2N to bring the goal into the N realm, where it's easier to solve using modular arithmetic rules *)
n:nat
l:list N
v:Vector.t bool n

((fold_left N.add l 0 + Bv2N v) mod 2 ^ N.of_nat n)%N = ((Bv2N v + fold_left N.add l 0 mod 2 ^ N.of_nat n) mod 2 ^ N.of_nat n)%N
n:nat
l:list N
v:Vector.t bool n

((fold_left N.add l 0 + Bv2N v) mod 2 ^ N.of_nat n)%N = ((Bv2N v + fold_left N.add l 0) mod 2 ^ N.of_nat n)%N
n:nat
l:list N
v:Vector.t bool n

((Bv2N v + fold_left N.add l 0) mod 2 ^ N.of_nat n)%N = ((Bv2N v + fold_left N.add l 0) mod 2 ^ N.of_nat n)%N
reflexivity. Qed. (* Correctness lemma for sum *)
n:nat
input:list (combType (Vec Bit n))

simulate sum input = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

simulate sum input = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

simulate (Loop (Comb (fun '(input, state) => sum <- addN (input, state);; ret (sum, sum)))) input = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

fold_left_accumulate (fun '(cs, st) (i : combType (Vec Bit n)) => let '(cs', (o, st')) := step (Comb (fun '(input, state) => sum <- addN (input, state);; ret (sum, sum))) cs (i, st) in (cs', st', o)) input (reset_state (Comb (fun '(input, state) => sum <- addN (input, state);; ret (sum, sum))), defaultCombValue (Vec Bit n)) = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

fold_left_accumulate (fun '(cs, st) (i : combType (Vec Bit n)) => let '(cs', (o, st')) := step (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))) cs (i, st) in (cs', st', o)) input (reset_state (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))), defaultCombValue (Vec Bit n)) = spec_of_sum input
(* apply loop invariant lemma using sum_invariant; generates three side conditions *)
n:nat
input:list (combType (Vec Bit n))

sum_invariant input 0 (reset_state (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))), defaultCombValue (Vec Bit n)) []
n:nat
input:list (combType (Vec Bit n))
forall (t : nat) (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))) (d : combType (Vec Bit n)), sum_invariant input t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType (Vec Bit n) => let '(cs', (o, st')) := step (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))) cs (i, st0) in (cs', st', o)) (nth t input d) in sum_invariant input (S t) (fst out) (acc ++ [snd out])
n:nat
input:list (combType (Vec Bit n))
forall (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))), sum_invariant input (Datatypes.length input) st acc -> acc = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

sum_invariant input 0 (reset_state (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))), defaultCombValue (Vec Bit n)) []
n:nat
input:list (combType (Vec Bit n))

(reset_state (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))), defaultCombValue (Vec Bit n)) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn 0 input)))) /\ [] = spec_of_sum (firstn 0 input)
n:nat
input:list (combType (Vec Bit n))

(tt, Vector.const false n) = (tt, Bvect_false n) /\ [] = []
split; reflexivity.
n:nat
input:list (combType (Vec Bit n))

forall (t : nat) (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))) (d : combType (Vec Bit n)), sum_invariant input t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType (Vec Bit n) => let '(cs', (o, st')) := step (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))) cs (i, st0) in (cs', st', o)) (nth t input d) in sum_invariant input (S t) (fst out) (acc ++ [snd out])
n:nat
input:list (combType (Vec Bit n))
forall (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))), sum_invariant input (Datatypes.length input) st acc -> acc = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

forall (t : nat) (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))) (d : combType (Vec Bit n)), sum_invariant input t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType (Vec Bit n) => let '(cs', (o, st')) := step (Comb (fun '(input, state) => (addN (input, state), addN (input, state)))) cs (i, st0) in (cs', st', o)) (nth t input d) in sum_invariant input (S t) (fst out) (acc ++ [snd out])
(* prove that, if the invariant holds at the beginning of the loop body for timestep t, it holds at the end of the loop body for timestep t + 1 *)
n:nat
input:list (combType (Vec Bit n))

forall (t : nat) (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))) (d : combType (Vec Bit n)), st = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))) /\ acc = spec_of_sum (firstn t input) -> 0 <= t < Datatypes.length input -> fst ((let '(_, st0) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st0), addN (i, st0))) (nth t input d)) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input)))) /\ acc ++ [snd ((let '(_, st0) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st0), addN (i, st0))) (nth t input d))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
st:(unit * combType (Vec Bit n))%type
acc:list (combType (Vec Bit n))
d:combType (Vec Bit n)
H:st = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))) /\ acc = spec_of_sum (firstn t input)
H0:0 <= t < Datatypes.length input

fst ((let '(_, st) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st), addN (i, st))) (nth t input d)) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input)))) /\ acc ++ [snd ((let '(_, st) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st), addN (i, st))) (nth t input d))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
st:(unit * combType (Vec Bit n))%type
acc:list (combType (Vec Bit n))
d:combType (Vec Bit n)
H:st = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))) /\ acc = spec_of_sum (firstn t input)
H0:0 <= t < Datatypes.length input

fst ((let '(_, st) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st), addN (i, st))) (nth t input d)) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input)))) /\ acc ++ [snd ((let '(_, st) := st in fun i : combType (Vec Bit n) => (tt, addN (i, st), addN (i, st))) (nth t input d))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

fst (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input)))) /\ spec_of_sum (firstn t input) ++ [snd (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

fst (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input))))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input
spec_of_sum (firstn t input) ++ [snd (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

fst (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (S t) input))))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

fst (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input ++ [nth t input d]))))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

fst (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

(tt, N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))) = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

(tt, N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))) = (tt, N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input))))))
reflexivity.
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

spec_of_sum (firstn t input) ++ [snd (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

spec_of_sum (firstn t input) ++ [snd (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = spec_of_sum (firstn (S t) input)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (N2Bv_sized n) (map (fun i : nat => sum_list_N (firstn (S i) (map Bv2N (firstn t input)))) (seq 0 (Datatypes.length (map Bv2N (firstn t input))))) ++ [snd (tt, addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))), addN (nth t input d, N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (N2Bv_sized n) (map (fun i : nat => sum_list_N (firstn (S i) (map Bv2N (firstn (S t) input)))) (seq 0 (Datatypes.length (map Bv2N (firstn (S t) input)))))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (N2Bv_sized n) (map (fun i : nat => sum_list_N (firstn (S i) (map Bv2N (firstn t input)))) (seq 0 (Datatypes.length (map Bv2N (firstn t input))))) ++ [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (N2Bv_sized n) (map (fun i : nat => sum_list_N (firstn (S i) (map Bv2N (firstn (S t) input)))) (seq 0 (Datatypes.length (map Bv2N (firstn (S t) input)))))
(* simplify expression using list lemmas *)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 (Datatypes.length (map Bv2N (firstn t input)))) ++ [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn (S t) input))))) (seq 0 (Datatypes.length (map Bv2N (firstn (S t) input))))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 t) ++ [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn (S t) input))))) (seq 0 (S t))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 t) ++ [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input ++ [nth t input d]))))) (seq 0 (S t))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 t) ++ [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))) (seq 0 t) ++ [N2Bv_sized n (sum_list_N (firstn (S t) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 t) = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))) (seq 0 t)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input
[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (sum_list_N (firstn (S t) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input))))) (seq 0 t) = map (fun x : nat => N2Bv_sized n (sum_list_N (firstn (S x) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))) (seq 0 t)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input
a:nat

In a (seq 0 t) -> N2Bv_sized n (sum_list_N (firstn (S a) (map Bv2N (firstn t input)))) = N2Bv_sized n (sum_list_N (firstn (S a) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input
a:nat
H:0 <= a < 0 + t

N2Bv_sized n (sum_list_N (firstn (S a) (map Bv2N (firstn t input)))) = N2Bv_sized n (sum_list_N (firstn (S a) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))
(* list simplifications *)
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input
a:nat
H:0 <= a < 0 + t

N2Bv_sized n (sum_list_N (map Bv2N (firstn (Init.Nat.min (S a) t) input))) = N2Bv_sized n (sum_list_N (map Bv2N (firstn (Init.Nat.min (S a) t) input)))
reflexivity.
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (sum_list_N (firstn (S t) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (sum_list_N (firstn (S t) (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)])))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (sum_list_N (map Bv2N (firstn (Init.Nat.min (S t) t) input) ++ [Bv2N (nth t input d)]))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (sum_list_N (map Bv2N (firstn t input) ++ [Bv2N (nth t input d)]))]
n:nat
input:list (combType (Vec Bit n))
t:nat
d:combType (Vec Bit n)
H0:0 <= t
H1:t < Datatypes.length input

[N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))] = [N2Bv_sized n (Bv2N (nth t input d) + Bv2N (N2Bv_sized n (sum_list_N (map Bv2N (firstn t input)))))]
reflexivity. } }
n:nat
input:list (combType (Vec Bit n))

forall (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))), sum_invariant input (Datatypes.length input) st acc -> acc = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

forall (st : unit * combType (Vec Bit n)) (acc : list (combType (Vec Bit n))), sum_invariant input (Datatypes.length input) st acc -> acc = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))
st:(unit * combType (Vec Bit n))%type
acc:list (combType (Vec Bit n))
H:st = (tt, N2Bv_sized n (sum_list_N (map Bv2N (firstn (Datatypes.length input) input)))) /\ acc = spec_of_sum (firstn (Datatypes.length input) input)

acc = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

spec_of_sum (firstn (Datatypes.length input) input) = spec_of_sum input
n:nat
input:list (combType (Vec Bit n))

spec_of_sum input = spec_of_sum input
reflexivity. } Qed.

To wrap up our sum proofs, here's a quick demonstration that sum_concise is equivalent to sum:

n:nat
input:list (combType (Vec Bit n))

simulate sum_concise input = simulate sum input
n:nat
input:list (combType (Vec Bit n))

simulate sum_concise input = simulate sum input
reflexivity. Qed.

Example 7 : Fibonacci Sequence

In this example, we'll write a circuit that computes the Fibonacci sequence. Here's the circuit diagram:

Circuit diagram for the fibonacci circuit

In the diagram, r1 and r2 are registers. Because of the delay a register introduces, the addN in the middle of the circuit will, at timestep t, add together the output from timestep t-1 and the output from timestep t-2. In Cava, the circuit description looks like:

  Definition fibonacci {sz}
    : Circuit (signal Void) (signal (Vec Bit sz)) :=
    (* initial state of r1 = 1 *)
    let r1_init : combType (Vec Bit sz) := N2Bv_sized sz 1 in
    (* initial state of r2 = 2^sz-1 *)
    let r2_init : combType (Vec Bit sz) :=
        N2Bv_sized sz (2^N.of_nat sz - 1) in

    LoopInit r1_init
             ( (* start: (in, r1) *)
               Comb (dropl >=> fork2) >==> (* r1, r1 *)
                    Second (DelayInit r2_init) >==> (* r1, r2 *)
                    Comb (addN >=> fork2) (* r1 + r2, r1 + r2 *)).

Note the initial values. In order to get the correct output for the first two timesteps (0 and 1), we set r1 = 1 and r2 = 2^sz-1, where sz is the size of the bit vector. Since addN performs truncating bit-vector addition, the two initial values will sum to zero.

The circuit input is a Void signal, another SignalType. It's an empty type that's interpreted as a unit in Coq, and only serves to tell the circuit how many timesteps it should run for.

It's also possible to write the fibonacci circuit as two nested loops with a combinational body (essentially a mealy machine).

  Definition fibonacci_mealy {sz}
    : Circuit (signal Void) (signal (Vec Bit sz)) :=
    let v1 : combType (Vec Bit sz) := N2Bv_sized sz 1 in
    let v_negative1 : combType (Vec Bit sz) := Vector.const one sz in
    LoopInit v1
      (LoopInit v_negative1
                (Comb
                   (fun '(_,r1,r2) =>
                      sum <- addN (r1, r2) ;;
                      ret (sum, sum, r1)))).

As always, we can generate a netlist:

Definition fibonacci_interface {n : nat}
  := sequentialInterface "sum_interface"
     "clk" PositiveEdge "rst" PositiveEdge
     [mkPort "i" Void]
     [mkPort "o" (Vec Bit n)].

= {| moduleName := "sum_interface"; netlist := [AssignSignal (NamedVector Bit 4 "o") (LocalVec Bit 4 2); DelayEnable (Vec Bit 4) [true; false; false; false]%vector Vcc (LocalVec Bit 4 2) (LocalVec Bit 4 0); AssignSignal (LocalVec Bit 4 2) (VecLit [Wire 10; Wire 15; Wire 20; Wire 25]); Or (Wire 24) (Wire 26) (Wire 27); And (Wire 23) (Wire 22) (Wire 26); Xor (Wire 23) (Wire 22) (Wire 25); And (Wire 3) (Wire 7) (Wire 24); Xor (Wire 3) (Wire 7) (Wire 23); Or (Wire 19) (Wire 21) (Wire 22); And (Wire 18) (Wire 17) (Wire 21); Xor (Wire 18) (Wire 17) (Wire 20); And (Wire 2) (Wire 6) (Wire 19); Xor (Wire 2) (Wire 6) (Wire 18); Or (Wire 14) (Wire 16) (Wire 17); And (Wire 13) (Wire 12) (Wire 16); Xor (Wire 13) (Wire 12) (Wire 15); And (Wire 1) (Wire 5) (Wire 14); Xor (Wire 1) (Wire 5) (Wire 13); Or (Wire 9) (Wire 11) (Wire 12); And (Wire 8) Gnd (Wire 11); Xor (Wire 8) Gnd (Wire 10); And (Wire 0) (Wire 4) (Wire 9); Xor (Wire 0) (Wire 4) (Wire 8); AssignSignal (Wire 7) (IndexConst (LocalVec Bit 4 1) 3); AssignSignal (Wire 6) (IndexConst (LocalVec Bit 4 1) 2); AssignSignal (Wire 5) (IndexConst (LocalVec Bit 4 1) 1); AssignSignal (Wire 4) (IndexConst (LocalVec Bit 4 1) 0); AssignSignal (Wire 3) (IndexConst (LocalVec Bit 4 0) 3); AssignSignal (Wire 2) (IndexConst (LocalVec Bit 4 0) 2); AssignSignal (Wire 1) (IndexConst (LocalVec Bit 4 0) 1); AssignSignal (Wire 0) (IndexConst (LocalVec Bit 4 0) 0); DelayEnable (Vec Bit 4) [true; true; true; true]%vector Vcc (LocalVec Bit 4 0) (LocalVec Bit 4 1)]; inputs := [{| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 4 |}] |} : Module
= {| moduleName := "sum_interface"; netlist := [AssignSignal (NamedVector Bit 4 "o") (LocalVec Bit 4 2); DelayEnable (Vec Bit 4) [true; false; false; false]%vector Vcc (LocalVec Bit 4 2) (LocalVec Bit 4 0); DelayEnable (Vec Bit 4) [true; true; true; true]%vector Vcc (LocalVec Bit 4 0) (LocalVec Bit 4 1); AssignSignal (LocalVec Bit 4 2) (VecLit [Wire 10; Wire 15; Wire 20; Wire 25]); Or (Wire 24) (Wire 26) (Wire 27); And (Wire 23) (Wire 22) (Wire 26); Xor (Wire 23) (Wire 22) (Wire 25); And (Wire 3) (Wire 7) (Wire 24); Xor (Wire 3) (Wire 7) (Wire 23); Or (Wire 19) (Wire 21) (Wire 22); And (Wire 18) (Wire 17) (Wire 21); Xor (Wire 18) (Wire 17) (Wire 20); And (Wire 2) (Wire 6) (Wire 19); Xor (Wire 2) (Wire 6) (Wire 18); Or (Wire 14) (Wire 16) (Wire 17); And (Wire 13) (Wire 12) (Wire 16); Xor (Wire 13) (Wire 12) (Wire 15); And (Wire 1) (Wire 5) (Wire 14); Xor (Wire 1) (Wire 5) (Wire 13); Or (Wire 9) (Wire 11) (Wire 12); And (Wire 8) Gnd (Wire 11); Xor (Wire 8) Gnd (Wire 10); And (Wire 0) (Wire 4) (Wire 9); Xor (Wire 0) (Wire 4) (Wire 8); AssignSignal (Wire 7) (IndexConst (LocalVec Bit 4 1) 3); AssignSignal (Wire 6) (IndexConst (LocalVec Bit 4 1) 2); AssignSignal (Wire 5) (IndexConst (LocalVec Bit 4 1) 1); AssignSignal (Wire 4) (IndexConst (LocalVec Bit 4 1) 0); AssignSignal (Wire 3) (IndexConst (LocalVec Bit 4 0) 3); AssignSignal (Wire 2) (IndexConst (LocalVec Bit 4 0) 2); AssignSignal (Wire 1) (IndexConst (LocalVec Bit 4 0) 1); AssignSignal (Wire 0) (IndexConst (LocalVec Bit 4 0) 0)]; inputs := [{| port_name := "rst"; port_type := Bit |}; {| port_name := "clk"; port_type := Bit |}]; outputs := [{| port_name := "o"; port_type := Vec Bit 4 |}] |} : Module

We can run some simulations to make sure the circuit produces the expected outputs:

= [0%N; 1%N; 1%N; 2%N; 3%N; 5%N; 8%N; 13%N; 21%N; 34%N] : list N
= [0%N; 1%N; 1%N; 2%N; 3%N; 5%N; 8%N; 13%N; 21%N; 34%N] : list N

Let's now try to prove that the circuit is correct. As with sum, we first need to first describe the behavior we expect. Here's a natural-number function that computes the nth element of the Fibonacci sequence:

Fixpoint fibonacci_nat (n : nat) :=
  match n with
  | 0 => 0
  | S m =>
    let f_m := fibonacci_nat m in
    match m with
    | 0 => 1
    | S p => fibonacci_nat p + f_m
    end
  end.

So, the specification of our fibonacci circuit is that, given n of its empty inputs, the circuit produces (the bit-vector versions of) the first n elements of the Fibonacci sequence:

Definition spec_of_fibonacci {sz} (input : list unit)
  : list (combType (Vec Bit sz))
  := map (fun n => N2Bv_sized sz (N.of_nat (fibonacci_nat n)))
         (seq 0 (length input)).

We'll need a loop invariant, which just says that the output accumulator matches the spec and that the values in r1 and r2 are the right numbers from the Fibonacci sequence.

Definition fibonacci_invariant {sz}
           (t : nat)
           (loop_state :
              unit * (unit * combType (Vec Bit sz)) * unit
              * combType (Vec Bit sz))
           (output_accumulator : list (combType (Vec Bit sz))) : Prop :=
  let r1 := snd loop_state in
  let r2 := snd (snd (fst (fst loop_state))) in
  (* at timestep t... *)
  (* ...r1 holds fibonacci_nat (t-1), or 1 if t=0 *)
  r1 = match t with
       | 0 => N2Bv_sized sz 1
       | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1))
       end
  (* ... and r2 holds fibonacci_nat (t-2), or 1 if t=1, 2^sz-1 if t=0 *)
  /\ r2 = match t with
         | 0 => N2Bv_sized sz (2^N.of_nat sz - 1)
         | 1 => N2Bv_sized sz 1
         | S (S t_minus2) =>
           N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2))
         end
  (* ... and the output accumulator matches the circuit spec for the
     inputs so far *)
  /\ output_accumulator = spec_of_fibonacci (repeat tt t).

Note that, unlike for sum, there's actually a bit-vector in the loop body state from the DelayInit element.

The extra unit types are an unfortunate feature of the current setup and we're working on removing them. For now, just know that you can figure out what the loop body's type should be by computing its circuit_state, like this:

= (unit * (unit * Vector.t bool ?n) * unit)%type : Type

Here's the proof of correctness, with the help of a couple of small helper lemmas:

(* Helper lemma for fibonacci_correct *)
sz:nat

N2Bv_sized sz (1 + (2 ^ N.of_nat sz - 1)) = N2Bv_sized sz 0
sz:nat

N2Bv_sized sz (1 + (2 ^ N.of_nat sz - 1)) = N2Bv_sized sz 0
sz:nat

((1 + (2 ^ N.of_nat sz - 1)) mod 2 ^ N.of_nat sz)%N = (0 mod 2 ^ N.of_nat sz)%N
sz:nat
H:(2 ^ N.of_nat sz)%N <> 0%N

((1 + (2 ^ N.of_nat sz - 1)) mod 2 ^ N.of_nat sz)%N = (0 mod 2 ^ N.of_nat sz)%N
sz:nat
H:(2 ^ N.of_nat sz)%N <> 0%N

(2 ^ N.of_nat sz mod 2 ^ N.of_nat sz)%N = (0 mod 2 ^ N.of_nat sz)%N
sz:nat
H:(2 ^ N.of_nat sz)%N <> 0%N

0%N = 0%N
reflexivity. Qed. (* Helper lemma for fibonacci_correct *)
n:nat

fibonacci_nat (S (S n)) = fibonacci_nat (S n) + fibonacci_nat n
n:nat

fibonacci_nat (S (S n)) = fibonacci_nat (S n) + fibonacci_nat n
n:nat

fibonacci_nat n + match n with | 0 => 1 | S p => fibonacci_nat p + fibonacci_nat n end = match n with | 0 => 1 | S p => fibonacci_nat p + fibonacci_nat n end + fibonacci_nat n
lia. Qed. (* the nth element of the simulation output is the bit-vector version of (fibonacci_spec n) *)
sz:nat
input:list unit

simulate fibonacci input = spec_of_fibonacci input
sz:nat
input:list unit

simulate fibonacci input = spec_of_fibonacci input
sz:nat
input:list unit

simulate (LoopInit (N2Bv_sized sz 1) (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2))) input = spec_of_fibonacci input
sz:nat
input:list unit

fold_left_accumulate (fun '(cs, st) (i : combType Void) => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st) in (cs', st', o)) input (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) = spec_of_fibonacci input
sz:nat
input:list unit

fold_left_accumulate (fun '(cs, st) (i : combType Void) => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st) in (cs', st', o)) input (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) = spec_of_fibonacci input
sz:nat
input:list unit

fold_left_accumulate (fun '(cs, st) (i : combType Void) => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st) in (cs', st', o)) input (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) = spec_of_fibonacci input
sz:nat
input:list unit

fibonacci_invariant 0 (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) []
sz:nat
input:list unit
forall (t : nat) (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))) (d : combType Void), fibonacci_invariant t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st0) in (cs', st', o)) (nth t input d) in fibonacci_invariant (S t) (fst out) (acc ++ [snd out])
sz:nat
input:list unit
forall (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))), fibonacci_invariant (Datatypes.length input) st acc -> acc = spec_of_fibonacci input
sz:nat
input:list unit

fibonacci_invariant 0 (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) []
sz:nat
input:list unit

snd (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1) = N2Bv_sized sz 1 /\ snd (snd (fst (fst (reset_state (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)), N2Bv_sized sz 1)))) = N2Bv_sized sz (2 ^ N.of_nat sz - 1) /\ [] = spec_of_fibonacci (repeat tt 0)
sz:nat
input:list unit

P2Bv_sized sz 1 = P2Bv_sized sz 1 /\ N2Bv_sized sz (2 ^ N.of_nat sz - 1) = N2Bv_sized sz (2 ^ N.of_nat sz - 1) /\ [] = []
ssplit; reflexivity.
sz:nat
input:list unit

forall (t : nat) (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))) (d : combType Void), fibonacci_invariant t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st0) in (cs', st', o)) (nth t input d) in fibonacci_invariant (S t) (fst out) (acc ++ [snd out])
sz:nat
input:list unit
forall (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))), fibonacci_invariant (Datatypes.length input) st acc -> acc = spec_of_fibonacci input
sz:nat
input:list unit

forall (t : nat) (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))) (d : combType Void), fibonacci_invariant t st acc -> 0 <= t < Datatypes.length input -> let out := (let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (Comb (dropl >=> fork2) >==> Second (DelayInit (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) >==> Comb (addN >=> fork2)) cs (i, st0) in (cs', st', o)) (nth t input d) in fibonacci_invariant (S t) (fst out) (acc ++ [snd out])
(* prove that, if the invariant holds at the beginning of the loop body for timestep t, it holds at the end of the loop body for timestep t + 1 *)
sz:nat
input:list unit

forall (t : nat) (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))) (d : combType Void), snd st = match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end /\ snd (snd (fst (fst st))) = match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc = spec_of_fibonacci (repeat tt t) -> 0 <= t < Datatypes.length input -> snd (fst ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (Comb (fun x : combType Void * combType ... => x <- dropl x;; fork2 x) >==> Second (Comb (fun ... => ret ...) >==> DelayInitCE (N2Bv_sized sz (...))) >==> Comb (fun x : combType (...) * combType (...) => x <- addN x;; fork2 x)) cs (i, st0) in (cs', st', o)) (nth t input d))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (... >==> ... >==> Comb ...) cs (i, st0) in (cs', st', o)) (nth t input d)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc ++ [snd ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := step (Comb (fun x : combType Void * combType ... => x <- dropl x;; fork2 x) >==> Second (Comb (fun ... => ret ...) >==> DelayInitCE (N2Bv_sized sz (...))) >==> Comb (fun x : combType (...) * combType (...) => x <- addN x;; fork2 x)) cs (i, st0) in (cs', st', o)) (nth t input d))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit

forall (t : nat) (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))) (d : combType Void), snd st = match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end /\ snd (snd (fst (fst st))) = match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc = spec_of_fibonacci (repeat tt t) -> 0 <= t < Datatypes.length input -> snd (fst ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := let '(cs1, x) := let '(cs2, y) := let '(cs', x) := let '(cs2, y) := let '(input, en) := ret ... in (..., ...) in (tt, cs2, y) in (cs', (fst (x <- ...;; ...), x)) in (tt, cs2, y) in (cs1, tt, x <- addN x;; fork2 x) in (cs', st', o)) (nth t input d))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := let '(cs1, x) := let '... := ... in ... in (cs1, tt, x <- ...;; ...) in (cs', st', o)) (nth t input d)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc ++ [snd ((let '(cs, st0) := st in fun i : combType Void => let '(cs', (o, st')) := let '(cs1, x) := let '(cs2, y) := let '(cs', x) := let '(cs2, y) := let '(input, en) := ret ... in (..., ...) in (tt, cs2, y) in (cs', (fst (x <- ...;; ...), x)) in (tt, cs2, y) in (cs1, tt, x <- addN x;; fork2 x) in (cs', st', o)) (nth t input d))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
st:(unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz))%type
acc:list (combType (Vec Bit sz))
d:combType Void
H:snd st = match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end /\ snd (snd (fst (fst st))) = match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc = spec_of_fibonacci (repeat tt t)
H0:0 <= t < Datatypes.length input

snd (fst ((let '(cs, st) := st in fun i : combType Void => let '(o, st') := fork2 (addN (fst (fork2 (dropl (i, st))), snd (snd (fst cs)))) in (tt, (tt, snd (fork2 (dropl (i, st)))), tt, st', o)) (nth t input d))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst ((let '(cs, st) := st in fun i : combType Void => let '(o, st') := fork2 (addN ( fst (fork2 ...), snd (snd ...))) in (tt, (tt, snd (fork2 (dropl ...))), tt, st', o)) (nth t input d)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc ++ [snd ((let '(cs, st) := st in fun i : combType Void => let '(o, st') := fork2 (addN (fst (fork2 (dropl (i, st))), snd (snd (fst cs)))) in (tt, (tt, snd (fork2 (dropl (i, st)))), tt, st', o)) (nth t input d))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
u0, u1:unit
c0:combType (Vec Bit sz)
u:unit
c:combType (Vec Bit sz)
acc:list (combType (Vec Bit sz))
d:combType Void
Hl:snd (u0, (u1, c0), u, c) = match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end
Hrl:snd (snd (fst (fst (u0, (u1, c0), u, c)))) = match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end
Hrr:acc = spec_of_fibonacci (repeat tt t)
H0l:0 <= t
H0r:t < Datatypes.length input

snd (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), snd (snd (fst (u0, (u1, c0), u))))) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), snd (snd (fst (u0, (u1, c0), u))))) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc ++ [snd (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), snd (snd (fst (u0, (u1, c0), u))))) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
u0, u1:unit
c0:combType (Vec Bit sz)
u:unit
c:combType (Vec Bit sz)
acc:list (combType (Vec Bit sz))
d:combType Void
Hl:c = match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end
Hrl:c0 = match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end
Hrr:acc = spec_of_fibonacci (repeat tt t)
H0l:0 <= t
H0r:t < Datatypes.length input

snd (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), c0)) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), c0)) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc ++ [snd (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, c))), c0)) in (tt, (tt, snd (fork2 (dropl (nth t input d, c)))), tt, st', o))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= t
H0r:t < Datatypes.length input

snd (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end))), match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)) in (tt, (tt, snd (fork2 (dropl (nth t input d, match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end)))), tt, st', o))) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ snd (snd (fst (fst (fst (let '(o, st') := fork2 (addN (fst (fork2 (dropl ( nth t input d, match ... with | ... ... | ... ... end))), match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)) in (tt, (tt, snd (fork2 (dropl ( nth t input d, match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (...) end)))), tt, st', o)))))) = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ spec_of_fibonacci (repeat tt t) ++ [snd (let '(o, st') := fork2 (addN (fst (fork2 (dropl (nth t input d, match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end))), match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)) in (tt, (tt, snd (fork2 (dropl (nth t input d, match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end)))), tt, st', o))] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= t
H0r:t < Datatypes.length input

N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ spec_of_fibonacci (repeat tt t) ++ [N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)] = spec_of_fibonacci (repeat tt (S t))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= t
H0r:t < Datatypes.length input

N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (Datatypes.length (repeat tt t))) ++ [N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (Datatypes.length (repeat tt (S t))))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= t
H0r:t < Datatypes.length input

N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end) = N2Bv_sized sz (N.of_nat (fibonacci_nat t)) /\ match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end = match t with | 0 => N2Bv_sized sz 1 | S t_minus2 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 t) ++ [N2Bv_sized sz (Bv2N match t with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end + Bv2N match t with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end)] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S t))
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 0
H0r:0 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) = N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) /\ N2Bv_sized sz 1 = N2Bv_sized sz 1 /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 0) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1)))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 1)
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 1
H0r:1 < Datatypes.length input
N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 1)) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 1) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 2)
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input
N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t)))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t)))) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S t))) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t))))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S (S t))))
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 0
H0r:0 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) = N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) /\ N2Bv_sized sz 1 = N2Bv_sized sz 1 /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 0) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1)))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 1)
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 0
H0r:0 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1))) = N2Bv_sized sz (N.of_nat 0) /\ N2Bv_sized sz 1 = N2Bv_sized sz 1 /\ [] ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz 1) + Bv2N (N2Bv_sized sz (2 ^ N.of_nat sz - 1)))] = [N2Bv_sized sz (N.of_nat 0)]
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 0
H0r:0 < Datatypes.length input

N2Bv_sized sz (1 + (2 ^ N.of_nat sz - 1)) = N2Bv_sized sz (N.of_nat 0) /\ N2Bv_sized sz 1 = N2Bv_sized sz 1 /\ [] ++ [N2Bv_sized sz (1 + (2 ^ N.of_nat sz - 1))] = [N2Bv_sized sz (N.of_nat 0)]
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 0
H0r:0 < Datatypes.length input

N2Bv_sized sz 0 = N2Bv_sized sz (N.of_nat 0) /\ N2Bv_sized sz 1 = N2Bv_sized sz 1 /\ [] ++ [N2Bv_sized sz 0] = [N2Bv_sized sz (N.of_nat 0)]
ssplit; reflexivity.
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 1
H0r:1 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 1)) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 1) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 2)
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input
N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t)))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t)))) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S t))) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t))))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S (S t))))
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 1
H0r:1 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 1)) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) = N2Bv_sized sz (N.of_nat (fibonacci_nat 0)) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 1) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat 0))) + Bv2N (N2Bv_sized sz 1))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 2)
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 1
H0r:1 < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat 0)) + Bv2N (N2Bv_sized sz 1)) = N2Bv_sized sz (N.of_nat 1) /\ N2Bv_sized sz (N.of_nat 0) = N2Bv_sized sz (N.of_nat 0) /\ [N2Bv_sized sz (N.of_nat 0)] ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat 0)) + Bv2N (N2Bv_sized sz 1))] = [N2Bv_sized sz (N.of_nat 0); N2Bv_sized sz (N.of_nat 1)]
sz:nat
input:list unit
u0, u1, u:unit
d:combType Void
H0l:0 <= 1
H0r:1 < Datatypes.length input

N2Bv_sized sz (N.of_nat 0 + 1) = N2Bv_sized sz (N.of_nat 1) /\ N2Bv_sized sz (N.of_nat 0) = N2Bv_sized sz (N.of_nat 0) /\ [N2Bv_sized sz (N.of_nat 0)] ++ [N2Bv_sized sz (N.of_nat 0 + 1)] = [N2Bv_sized sz (N.of_nat 0); N2Bv_sized sz (N.of_nat 1)]
ssplit; reflexivity.
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t)))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t)))) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S t))) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t))))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S (S t))))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input

N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t)))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t)))) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S t))) ++ [N2Bv_sized sz (Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))) + Bv2N (N2Bv_sized sz (N.of_nat (fibonacci_nat t))))] = map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 (S (S (S t))))
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input

N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)) + N.of_nat (fibonacci_nat t)) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t)))) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ ((map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 t) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat t))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)) + N.of_nat (fibonacci_nat t))] = ((map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 t) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat t))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S (S t))))]
sz:nat
input:list unit
t:nat
u0, u1, u:unit
d:combType Void
H0l:0 <= S (S t)
H0r:S (S t) < Datatypes.length input

N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)) + N.of_nat (fibonacci_nat t)) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t) + fibonacci_nat t)) /\ N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) = N2Bv_sized sz (N.of_nat (fibonacci_nat (S t))) /\ ((map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 t) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat t))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)) + N.of_nat (fibonacci_nat t))] = ((map (fun n : nat => N2Bv_sized sz (N.of_nat (fibonacci_nat n))) (seq 0 t) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat t))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t)))]) ++ [N2Bv_sized sz (N.of_nat (fibonacci_nat (S t) + fibonacci_nat t))]
ssplit; repeat (f_equal; try lia). }
sz:nat
input:list unit

forall (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))), fibonacci_invariant (Datatypes.length input) st acc -> acc = spec_of_fibonacci input
sz:nat
input:list unit

forall (st : unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz)) (acc : list (combType (Vec Bit sz))), fibonacci_invariant (Datatypes.length input) st acc -> acc = spec_of_fibonacci input
sz:nat
input:list unit
st:(unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz))%type
acc:list (combType (Vec Bit sz))
H:snd st = match Datatypes.length input with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end /\ snd (snd (fst (fst st))) = match Datatypes.length input with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end /\ acc = spec_of_fibonacci (repeat tt (Datatypes.length input))

acc = spec_of_fibonacci input
sz:nat
input:list unit
st:(unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz))%type
H:snd st = match Datatypes.length input with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end
H0:snd (snd (fst (fst st))) = match Datatypes.length input with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end

spec_of_fibonacci (repeat tt (Datatypes.length input)) = spec_of_fibonacci input
sz:nat
input:list unit
st:(unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz))%type
H:snd st = match Datatypes.length input with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end
H0:snd (snd (fst (fst st))) = match Datatypes.length input with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end

spec_of_fibonacci (repeat tt (Datatypes.length input)) = spec_of_fibonacci input
sz:nat
input:list unit
st:(unit * (unit * combType (Vec Bit sz)) * unit * combType (Vec Bit sz))%type
H:snd st = match Datatypes.length input with | 0 => N2Bv_sized sz 1 | S t_minus1 => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus1)) end
H0:snd (snd (fst (fst st))) = match Datatypes.length input with | 0 => N2Bv_sized sz (2 ^ N.of_nat sz - 1) | 1 => N2Bv_sized sz 1 | S (S t_minus2) => N2Bv_sized sz (N.of_nat (fibonacci_nat t_minus2)) end

spec_of_fibonacci input = spec_of_fibonacci input
reflexivity. } Qed.

That concludes our tutorial! If you want to explore further, take a look at the examples directory in our GitHub repo, or check out our advanced-fetures demo. You can also view the full source for this page if you want to experiment with these examples yourself.