Combinational and sequential circuits
In Agda
Utrecht University, June 2017
Plan for the talk
- We'll start with an example
- EDSL definitions to understand the example
- Combinational vs. sequential (example)
- Semantics, equivalence
- Circuit patterns, etc.
What does this compute?
Haskell's mapAccumL
A.K.A "stateful map"
mapAccumL :: (s -> a -> (s,b)) -> s -> [a] -> (s, [b])
mapAccumL _ s [] = (s, [])
mapAccumL f s (x:xs) = (s″,y:ys)
where (s′, y ) = f s x
(s″,ys) = mapAccumL f s′ xs
mapAccumL
in Agda (λ₁
)
λ₁
(Lambda1) is the name of our EDSL
- This
mapAccL-par
works with vectors
mapAccL-par : ∀ n (λ₁ σ → λ₁ ρ → λ₁ (σ ⊗ τ)) → λ₁ σ → λ₁ (vec ρ n) → λ₁ (σ ⊗ vec τ n)
mapAccL-par zero _ s _ = s , []
mapAccL-par (suc n′) f s xs = case[] xs of λ x xs′ →
case⊗ (f s x) of λ s′ y →
case⊗ (mapAccL-par n′ f s′ xs′) of λ s″ ys′ →
s″ , (y ∷ ys′)
- This is a "parallel" version, later a sequential one
Definitions and notation
- Universe of types for circuit I/O
- Unit, base type (B), sums, products
- Notation for object type vars:
σₙ, τₙ, ρₙ
data Uₚ (B : Set) : Set where
𝟙 : Uₚ B
ι : Uₚ B
_⊗_ _⊕_ : (τ₁ τ₂ : Uₚ B) → Uₚ B
vec τ n = τ ⊗ τ ⊗ … ⊗ τ
Tₚ : ∀ {B} (τ : Uₚ B) → Set
Tₚ 𝟙 = ⊤
Tₚ {B} ι = B
Tₚ (τ₁ ⊗ τ₂) = Tₚ τ₁ × Tₚ τ₂
Tₚ (τ₁ ⊕ τ₂) = Tₚ τ₁ ⊎ Tₚ τ₂
Context, Environments and Gates
- A
Ctxt
(inputs) is a list of Uₚ
- An
Env
is a heterogenous list indexed by a Ctxt
- Contexts:
Γₙ, Δₙ,…
- Environments:
γₙ, δₙ,…
- A
Gate
has an interface (Δ,τ
) and a spec (⟦–⟧g
)
- Other information for synthesis…
record Gate {B} (Δ : Ctxt B) (τ : Uₚ B) : Set where
constructor MkG
field ⟦–⟧g : Env Tₚ Δ → Tₚ τ
CONST : ∀ {B} {τ : Uₚ B} (x : Tₚ τ) → Gate {B} ε τ
CONST x = MkG (const x)
The "Lambda1" EDSL
- Core datatype (
λ₁′
) indexed by inputs, output
- De Bruijn indices, let's for sharing, explicit looping construct
- Products, sums, fundamental gates
- No arrow types: no lambda, no application
data λ₁′ {B} : (Γ : Ctxt B) (τ : Uₚ B) → Set where
[_] : ∀ {Γ τ} (i : Γ ∋ τ)
→ λ₁′ Γ τ
letₓ′ : ∀ {Γ σ τ} (x : λ₁′ Γ σ) (b : λ₁′ (σ ◅ Γ) τ)
→ λ₁′ Γ τ
loop′ : ∀ {Γ σ τ} (c : λ₁′ (σ ◅ Γ) (σ ⊗ τ))
→ λ₁′ Γ τ
Lambda1 surface language (λ₁
)
- De Bruijn isn't so friendly
- So we transform Agda binders into binders of the EDSL
- Using a version of "unembedding"
λ₁ : ∀ {B} (τ : Uₚ B) → Set
λ₁ {B} τ = ∀ {Γ : Ctxt B} → λ₁′ Γ τ
letₓ : ∀ (x : λ₁ σ) (f : λ₁ σ → λ₁ τ) → λ₁ τ
loop : ∀ (f : λ₁ σ → λ₁ (σ ⊗ τ)) → λ₁ τ
syntax letₓ eₓ (λ x → b) = let͘ x ≔ eₓ in͘ b
syntax loop {σ} (λ s → b) = loop s ∶ σ in͘ b
(c₁ $ c₂) ⇒ (letₓ c₂ c₁)
(λ₁ σ₀ → λ₁ σ₁ → … → λ₁ σₙ → λ₁ τ) ⇒ (λ₁′ (σ₀ ◅ σ₁ ◅ … ◅ σₙ ◅ ε) τ)
Back to an example for a while
mapAccL-par : ∀ n (λ₁ σ → λ₁ ρ → λ₁ (σ ⊗ τ)) → λ₁ σ → λ₁ (vec ρ n) → λ₁ (σ ⊗ vec τ n)
mapAccL-par zero _ s _ = s , []
mapAccL-par (suc n′) f s xs = case[] xs of λ x xs′ →
case⊗ (f s x) of λ s′ y →
case⊗ (mapAccL-par n′ f s′ xs′) of λ s″ ys′ →
s″ , (y ∷ ys′)
Sequential mapAccL
- We can easily define a sequential version of
mapAccL
mapAccL-seq
, has a loop
instead of recursion
mapAccL-seq : (f : λ₁ σ → λ₁ ρ → λ₁ (σ ⊗ τ)) (x : λ₁ ρ) → λ₁ τ
mapAccL-seq {σ} f x = loop s ∶ σ in͘ (f s x)
Semantics, one step at a time
- Loops in circuits: semantics must consider state
⟦_⟧s : (c : λ₁′ Γ τ) (s : λₛ c) (γ : Env Tₚ Γ) → (λₛ c × Tₚ τ)
- There's an inductive family giving the state type of a circuit
data λs {B} : {Γ : Ctxt B} {τ : Uₚ B} (c : λ₁′ Γ τ) → Set where
s[_] : ∀ {Γ σ} (i : Γ ∋ σ) → λₛ [ i ]
sLet : ∀ {Γ σ τ} {x : λ₁′ Γ σ} {e : λ₁′ (σ ◅ Γ) τ} (xₛ : λₛ x) (eₛ : λₛ e)
→ λₛ (letₓ′ x e)
sLoop : ∀ {Γ σ τ} {c : λ₁′ (σ ◅ Γ) (σ ⊗ τ)} (iₛ : Tₚ σ) (cₛ : λₛ c)
→ λₛ (loop′ c)
_s,_ : ∀ {Γ τ₁ τ₂} {x : λ₁′ Γ τ₁} {y : λ₁′ Γ τ₂} (xₛ : λₛ x) (yₛ : λₛ y)
→ λₛ (x ,′ y)
Circuits in (discrete) time: n
steps
- One state transition per clock cycle
- We can simulate a number of cycles with
⟦_⟧n
- Final state also returned
- Usefulness clear soon
⟦_⟧n : (c : λ₁′ Γ τ) (s : λₛ c) (γn : Vec (Env Tₚ Γ) n) → λₛ c × Vec (Tₚ τ) n
⟦_⟧n n c s = mapAccumL n s ⟦ c ⟧s
Parallel vs. sequential
- It's quite intuitive how
mapAccL-par
and mapAccL-seq
are related
- In one step from
mapAccL-par
, in n
steps from mapAccL-seq
- But the exact relation is not so short…
mapAccL-seq : (f : λ₁ σ → λ₁ ρ → λ₁ (σ ⊗ τ)) (x : λ₁ ρ) → λ₁ τ
mapAccL-seq {σ} f x = loop s ∶ σ in͘ (f s x)
mapAccL-par
vs. mapAccL-seq
_⊒_ : ∀ {α β φ : Set} → φ × (α × β) → α × β → Set
(_ , sa,ra) ⊒ sb,rb = sa,ra ≡ sb,rb
n : ℕ
X : λₛ (mapAccL-par n f)
σ ρ τ : Uₚ
f : λ₁ σ → λ₁ ρ → λ₁ (σ ⊗ τ)
s : Tₚ σ
xs : Tₚ (vec ρ n)
MALSt : Tₚ σ → λₛ (mapAccL-seq {σ} f)
MALIns : Tₚ (vec ρ n) → Vec (Env Tₚ (ρ ◅ ε)) n
⟦ mapAccL-par n f ⟧s X (s ↦ xs ◅ ε) ⊒ ⟦ mapAccL-seq f ⟧n n (MALSt s) (MALIns xs)
From mapAccL
, other patterns…
iterate-par : ∀ n (f : λ₁ τ → λ₁ τ) (x : λ₁ τ) → λ₁ (vec τ (suc n))
iterate-par n {τ} f x = mapAccL-par n f′ x (replicate͘ unit)
where f′ : λ₁ τ → λ₁ 𝟙 → λ₁ (τ ⊗ τ)
f′ x _ = fork (f x)
Sequential version: iterate-seq
iterate-seq : (f : λ₁ τ → λ₁ τ) → λ₁ τ
iterate-seq {τ} f = mapAccL-seq f′ unit
where f′ : λ₁ τ → λ₁ 𝟙 → λ₁ (τ ⊗ τ)
f′ x _ = fork (f x)
Folds and scans, all-in-one
foldl-scanl-par : ∀ n (λ₁ τ → λ₁ τ → λ₁ τ) → λ₁ τ → λ₁ (vec τ n) → λ₁ (τ ⊗ vec τ n)
foldl-scanl-par n _⊞_ = mapAccL-par n (λ acc x → acc , (acc ⊞ x))
foldl-par n _⊞_ e xs = fst (foldl-scanl-par n _⊞_ e xs)
scanl-par n _⊞_ e xs = snd (foldl-scanl-par n _⊞_ e xs)
Sequential version: foldl-scanl-seq
foldl-scanl-seq : ∀ (_⊞_ : λ₁ τ → λ₁ τ → λ₁ τ) (x : λ₁ τ) → λ₁ τ
foldl-scanl-seq _⊞_ = mapAccL-seq (λ acc x → acc , (acc ⊞ x))
Composition and pipelining
- Pardon the flipped composition, it makes things easier…
_comp_ : (f : λ₁ σ → λ₁ ρ) (g : λ₁ ρ → λ₁ τ) → (λ₁ σ → λ₁ τ)
_comp_ = flip _∘_
_compD_ : (f : λ₁ σ → λ₁ ρ) (g : λ₁ ρ → λ₁ τ) → (λ₁ σ → λ₁ τ)
f compD g = (f comp delay) comp g
Correctness of pipelining
- Here the whole output Stream really matters
- The pipelined version should be shifted by one time unit
- Bisimilarity
⟦ f comp g ⟧* (stComp sf sg) γ ≈ tail (⟦ f compD g ⟧* (stCompD sf sg sd) γ)
N-ary composition
- Type-aligned sequence:
VecA F as bs
fs : VecA F as bs = [f₀ : F a₀ b₀, f₁ : F a₁ b₁, …
- With
b₀ = a₁, b₁ = a₂, …
compN : let F = λ σ τ → (λ₁ σ → λ₁ τ)
in (fs : VecA F σs τs) → λ₁ (head σs) → λ₁ (last τs))
⟦ compN fs ⟧* (stCompN n sfs) γ ≈ drop n (⟦ compDN fs ⟧* (stCompDN n sfs sds) γ)