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章...頑張るぞっと(´・ω・`)