Tietokoneen mysteeri kombinaatiologiikka Petteri Kaski Tietotekniikan laitos Aalto-yliopisto ICS-A2 Ohjelmointi 2 29. helmikuuta 26
Tietokoneen mysteeri (4 kierrosta) 2. Bitit ja data binäärilukujärjestelmä, bittien käsittely Scalalla 3. Kombinaatiologiikka 4. Sekventiaalilogiikka 5. Ohjelmoitava kone logiikkaporteista rakennettuja piirejä Scala-simuloituna kello ja takaisinkytkentä Scala-simulaatioon, prosessorin laskentapolku konekieliohjelmoimme 5574-porttista Scalalla rak. & simuloitua armlet -prosessoria
3. Kombinaatiologiikka
[demo]
Millä periaatteilla tietokone toimii? Miten rakennetaan bittejä käsitteleviä koneita?
Pohjalta ponnistaen ( bottom up )
Tavoite: Yhteenlaskukone
Kierros 3: Kombinaatiologiikka Logiikkaportit (AND, OR,...) ja niiden yhdistäminen piiriksi Scala-simulaatio ja käpistely ( Toggler -työkalu) Totuustaulu ja piirisynteesi totuustaulusta Porttitason ja väylätason suunnittelu
Nuoremman tietokoneinsinöörin rakennussarja* NOT EI (johtoa) AND JA OR TAI (logiikkaportteja) *) Kurssin edetessä sarjaan tulee täydennyksiä
NOT AND OR NOT AND OR AND OR Portin arvo AND OR määräytyy sisääntulojen arvoista
Millaisia koneita voidaan rakentaa logiikkaportteja yhdistelemällä?
a b AND NOT OR a=b? AND NOT Porttien yhdistelmää kutsutaan piiriksi* Piirillä on sisääntuloja ja ulostuloja *) vaihtoehtoisesti: logiikkapiiri tai Boolen piiri
AND NOT NOT AND OR Piirin ulostulojen arvot määräytyvät portti portilta sisääntulojen arvoista
AND OR NOT NOT AND AND OR NOT NOT AND AND OR NOT NOT AND AND OR NOT NOT AND Piirin ulostulojen arvo vaihtelee sisääntulojen arvojen mukaan
Scala-ohjelmoijan tietokoneen- rakennussarja*? (Olio-ohjelmoinnin hengessä) NOT EI AND JA (johtoa) OR TAI (logiikkaportteja) *) Kurssin edetessä sarjaan tulee täydennyksiä
Ehkäpä jokainen portti on olio... g AND NOT g2 AND OR g5 NOT g3 g4... mitä muita olioita tarvitaan? (Mallinnettaessa luokkarakenne kannattaa pitää mahd. yksinkertaisena)
... tehdään lisäksi jokaisesta piirin sisääntulosta olio a b AND g NOT g2 AND OR g5 NOT g3 g4... tarvitaanko lisää luokkia?
a b AND g=and(a,b) g5= NOT g2=not(a) OR OR(g,g4) AND NOT g4=and(g2,g3) g3=not(b) Havainto: Jokainen logiikkaportti ottaa syötteensä piirin sisääntuloista tai muista logiikkaporteista Ehkäpä johtoja ja ulostuloja ei tarvitse erikseen mallintaa, ts. luokkarakenteeksi riittävät logiikkaportit ja sisääntulot
Rakennussarja Scala-pakkauksena package tinylog abstract class Gate() { def value: Boolean } // implemented by the extending classes Yhteinen abstrakti kantaluokka logiikkaporteille ja sisääntuloille
Logiikkaporttien toteutus: class NotGate(in: Gate) extends Gate() { def value =!in.value } class OrGate(in: Gate, in2: Gate) extends Gate() { def value = in.value in2.value } class AndGate(in: Gate, in2: Gate) extends Gate() { def value = in.value && in2.value } Sisääntulojen toteutus: class InputElement() extends Gate() { var v = false def set(s: Boolean) { v = s } def value = v } // default value is false
a b AND g NOT g2 AND OR g5 NOT g3 g4 Esimerkkipiiri rakennettuna Scalalla: import tinylog._ // Build a simple circuit val a = new InputElement() val b = new InputElement() val g = new AndGate(a,b) val g2 = new NotGate(a) val g3 = new NotGate(b) val g4 = new AndGate(g2,g3) val g5 = new OrGate(g,g4)
Rakennetun piirin käpistelyä Scala-konsolilla ja Toggler-työkalulla [demo]
Esimerkkipiiri rakennettuna Scalalla: import tinylog._ // Build a simple circuit val a = new InputElement() val b = new InputElement() val g = new AndGate(a,b) val g2 = new NotGate(a) val g3 = new NotGate(b) val g4 = new AndGate(g2,g3) val g5 = new OrGate(g,g4)... ja täsmälleen sama hieman napakammin: import tinylog._ // Build a simple circuit val a = new InputElement() val b = new InputElement() val g = a && b val g2 =!a val g3 =!b val g4 = g2 && g3 val g5 = g g4 Mitä ihmettä?
Omat operaattorit Scalassa Scala antaa ohjelmoijalle mahdollisuuden määritellä ohjelmoijan oma toteutus operaattoreille kuten! && + - * / % += ja niin edelleen abstract class Gate() { def value: Boolean def unary_!: Gate = new NotGate(this) def &&(that: Gate): Gate = new AndGate(this, that) def (that: Gate): Gate = new OrGate(this, that) } Yllä olemme määritelleet Gate-kantaluokassa, että luokan olio(i)ta käsiteltäessä jollakin operaattoreista! && Scala rakentaa operaattorin argument(e)ista syötettä ottavia EI JA TAI -porttiolioita
import tinylog._ // Build a simple circuit val a = new InputElement() val b = new InputElement() val g = a && b val g2 =!a val g3 =!b val g4 = g2 && g3 val g5 = g g4 a b NOT NOT g2 g3 AND AND g g4 OR g5... ja täsmälleen sama ilman Gate-olioiden g g2 g3 g4 nimeämistä: import tinylog._ // Build a simple circuit val a = new InputElement() val b = new InputElement() val g5 = (a && b) (!a &&!b) Näin napakalla syntaksilla on näppärä rakentaa isompiakin piirejä Scala huolehtii, että tarvittavat Gate-oliot syntyvät taustalla
Entäpä yhteenlaskukone?
Yhteenlaskukone kahdelle -bittiselle luvulle?
Kahden -bittisen a b (yhteenlaskettavat) luvun yhteenlasku summa ylivuoto ( carry )
a b NOT a b c s AND? OR c s
a b a b?? c s c s
a b a b c s? c s Määritelmä Spesifikaatio Mitä pitää tehdä Toteutus Implementaatio Miten tehdään
Totuustaulu ulostulolle c a b c a b Totuustaulu ulostulolle s a b s? c s
Synteesi totuustaulu(i)sta a b c (a && b) a b s (!a && b) (a &&!b) val c = (a && b) val s = (!a && b) (a &&!b)
(Kun teet tämän Gate-olioilla, Scala rakentaa sinulle Gate-olioista täsmälleen totuustaulun määräämällä tavalla toimivan piirin.) Synteesi totuustaulusta ) Kirjoita jokaiselle -riville JA-lauseke, joka on tosi täsmälleen kun sisääntulot ovat kyseisen rivin arvoissa s a b r (!s && a &&!b) (!s && a && b) ( s &&!a && b) ( s && a && b) 2) Yhdistä JA-lausekkeet isoksi TAI-lausekkeeksi val r = (!s && a &&!b) (!s && a && b) ( s &&!a && b) ( s && a && b)
Nyt osaamme rakentaa (Scalalla) koneita, jotka käsittelevät bittejä kuten haluamme Totuustaulu Kone (piiri) synteesi totuustaulusta
Tässäkö kaikki? Entäpä, sanokaamme, yhteenlaskukone kahdelle 64-bittiselle luvulle?
Piirissä on 2 x 64 = 28 sisääntuloa Montako riviä on totuustaulu(i)ssa?
342823669293846346337467437682456 (= 2 28 )
Kuinka rakentaa isompia ja monimutkaisempia piirejä?
) Scala-työkalujen napakoittaminen väylätason suunnitteluun 2) Parempi ymmärrys tehtävästä mihin konetta suunnitellaan (= yhteenlasku )
aa (4 bittiä) bb (4 bittiä) s (valitsee ulostuloon rr joko aa tai bb) (4-bittinen väylävalitsinpiiri) rr (4 bittiä) Monimutkaisemmat piirit käsittelevät yksittäisiä bittejä kuljettavien yksittäisten johtojen sijasta useampia bittejä kuljettavia väyliä
Kuinka toteuttaa väylät Scala-pakkaukseemme? Johtoja ei ole mallinnettu Scala-pakkauksemme luokkarakenteessa Päättäkäämme siis mallintaa väylä jonoksi Gate-olioita Scalan collection-kehikossa jonoilla on oma piirre (trait) Seq[T] Luokka Bus jatkakoon siis Seq[Gate] Bus-luokkaan on näppärä määritellä väylätason rakennusoperaattoreita & ~
Bus- luokan toteutus (*) // A custom collection for bus-level building import collection.seqlike import collection.mutable.{arraybuffer,builder} import collection.generic.canbuildfrom class Bus(gates: Seq[Gate]) extends Seq[Gate] with SeqLike[Gate,Bus] { def length = gates.length // the underlying Seq[Gate]-object implements Seq ops def apply(idx: Int) = gates.apply(idx) def apply(idxs: Seq[Int]) = new Bus(idxs.map(gates(_))) def iterator = gates.iterator override protected[this] def newbuilder: Builder[Gate, Bus] = Bus.newBuilder // get a builder from companion object def values = gates.map(_.value) } def &&(that: Gate) = new Bus(this.map(_ && that)) def (that: Gate) = new Bus(this.map(_ that)) def unary_~ = this.map(!_) def &(that: Bus) = new Bus((this zip that).map(x => x._ && x._2)) def (that: Bus) = new Bus((this zip that).map(x => x._ x._2)) object Bus { def apply(gates: Gate*) = new Bus(gates) // Bus(...) companion builder def newbuilder: Builder[Gate, Bus] = new ArrayBuffer[Gate] mapresult (s => new Bus(s)) // rely on mutables (ArrayBuffer & Builder) to build Bus objects in the // internals of the collections framework implicit def canbuildfrom: CanBuildFrom[Bus, Gate, Bus] = { new CanBuildFrom[Bus, Gate, Bus] { def apply(): Builder[Gate, Bus] = newbuilder def apply(from: Bus): Builder[Gate, Bus] = newbuilder } } def inputs(n: Int) = new Bus(( to n).map(x => Gate.input())) def falses(n: Int) = new Bus(( to n).map(x => Gate.False)) def trues(n: Int) = new Bus(( to n).map(x => Gate.True)) }
Väylien käyttö Bus-olio on jono Gate-olioita Scalan kaikki työkalut jonojen käsittelyyn (ks. scala.collection.seq metodit) ovat käytössäsi Bus-olioita käsitellessä Esimerkki Jos x on Bus-olio, tällöin x() on väylän x portti (=Gate-olio) järjestysnumerolla, x() on portti järjestynumerolla,..., ja x(x.length-) on portti järjestysnumerolla x.length-
Lisää esimerkkejä: Bus(g,g,g2) luo Bus-olion Gate-olioista g,g,g2 Bus.inputs(64) rakentaa 64 uutta InputElement-oliota ja luo näistä Bus-olion Jos x ja y ovat Bus-olioita, tällöin x ++ y palauttaa Bus-olion, jossa jonot x ja y on liitetty peräkkäin x y palauttaa Bus-olion, jonka portti järjestysnumerolla j on x(j) y(j), eli toisin sanoen, x y rakentaa väylän OrGate-olioita, joiden syötteet tulevat väylien x ja y vastinporteista. (Ks. kuvat lukemistossa!) Jos x on Bus-olio ja g on Gate-olio, tällöin x && g palauttaa Bus-olion, jonka portti järjestysnumerolla j on x(j) && g, eli toisin sanoen, x && g rakentaa väylän AndGate-olioita, joiden syötteet tulevat väylän x porteista ja kiinteästä portista g. (Ks. kuvat lukemistossa!)
aa bb s (aa &&!s) (bb && s) Ylläoleva piiri rakentuu tällä Scala-lausekkeella väylistä aa, bb ja portista s
Väylätason työkaluista lisää lukemistossa
64-bittisen yhteenlaskukoneen sisääntuloina on kaksi 64-bittistä väylää...
... mitä täsmälleen teemme laskiessamme kokonaislukuja yhteen?
-kantaisessa lukujärjestelmässä
2-kantaisessa lukujärjestelmässä
Idea: Rakennetaan piiri tekemään täsmälleen näin yksi merkitsevä numero kerrallaan
Jokainen merkitsevä numero määräytyy laskemalla yhteen kolme numeroa (jokainen joko tai )
Full adder a b c out c in s a b c in c out a b c in s
aa(3) aa(2) aa() aa() bb(3) bb(2) bb() bb() Yksi merkitsevä i = a c out b c in s numero kerrallaan, i = a c out b c in s (ts. Full Adder kerrallaan), i = 2 a c out b c in s kuten kynällä ja i = 3 a c out b c in s paperilla! ss(3) ss(2) ss() ss() 4-bittinen yhteenlaskukone
Ja sama Scalalla 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) } def buildripplecarryadder(aa: Bus, bb: Bus) = { var c: Gate = Gate.False // no initial carry var ss = new Array[Gate](aa.length) for(i <- until aa.length) { val cs = buildfulladder(aa(i),bb(i),c) c = cs._ // carry from bit i propagates to bit i+ ss(i) = cs._2 } } new Bus(ss) val n = 4 //... or n = 64 if we so want val aa = Bus.inputs(n) val bb = Bus.inputs(n) val ss = buildripplecarryadder(aa, bb) Toistuva alipiirirakenne on näppärä kapseloida funktioiksi, jotka kutsuttaessa rakentavat halutun alipiirin ja palauttavat rakennetun alipiirin ulostulon/ulostulot
Harjoituksissa Scala-pakkauksen laajentamista Koneenrakennusta Porttitason piirisuunnittelua Väylätason piirisuunnittelua Entäpä se ohjelmoitava kone? Ohjelmoitavuuden karkea esiaste: data ja ohjaus ( control ) ohjaussisääntulot ohjaavat mitä piiri tekee datasisääntuloille
Tehtävät gates porttien ja porttitason simulaation harjoittelua buswrite väyläkirjoitusyksikkö (kirjoittaa dataväylän valittuun rekisteriväylään) circuits väylätason suunnittelua (yhtäsuuruus, etumerkitön vertailu,..., kertolasku) shallowops (haastetehtävä) matalia piirejä (logaritminen syvyys) (väylä-tai, lisää yhdellä, yhteenlasku)