580 likes | 713 Vues
This tutorial by Dr. Doug Edwards provides an in-depth exploration of structural iteration in the Balsa programming language, focusing on modules for buffer management and counter designs. It covers key concepts such as passive input, modulo counters, and loadable counters, emphasizing strong typing, data structures, and shared procedures. Detailed code examples illustrate the principles of implementing counters, with a focus on both up/down control signals and loop structures. Ideal for learners and developers interested in hardware description languages and digital circuit design.
E N D
Balsa: A Language Tutorial Dr. Doug Edwards doug@cs.man.ac.uk
Structural Iteration constant n = 8 procedure buffer_n (input i : word; output o : word) is local array 1 .. n-1 of channel c : word begin buffer (i, c[1]) || -- first buffer buffer (c[n-1], o) || -- last buffer for || i in 1 .. n-2 then -- i’th buffer buffer (c[i], c[i+1]) end end declare constant internal arrayed channels compose n-2 internal buffers parallel structural iteration
Micropipeline Buffer procedure buffer (input i : byte; output o : byte) is local variable x : byte begin loop select i then x := i -- store the data end; o <- x end end select “chooses” a single input resulting in input “i” being passive select encloses assignment select statement has enclosed semantics
Examples: Simple Counters • Circuits count handshakes on their I/Ps and produce an O/P count bundle • They illustrate: • strong data typing • data types • arrays, records, enumeration • control structures • if, case • shared procedures
Modulo-16 binary counter procedure count16 (sync aclk; output count : nibble) is local variable count_reg : nibble begin loop sync aclk ; count <- count_reg ; count_reg := ( count_reg + 1 as nibble) end end handshake only input 4-bit output await h/s on input then output count from internal register inc internal register assign result back to internal variable cast result back to correct size
Passive Input Counter procedure count16 (sync aclk; output count : nibble) is local variable count_reg : nibble begin loop count <- count_reg ; select aclk then continue end ; count_reg := ( count_reg + 1 as nibble) end end using select makes aclk a passive i/p
Modulo-10 counter procedure count10(sync aclk; output count: C_size) is local variable count_reg : C_size variable tmp : C_size begin loop sync aclk ; if count_reg /= max_count then tmp := (count_reg + 1 as C_size) else tmp := 0 end || count <- count_reg ; count_reg := tmp end -- loop end end C_size and max_count previously declared temp variable updated in parallel with O/P assignment if then else construct
Nested if Statements • Balsa does not have an elseif keyword if condition1 then procA else if condition2 then procB end else procC end • Note the explicit sequencing
Parallel Guard Evaluation • If the guards are mutually exclusive, conditions may be tested in parallel: if condition1 then procA | condition2 then procB else procC end
Loadable Decade Counter -1 • Illustrates record data type: -- count10b.balsa: an aysnchronous loadable decade counter import [balsa.types.basic] public type C_size is nibble constant max_count = 9 type In_bundle is record ld_data : C_size ; ld_ctrl : bit end in-line comment record data-structure definition
Loadable Decade Counter -2 procedure updown10 (input in_sigs: In_bundle; output count: C_size) is local variable count_reg : C_size variable tmp : C_size begin loop -- main procedure body end end body in next slide
Recordselector Recordselector Loadable Decade Counter -3 select in_sigs then if in_sigs.ld_ctrl = 0 then count_reg := in_sigs.ld_data else if count_reg /= max_count then tmp := (count_reg + 1 as C_size) else tmp := 0 end || count <- count_reg ; count_reg := tmp end -- end if end -- complete select H/S load counter enclosed select allows multiple reads from I/P bundlewithout latches increment count or wrap round
Padding Record Structures • Records can be coerced to a fixed length: type Flags is record carry, overflow, zero, negative, int_en: bit over byte Flags is padded to 8 bits
Up/Down Counter • Add another bit to control direction of counting: type C_size is nibble constant max_count = 9 type In_bundle is record ld_data : C_size ; ld_ctrl : bit; dir : bit end direction bit
Up/Down Counter case in_sigs.dir of 0 then -- counting down if count_reg /= 0 then tmp := (count_reg - 1 as C_size) else tmp := max_count end || count <- count_reg | 1 then -- counting up if count_reg /= max_count then tmp := (count_reg + 1 as C_size) else tmp := 0 end || count <- count_reg end ; -- end case statement case statement count down or count up
Enumeration • Up/down control signal could be defined as: type dir is enumeration down, up end • Values may be aliased type opcode is enumeration ADD, ADC, SUB, SBC, AND, OR, LD, ST=LD, BR over 3 bits aliased values
Shared Procedures -1 type inc is 2 signed bits procedure updown10 (input in_sigs: In_bundle; output count: C_size) is local variable count_reg : C_size variable tmp : C_size variable inc : inc -- define shared procedure shared update_ctr is begin tmp := (count_reg + inc as C_size) end -- procedure body inc takes values -1 and +1 use shared hardware for counting up and down shared procedure cast result to correct size inc can be +/- 1
Shared Procedures -2 loop select in_sigs then begin if in_sigs.load_l = 0 then count_reg := in_sigs.ld_data else case in_sigs.dir of down then -- counting down inc := ( -1 as inc); if count_reg /= 0 then update_ctr() else tmp := max_count similar code for count up counting down: set inc to -1 cast required call shared procedure
Arrays • Numerically indexed compositions of the same type • Typical use is in the specification of register banks type RegData is 16 bits variable RegBank : array 0..7 of RegData
Register Bank Definition bundle containing Control info type RegCtrl is record Read0 : RegNo ; Read1 : RegNo ; Write : RegNo ; DoRead0 : bit ; -- if true read port 0 DoRead1 : bit ; -- if true read port 1 DoWrite : bit -- if true write data end procedure RegBank ( input RegCtrl : RegCtrl; input WritePort : RegData ; output ReadPort0, ReadPort1 : RegData ) is local variable RegBank : array 0..7 of RegData Register identifiers read/write control bits Control word write data read data internal registers
Register Bank Control write signalled- await data, then update register await control word loop select RegCtrl then if RegCtrl.DoWrite then select WritePort then RegBank[RegCtrl.Write] := WritePort end else if RegCtrl.DoRead0 then ReadPort0 <- RegBank[RegCtrl.Read0] end || if RegCtrl.DoRead1 then ReadPort1 <- RegBank[RegCtrl.Read1] end end end end two reads can occur together can’t read and write simultaneously read register & output to channel
Array Operations • Arrays can be concatenated variable a, b : byte variable c: array 4 of byte variable d : array 6 of byte c:= {a,b,a,b} -- tuple construction c:= {a,b} @ {a,b} -- array concatenation d:= c @ {a,b}
Array Operations • arrays can be sliced variable x : array 0 ..7 of 4 bits variable y : array 0.. 1 of 4 bits y:= x[1..2] -- returns a slice of x of type-- array 0 .. 1 of 4 bits
Instruction Decoder type Word is 16 signed bits type Imm5 is 5 signed bits variable ICtrl is 8 signed bits -- bottom 5 bits contain an immediate value variable Imm16 : word Im16:= (((ICtrl as array 0..7 of bit)[0..4] as Imm5) as Word) Word and Imm5 are signed bits 5-bit field is extracted & sign-extended to 16 bits
Instruction Decoder type Word is 16 signed bits type Imm5 is 5 signed bits variable ICtrl is 8 signed bits -- bottom 5 bits contain an immediate value variable Imm16 : word Im16:= (ICtrl as array 0..7 of bit) casts Ictrl into an array of bits for slicing
Instruction Decoder type Word is 16 signed bits type Imm5 is 5 signed bits variable ICtrl is 8 signed bits -- bottom 5 bits contain an immediate value variable Imm16 : word Im16:= (ICtrl as array 0..7 of bit)[0..4] extract bottom 5 bits
Instruction Decoder type Word is 16 signed bits type Imm5 is 5 signed bits variable ICtrl is 8 signed bits -- bottom 5 bits contain an immediate value variable Imm16 : word Im16:= ((ICtrl as array 0..7 of bit)[0..4] as Imm5) cast the extracted bits into a 5-bit signed number
Instruction Decoder type Word is 16 signed bits type Imm5 is 5 signed bits variable ICtrl is 8 signed bits -- bottom 5 bits contain an immediate value variable Imm16 : word Im16:= (((ICtrl as array 0..7 of bit)[0..4] as Imm5) as Word) sign-extend the 5-bit number to 16 bits
The Select Statement • MUX: • Illustrates unbuffered choice • Inputs assumed mutually exclusive • No internal latches procedure mux (input a, b :byte; output c :byte) is begin loop select a then c <- a -- channel behaves like a variable | b then c <- b -- ditto end end end select gives choice
Arbitrated Choice public procedure mux (input a, b :byte; output c :byte) is begin loop arbitrate a then c <- a | b then c <- b end end end arbitrated choice
Parameterised Procedures • Facilitates libraries • Ex: buffer with parameterised width procedure Buffer ( parameter X : type ; input i : X; output o : X) is local variable x : X begin loop i -> x ; o <- x end end X is of type type vars defined in terms of parameterised type
Using Parameterised Modules -- pbuffer1a.balsa - calling parameterised a procedure import [balsa.types.basic] import [pbuffer1] public -- instantiate a byte-wide single place buffer procedure test (input a :byte ; output b : byte) is begin Buffer over type byte of a,b end invoke a buffer of width byte Buffer defined previously
Multiple Parameters -1 • Consider a pipeline • with parameterised width • with parameterised depth -- BufferN: a n-place parameterised, variable width buffer procedure BufferN( parameter n : cardinal ; parameter X : type ; input i : X ; output o : X) is local procedure buffer is Buffer over type X begin -- body of procedure end type cardinal ranges over natural numbers
test for special cases test for special cases Multiple Parameters -2 if n = 1 then -- single place pipeline buffer(i, o) | n >= 2 then local array 1 .. n-1 of channel c : X begin buffer(i, c[1]) || -- first buffer buffer(c[n-1], o) || -- last buffer for || i in 1 ..n-2 then buffer(c[i], c[i+1]) end end else print error, “zero length pipeline specified” end Procedure Buffer8 is BufferN over 4, type byte body of procedure define a 4-deep byte-wide pipeline
Recursive Procedures • Adding recursion to Balsa allows elegant specifications of many circuits • Consider circuit to generate n handshakes for each call • Divide circuit into odd and even cases • even case: call itself twice • odd case : issue preliminary h/s and then call itself twice
Handshake Generator -1 procedure Repeat (parameter n : cardinal; sync o ) is begin if n = 0 then print error, “Repeat n must not be 0” | n = 1 then sync o | n = 2 then sync o ; sync o else -- main body of procedure end procedure name “repeat” base cases recursive definition here
Handshake Generator -2 local shared doNext is begin Repeat over n/2 of (o) end begin if (n as bit) then -- n is odd sync o end ; doNext () ; doNext () end end Procedure Gen5 is Repeat over 5 define local shared procedure recursive call of “repeat” call shared procedure twice define a 5x generator
Handshake Multiplier -- MultHS: repeat: handshake on `o’ `n’ times for each input import [balsa.types.basic] import [GenHS] public procedure MultHS (parameter n : cardinal; sync i ; sync o ) is begin loop select i then continue end ; Repeat over n of (o) end end -- Here is a x5 gnerator procedure MultHS5 is MultHS over 5 procedure repeat defined earlier
An n-way multiplexer • Decompose MUX:
An n-way multiplexer -1 -- Pmux1.balsa: A recursive parameterised MUX definition import [balsa.types.basic] public procedure PMux ( parameter X : type; parameter n : cardinal; array n of input inp : X; output out : X ) is begin -- procedure body width of input number of inputs each input is a channel output channel
An n-way multiplexer -2 if n = 0 then print error,”Parameter n should not be zero” | n = 1 then loop select inp[0] -> inp then out <- inp end end | n = 2 then loop select inp[0] -> inp then out <- inp | inp[1] -> inp then out <- inp end end when data arrives on either i/p, pass it to o/p base cases
An n-way multiplexer -3 else local channel out0, out1 : X constant mid = n/2 begin PMux over type X, mid of inp[0..mid-1],out0 || PMux over type X, n-mid of inp[mid..n-1],out1 || PMux over type X, 2 of {out0,out1},out end end end 2 internal channels two half-size muxs & one 2:1 mux
Systolic Counters -1 • Kees van Berkel’s design: • Recursively split counter into a head cell & a n/2 tail cell • Head cells may be be odd or even counts
Systolic Counters -2 • Example: • A modulo-11 counter may be constructed 11 = 1 + 2 * 5 11 = 1 + 2 * (1 + 2 * 2) 11 = 1 + 2 * (1 + 2 * (2 * 1))
Systolic Counters -3 • Count even cell: procedure c_even(sync a_left, a_right, b_left, b_right) is begin loop select a_right then sync a_left ; sync a_left | b_right then sync b_left end end end Choose between a or b h/s on right if a then double else pass b from right to left
Systolic Counters -4 • Count odd cell procedure c_odd(sync a_left, a_right, b_left, b_right) is begin loop sync a_left; select a_right then sync a_left | b_right then sync b_left end end end first h/s on left then choose bewteen a & b and pass on
Systolic Counters -5 • Base case: count 1 cell procedure c_1(sync a, b) is begin loop sync a; sync b end end copy handshake from a to b
Systolic Counters -6 procedure countN (parameter N : cardinal ; sync a, b) is local sync a_int, b_int begin if N = 0 then print error, “Parameter n should not be zero” | N = 1 then c_1(a, b) else if (N as bit) then -- Odd c_odd(a, a_int, b, b_int) else c_even(a, a_int, b, b_int) end || countN over N/2 of a_int, b_int end end -- OK instantiate an arbitrary counter procedure Ctest is countN over 53 2 internal sync- only channels plant either an odd or even channel this is how we use use the counter definition and compose with remainder of counter
Systolic Counters -7 • The count even cell chooses between a and b with a handshake on its right
Count-Even cell • Count even cell: procedure c_even(sync a_left, a_right, b_left, b_right) is begin loop select a_right then sync a_left ; sync a_left | b_right then sync b_left end end end handshake right encloses rest of handshakes