LL脳がscalaの勉強を始めたよ その108
Scalaコップ本の30章のアクターと並行プログラミングの仕上げをやっていきますよー。今回は大規模なサンプルとしての並列離散イベントシミュレーション開発として、前回作成したフレームワークを元にシミュレーション回路の実装に入っていきますね(`・ω・´)
大規模なサンプル:並列離散イベントシミュレーション(続き)
前回までに実装したシミュレーションフレームワークを使って、実際の回路の実装を行っていきますデス
回路シミュレーションの実装
それでは回路実装用フレームワークとしてCircuitクラスを実装していきますデス。Circuitクラスは配線やゲート等をメンバーとして持つような構造になるのですが、とりあえずこんな感じになりますねー
class Circuit { val clock = new Clock //// 配線とゲートでやり取りするメッセージを定義 // ゲートから配線へのシグナルの変更メッセージ case class SetSignal(sig:Boolean) // 配線からゲートへのシグナル変更メッセージ case class SignalChanged(wire:Wire, sig:Boolean) //// シミュレーション上のディレイ定数 // すぐに調整できるように値を外出し val WireDelay = 1 val InverterDelay = 2 val OrGateDelay = 3 val AndGateDelay = 3 //// Wireクラスを定義 // Simulantをミックスインしてアクターとして実装しマス class Wire(name:String, init:Boolean) extends Simulant { // パラメータ省略で呼び出されたときのデフォルト値を設定 def this(name:String){ this(name, false) } def this(){ this("unnamed") } // Simulantをミックスインしているので使用するクロックを指定します val clock = Circuit.this.clock // クロックに個の部品オブジェクトを追加 clock.add(this) // 各種内部変数を定義シマス private var sigVal = init private var observers: List[Actor] = List() // シミュレーション中の応答メッセージを定義します def handleSimMessage(msg:Any){ msg match { // 受け取るのは信号変更のゲートからのSetSignalメッセージのみデス case SetSignal(s) => // 現在と異なる信号が渡されたときのみ処理します if(s != sigVal){ // 内部の信号値を変更します sigVal = s // この配線が入力元として接続されたゲートに // 信号が変更されたことを周知します signalObservers() } } } // 信号変更の周知をClock経由で行います def signalObservers(){ // Clock経由で関連するゲートに信号が変更されたコトを伝えます for(obs <- observers) clock ! AfterDelay( WireDelay, SignalChanged(this, sigVal), obs) } // シミュレーション開始時に関連ゲートに初期状態を送ります override def simStarting() { signalObservers() } // 新しいゲートに接続するためのメソッド def addObserver(obs:Actor){ observers = obs :: observers } // 配線名を出力するためのメソッドです override def toString = "Wire(" + name + ")" } // 論理回路用に信号が発生しないダミー配線を定義 private object DummyWire extends Wire("dummy") //// ゲートクラスを抽象クラスとして定義 // こちらもSimulantをミックスインしてアクターとして定義デス abstract class Gate(in1:Wire, in2:Wire, out:Wire) extends Simulant { //// 各ゲートオブジェクトで実装する各種メソッド // 入力に基づいて出力処理を実際に行うメソッドデス def computeOutput(s1:Boolean, s2:Boolean):Boolean // ゲートの種類に応じて遅延時間を設定します val delay:Int // Simulantをミックスインしたのでクロックを指定します val clock = Circuit.this.clock // この部品オブジェクトをClockに追加します clock.add(this) // 対応する2本の配線をこのゲートの入力線として接続します in1.addObserver(this) in2.addObserver(this) // 出力線への結果を初期化します // 入力線の値が変化した場合に再計算されます var s1, s2 = false // シミュレーションメッセージへの応答定義 def handleSimMessage(msg:Any){ msg match { // 配線からのシグナル変更メッセージが来た場合に処理開始 case SignalChanged(w, sig) => // 各配線結果に応じて出力先の配線の信号を変更 // 初期ゲートでは入力線1と出力線1、入力線2と出力線2を直結している if(w == in1) s1 = sig if(w == in2) s2 = sig // 出力線に対してClock経由で処理結果を送信 clock ! AfterDelay(delay, SetSignal(computeOutput(s1, s2)), out) } } } //// 抽象ゲートクラスを利用して具体的なゲートを定義 // ORゲートの定義 def orGate(in1:Wire, in2:Wire, output:Wire) = new Gate(in1, in2, output){ val delay = OrGateDelay def computeOutput(s1:Boolean, s2:Boolean) = s1 || s2 } // ANDゲートの定義 def andGate(in1:Wire, in2:Wire, output:Wire) = new Gate(in1, in2, output){ val delay = AndGateDelay def computeOutput(s1:Boolean, s2:Boolean) = s1 && s2 } // NOTゲートの定義 def inverter(input:Wire, output:Wire) = // 信号変換だけなのでダミー配線を利用 new Gate(input, DummyWire, output){ val delay = InverterDelay def computeOutput(s1:Boolean, ignored:Boolean) = !s1 } //// 回路上の信号の変化を監視するためのプローブユーティリティーを実装 // Simulantをミックスインすることでアクターとして定義 def probe(wire:Wire) = new Simulant{ // Simulant用のClockを指定 val clock = Circuit.this.clock // プローブを部品オブジェクトとして追加 clock.add(this) // 接続先の配線を登録 wire.addObserver(this) // 配線上の信号が変化したらその旨を表示 def handleSimMessage(msg:Any){ msg match { case SignalChanged(w, s) => println("signal " + w + " changed to " + s) } } } //// シミュレーションを開始 // clockにstartメッセージを送信 def start() { clock ! Start} }
回路の実装は以上ですねー。ついでに18章でも取り扱った半加算器と全加算器をトレイトとして実装してみます(`・ω・´)内容は18章とほとんど同じ内容です。
// トレイトとして加算器を定義しますねー trait Adders extends Circuit { // 半加算器を定義します def halfAdder(a:Wire, b:Wire, s:Wire, c:Wire){ val d, e = new Wire orGate(a, b, d) andGate(a, b, c) inverter(c, e) andGate(d,e,s) } // 半加算器を利用して全加算器を定義してやります def fullAdder(a:Wire, b:Wire, cin:Wire, sum:Wire, cout:Wire){ val s, c1, c2 = new Wire halfAdder(a, cin, s, c1) halfAdder(b, s, sum, c2) orGate(c1, c2, cout) } }
上記加算器'sを実際に使用する場合は次のようにインスタンス生成時にトレイトをミックスインしますデス
val circuit = new Circuit with Adders
このようにトレイトとして実装することで回路シミュレータにコンポーネントという形で付け加えることができるわけですね(`・ω・´)またAdders以外のコンポーネントを複数作って、ソレゾレを組み合わせた回路シミュレータも作れるようになるわけデスね
イメージとしてはこんな感じですかね?
val circuit = new Circuit with Adders // (架空の)コンポーネントを追加してやりますよ with Multiplexer with FlipFlops with MultiCoreprocessors
全体のテスト
それではシミュレーションの準備は完了したので、実際のシミュレーション用回路を記述してみます。内容的には全加算器のシミュレーションですかね
object Demo{ def main(args:Array[String]){ // Circuitオブジェクトを生成 val circuit = new Circuit with Adders // 各部品を呼び出しやすいようにimport import circuit._ // 入力配線を定義 val ain = new Wire("ain", true) val bin = new Wire("bin", false) val cin = new Wire("cin", true) // 出力配線を定義 val sout = new Wire("sout") val cout = new Wire("cout") // 信号監視用のプローブを定義 probe(ain) probe(bin) probe(cin) probe(sout) probe(cout) // 全加算器を定義 fullAdder(ain, bin, cin, sout, cout) // シミュレーションをスタート circuit.start() println("DemoStart") } }
それでは実行してみますー、本来であればコンパイルして云々なんでしょうが…めんどくさがって対話型シェルでむりくり動かしてみますデス(´・ω・`)
scala> Demo.main(Array()) DemoStart scala> Advancing to time1 Advancing to time2 signal Wire(cout) changed to false signal Wire(bin) changed to false signal Wire(cin) changed to true signal Wire(ain) changed to true signal Wire(sout) changed to false Advancing to time3 Advancing to time4 Advancing to time5 Advancing to time6 Advancing to time7 Advancing to time8 Advancing to time9 Advancing to time10 signal Wire(cout) changed to true Advancing to time11 Advancing to time12 Advancing to time13 Advancing to time14 Advancing to time15 Advancing to time16 Advancing to time17 Advancing to time18 signal Wire(sout) changed to true Advancing to time19 Advancing to time20 Advancing to time21 signal Wire(sout) changed to false ** Agenda empty. Clock exiting at time 21.
おお!無事動きましたー!
いじょー
アクター関連はひとまず終わりです。あとは地道にいろんな具体的サンプルを作って覚えるのが良さそうですな(`・ω・´)次回はパーサーコンビネーターに進みますよー
残りはあと3章...頑張るぞっと(´・ω・`)