CS-A1120 Programming 2
Department of Computer Science
Aalto University
(Based on material by Petteri Kaski and Tommi Junttila)
Last week we got familiar with the following operators:
AND, OR, NOT, and XOR (in Scala: &, |, !, ^).
Below are truth tables (given input a, b) for these operators - but, I have masked them by 🦍 🐳 🦑 🦆
Which one is which?
| a | b | a 🦍 b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
| a | 🐳a |
|---|---|
| 0 | 1 |
| 1 | 0 |
| a | b | a 🦑 b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| a | b | a 🦆 b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
A "machine" that adds up numbers
How to build machines that compute with bits?
We call these gates
(The output (value) of a gate is determined by the inputs )
out
?
?
?
?
0
out
(a & b) | (!a & !b)
| a | b | (a & b) | (!a & !b) |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
truth table
abstract class Value: def read: Int end Value class VariableValue(c: Int) extends Value: protected var _myVal = c def write(v: Int) = _myVal = v def read = _myVal end VariableValue class SumValue(v1: Value, v2: Value) extends Value: def read = v1.read + v2.read end SumValue
val v1 = VariableValue(10) val v2 = VariableValue(20) val s = SumValue(v1,v2) println(f"The sum is now ${s.read}") v1.write(0) println(f"The sum is now ${s.read}")
The sum is now 30 The sum is now 20
val x = VariableValue(10) val y = VariableValue(20) val z = VariableValue(30) val sum1 = SumValue(SumValue(x,y),z) val sum2 = VariableValue((x.read+y.read) +z.read) print(f"A:${sum1.read},") print(f"B:${sum2.read},") x.write(100) print(f"C:${sum1.read},") print(f"D:${sum2.read},")
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.
tinylogpackage 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
(Compare to the polynomial expression exercise in round 1.)
import tinylog.* // Build a simple circuit val a = InputElement() val b = InputElement() val g1 = AndGate(a,b) val g2 = NotGate(a) val g3 = NotGate(b) val g4 = AndGate(g2,g3) val g5 = OrGate(g1,g4)
Then we can test it:
scala> a.set(true) scala> b.set(false) scala> g5.value val res1: Boolean = false scala> a.set(false) scala> b.set(false) scala> g5.value val res2: 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
import tinylog.* // Build a simple circuit val a = InputElement() val b = InputElement() val g1 = a && b val g2 = !a val g3 = !b val g4 = g2 && g3 val g5 = g1 || g4
import tinylog.* // Build a simple circuit val a = InputElement() val b = 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
\(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.
\(0 + 0 = 00\)
\(0 + 1 = 01\)
\(1 + 0 = 01\)
\(1 + 1 = 10\)
(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 We could, in principle, list all possible combination of inputs and outputs for the sum… any problems?
Bus class in tinylogBus 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.
aa if s is 0 and bb if s is 1:
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 = 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
Video of similar demo is here
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 = 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!
Demo video
f do?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
Demo video
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.
Gate
Gate and Bus using boolean operationsGate (and Bus) are not known when the circuit is constructed!
Gate or Bus when you build the circuit, code 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