CS-A1120 Programming 2
Department of Computer Science
Aalto University
(Based on material by Petteri Kaski and Tommi Junttila)
How to build machines that compute with bits?
A "machine" that adds up numbers
def f(a:Boolean, b:Boolean) = if a == b then true else false
if
def f(a:Boolean, b:Boolean) = a == b
==
, !=
, et c), but using Boolean operators (&
, |
, !
)
def f(a:Boolean, b:Boolean) = (a & b) | (!a & !b)
A "machine" that adds up numbers
(The output (value) of a gate is determined by the inputs )
Let's write a Scala programmer's construction kit for a circuit (and, later, a computer)!
We will need to represent:
to represent gates
as well as inputs.
Then that should take care of the wiring.
And the 'output' is the value of the last gate object in the chain.
tinylog
package tinylog
Let's use common abstract class for logic gates and inputs
abstract class Gate():
def value: Boolean // implemented by the extending classes
end Gate
class NotGate(in: Gate) extends Gate():
def value = !in.value
end NotGate
class OrGate(in1: Gate, in2: Gate) extends Gate():
def value = in1.value || in2.value
end OrGate
class AndGate(in1: Gate, in2: Gate) extends Gate():
def value = in1.value && in2.value
end AndGate
And inputs
class InputElement() extends Gate():
var v = false // default value is false
def set(s: Boolean) = { v = s }
def value = v
end InputElement
import tinylog.*
// Build a simple circuit
val a = new InputElement()
val b = new InputElement()
val g1 = new AndGate(a,b)
val g2 = new NotGate(a)
val g3 = new NotGate(b)
val g4 = new AndGate(g2,g3)
val g5 = new OrGate(g1,g4)
sbt tinylog/console
from the round 3 exercise package.) Then we can test it:
scala> a.set(true)
scala> b.set(false)
scala> g5.value
val res5: Boolean = false
scala> a.set(false)
scala> b.set(false)
scala> g5.value
val res8: Boolean = true
Note how the circuit works as a function - when we change the input it 'recalculates' the output.
&&
||
+
-
/
…) for classes&&
, ||
, !
act as short-cuts for AND, OR, and NOT gates
We extend our abstract Gate
class somewhat:
class Gate():
/** Boolean value of the Gate.*/
def value: Boolean
/** Operator definition for NotGate of this Gate.*/
def unary_! = new NotGate(this)
/** Operator definition for AndGate of this Gate and that Gate.*/
def &&(that: Gate): Gate = new AndGate(this, that)
/** Operator definition for OrGate of this Gate and that Gate.*/
def ||(that: Gate): Gate = new OrGate(this, that)
end Gate
Now we can do:
import tinylog.*
// Build a simple circuit
val a = new InputElement()
val b = new InputElement()
val g1 = a && b
val g2 = !a
val g3 = !b
val g4 = g2 && g3
val g5 = g1 || g4
Or even:
import tinylog.*
// Build a simple circuit
val a = new InputElement()
val b = new InputElement()
val g5 = (a && b) || (!a && !b)
Scala creates the objects implicitly. Makes it easy to build large circuits.
Let's start with a machine that adds up two single bit numbers
Binary addition
\(0 + 0 = 0\)
\(0 + 1 = 1\)
\(1 + 0 = 1\)
\(1 + 1 = 10\)
Remember school: when the sum cannot 'fit' in a position we carry to the next.
Writing out the carry in all additions
\(0 + 0 = 00\)
\(0 + 1 = 01\)
\(1 + 0 = 01\)
\(1 + 1 = 10\)
Discuss in pairs
(Assuming the inputs (a
, b
) are tinylog
Gate
, then Scala will automatically create the circuit with these expressions!)
a
and b
, using a selector bit s
:
s
, is \(0\) we want the output, r
, to have the same value as a
, and if it is \(1\) the output should have the same value as b
A general procedure: Set up the truth table → build the formula
Bus
class in tinylog
Bus
represents multiple bits (gates), so let's make it extend the Scala trait Seq
Bus
is a custom Scala collection for Gate
objects&
, |
, ~
)
You can read more about how Bus
is constructed in the course notes, and find the source of Bus.scala
in the tinylog
sub-project of this round's exercises.
Because Bus
is a Seq
we have access to the usual methods for collections, such as map
and fold
.
A circuit that gives the value of bus aa
if s
is 0
and bb
if s
is 1
:
In Scala:
scala> import tinylog.*_
scala> val aa = Bus(Gate.False, Gate.True, Gate.True, Gate.True)
scala> val bb = Bus(Gate.True, Gate.False, Gate.False, Gate.False)
scala> val s = Gate.input() // Use an input gate so we can select
scala> val cc = (aa && !s) | (bb && s) // Construct the circuit
scala> cc.values // By default s is false, so we will select aa
val res0: Seq[Boolean] = List(false, true, true, true)
scala> s.set(true) // But, by setting s to true...
scala> cc.values // The circuit output has changed to bb
val res2: Seq[Boolean] = List(true, false, false, false)
Toggler
to display an UI:
scala> val t = new Toggler() // Create toggler UI
scala> t.watch("aa",aa) // Display bus aa
scala> t.watch("bb",bb) // Display bus bb
scala> t.watch("s",s) // Display selector bit
scala> t.watch("cc", cc) // Display output bus cc
scala> t.go() // And start
You can see a demonstration of a similar example here
Task: write a function that creates a circuit detecting if a Bus
contains a bit-palindrome.
=?
(a && b) || (!a && !b)
10101
is a palindrome; 11001
is not.)
// Helper function - output gate is true when input
// gates are equal
def gatesEqCircuit(a : Gate, b : Gate) : Gate =
(a && b) || (!a && !b)
// Output gate is true when input bus is a palindrome
def isPalindromeCircuit(aa: Bus) : Gate =
val len = aa.length
// Create an array of size len/2 to hold the eq gates
val eqc = new Array[Gate](len/2)
for i <- 0 until len/2 do
eqc(i) = gatesEqCircuit(aa(i), aa(len-1-i))
end for
// Now we can just reduce it with AND to form the
// final gate
eqc.reduce(_ && _)
end isPalindromeCircuit
val aa = Bus.inputs(7)
val p = isPalindromeCircuit(aa)
val t = new Toggler()
t.watch("Input",aa)
t.watch("Palindrome?",p)
t.go()
NOTE: At no point does the code need to check the value of a gate when we build the circuit!
import tinylog.*
def e(a:Gate, b:Gate) = (a && b) || (!a && !b)
def f(aa:Bus, bb:Bus) =
require(aa.length == bb.length)
val x = aa.zip(bb).map((a,b) => e(a,b))
x.reduce(_ && _)
end f
What about a 64 bit machine that adds up two 64 bit numbers?
What exactly do we do when adding up two integers?
Build a circuit that follows the procedure, one significant bit at a time.
0
or 1
.
Call this circuit a Full adder
Implementation:
def buildFullAdder(a: Gate, b: Gate, c_in: Gate): (Gate, Gate) =
val c_out = (!a && b && c_in) || (a && !b && c_in) || // ...
(a && b && !c_in) || (a && b && c_in)
val s = (!a && !b && c_in) || (!a && b && !c_in) || // ...
(a && !b && !c_in) || (a && b && c_in)
(c_out, s)
end buildFullAdder
def buildRCAdder(bus1: Bus, bus2: Bus) =
require(bus1.length == bus2.length,
"Can only add buses of same length")
var carry_in: Gate = Gate.False // no initial carry
val ss = new Array[Gate](bus1.length)
for i <- 0 until bus1.length do
val (carry_out, sum) = buildFullAdder(bus1(i), bus2(i),
carry_in)
carry_in = carry_out // carry from bit i propagates
ss(i) = sum
end for
new Bus(ss.toIndexedSeq)
end buildRCAdder
Example: 4 bit addition machine
Bus
as a data wordSequences are usually though of as indexed 'from left to right', but (binary) numbers are indexed (least significant digit) 'from right to left'.
For example, this constant three gate bus:
val aa = Bus(Gate.True, Gate.True, Gate.False)
represent the binary string 011
.
Common pitfall when debugging exercises for example.
This week:
Gate
Hints:
Gate
and Bus
using boolean operationsGate
(and Bus
) are not known when the circuit is constructed!
Gate
or Bus
when you build the circuit, your design is probably wrongGate
and Bus
- these are re-evaluated every time the input changes*Play.scala
main programs
Trigger
for you to edit and play around with