LL脳がscalaの勉強を始めたよ その65


Scalaコップ本の18章の続きをやっていきますよー、今回はデジタル回路シミュレーションのコードを書いていきますねー、この章はこれまでの復習内容がワンサカ出てくるので頑張ってやっていきましょうかね(´・ω・`)

デジタル回路のシミュレーション

前回書いたシミュレーションフレームワークを拡張して、配線・ANDゲート・ORゲート・インバータを生成するメソッドから構成されるデジタル回路DSL(BasicCircuitSimulationクラス)を実装していきますよー。まずは個々の要素をズラズラと書いていきますかねー

ディレイの定義

各ゲートのディレイはシミュレートする回路によって異なるので抽象メソッドとして定義しますね。なのでディレイに関する定義はサブクラスで行ないますよ。

def InverterDelay:Int
def AndGateDelay:Int
def OrGateDelay:Int

ちなみに各メソッド名の先頭が大文字なのは、メソッドを定数的に利用するからだそうです(´・ω・`)

Wireクラス

配線に関しては次の3つの基本操作をサポートするっぽいです

  • getSignal:Boolean [配線上の現在の信号を返す]
  • setSignal:(sig:Boolean) [配線上の信号をsigにする]
  • addAction:(p:Action) [配線のアクションに指定された作業項目pを加える]

addActionで付加されたアクションは追加時に一度実行されて、また配線の信号が変わるたびにも実行される(配線の信号が変わった際は配線が保持している全てのアクションが実行される)みたいです。

配線クラスの構成は次のようになりますねー

// 配線を表現するクラスです
class Wire{
  // 配線上の信号を保持する変数です
  private var sigVal = false
  // 配線上のアクションを保持する変数です
  private var actions:List[Action] = List()
  // 配線上の信号を取得します
  def getSignal = sigVal
  // 配線上の信号の値を変更しますよ
  def setSignal(s:Boolean) = {
    // 渡された信号が現在の信号と逆だった場合に処理を実行
    if(s != sigVal){
      // 信号値を変更
      sigVal = s
      // 保持しているアクション(で定義される関数)を全て実行
      // ちなみにactions.foreach(f => f())の略記法みたい
      actions.foreach(_ ())
    }
  }
  // 配線にアクションを追加しますよ
  def addAction(a:Action) = {
    actions = a :: actions
    a()
  }
}
inverterメソッド

信号を逆転するインバーターメソッドを考えますね。インバーターメソッドは信号を入力元配線オブジェクトから信号値を受け取って、反転した値で出力先の配線オブジェクトの信号を書き換えますね。

動作的にはインバーターメソッドの実行時に入力元配線オブジェクトに対して上記アクションを追加してやるような感じです。

def inverter(input:Wire, output:Wire) = {
 // インバーターのアクションを定義しますよ
  def invertAction(){
    // 入力元配線オブジェクトの信号を取得します
    val inputSig = input.getSignal
    // 別途定義されたディレイ後に出力先配線の信号を書き換えます
    afterDelay(InverterDelay){
      // 入力元信号の逆の値で出力先信号を書き換えます
      output.setSignal(!inputSig)
    }
  }
  // メソッド実行時(インバーター生成時)に入力元配線にアクションを追加します
  input.addAction(invertAction)
}
andGate / orGateメソッド

ANDゲート、ORゲートともにインバーターの時とほぼ同じような実装になりますね。
異なるのは入力線の数が2本になることと、実際のアクションがANDゲートでは論理積になってORゲートは論理和になることくらいですかね(´・ω・`)

んじゃANDゲートから書いてみますよ

def andGate(a1:Wire, a2:Wire, output:Wire) = {
  // ANDゲートアクションを定義しますよ
  def andAction() = {
    // 入力元配線から信号を取得しますよ 
    val a1Sig = a1.getSignal
    val a2Sig = a2.getSignal
    // ディレイ後に処理を実行します
    afterDelay(AndGateDelay){
      // 出力先配線に入力元の信号'sの論理積をセットします
      output.setSignal(a1Sig & a2Sig)
    }
  }
  // 入力元の2つの配線にアクションを追加します
  a1.addAction(andAction)
  a2.addAction(andAction)
}

ORゲートも実装してみますよ

def orGate(o1:Wire, o2:Wire, output:Wire){
  // ORゲートアクションを定義します
  def orAction(){
    // 入力元の配線'sから信号を取得します
    val o1Sig = o1.getSignal
    val o2Sig = o2.getSignal
    // ディレイ後に処理を実行します
    afterDelay(OrGateDelay){
      // 出力先配線に入力した信号の論理和をセットします
      output.setSignal(o1Sig | o2Sig)
    }
  }
  // 入力元の配線にORゲートアクションを追加します
  o1.addAction(orAction)
  o2.addAction(orAction)
}

ANDゲート、ORゲートともに2本の入力元配線にアクションを追加しているのは入力元が変わった際に出力を再計算する必要があるからだそうです(´・ω・`)

シミューレーションの出力

シミュレーターを実行する際は配線上の信号の変化をチェックする手段がないと何が起こっているかさっぱりわからなくなるので、プローブ(探測器)を用意してやります。プローブを生成するメソッドはこんな感じですかねー

def probe(name:String, wire:Wire){
  // 配線上の信号を出力するプローブアクションを定義します
  def probeAction(){
    // プローブ名と信号を表示しますね
    println(name + " " + currentTime + " 受け取った値は " + wire.getSignal)
  }
  // 配線にアクションを追加しますよ
  wire.addAction(probeAction)
}
んじゃ実装しますよ

以上の構成要素を組み合わせたコードは以下のようになりますねー

// パッケージ宣言です
package com.test.simulation
// Simulationクラスを拡張していきます
abstract class BasicCircuitSimulation extends Simulation {
  // 各ゲートのディレイはサブクラスで定義しますね
  def InverterDelay:Int
  def AndGateDelay:Int
  def OrGateDelay:Int
  
  // 配線を表現する入れ子クラスです
  class Wire{
    private var sigVal = false
    private var actions:List[Action] = List()
    def getSignal = sigVal
    def setSignal(s:Boolean) = {
      if(s != sigVal){
        sigVal = s
        actions foreach (_ ())
      }
    }
    def addAction(a:Action) = {
      actions = a :: actions
      a()
    }
  }
  
  // インバーターを生成するメソッドです
  def inverter(input:Wire, output:Wire) = {
    def invertAction(){
      val inputSig = input.getSignal
      afterDelay(InverterDelay){
        output.setSignal(!inputSig)
      }
    }
    input.addAction(invertAction)
  }
  
  // ANDゲートを生成するメソッドです
  def andGate(a1:Wire, a2:Wire, output:Wire) = {
    def andAction() = {
      val a1Sig = a1.getSignal
      val a2Sig = a2.getSignal
      afterDelay(AndGateDelay){
        output.setSignal(a1Sig & a2Sig)
      }
    }
    a1.addAction(andAction)
    a2.addAction(andAction)
  }
  
  // ORゲートを生成するメソッドです
  def orGate(o1:Wire, o2:Wire, output:Wire){
    def orAction(){
      val o1Sig = o1.getSignal
      val o2Sig = o2.getSignal
      afterDelay(OrGateDelay){
        output.setSignal(o1Sig | o2Sig)
      }
    }
    o1.addAction(orAction)
    o2.addAction(orAction)
  }
  
  // プローブを生成するメソッドです
  def probe(name:String, wire:Wire){
    def probeAction(){
      println(name + " " + currentTime + " 受け取った値は " + wire.getSignal)
    }
    wire.addAction(probeAction)
  }
}
シミュレータの実行

先程定義したBasicCircuitSimulationを更に拡張して半加算器と全加算器をシミュレートする抽象クラスCircuitSimulationを実装してみますよ。半加算器と全加算器のDSL上での表現は前々回にやったやつです。

package com.test.simulation
abstract class CircuitSimulation extends BasicCircuitSimulation {
  // 半加算器を生成するメソッドです
  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)
  }
}

んじゃ、とりあえず実行してみますよー、これまで定義したクラスをscalacでコンパイルしたあとに対話コンソールで実行してみますね

// これまで実装したパッケージをインポートしますよ
scala> import com.test.simulation._
import com.test.simulation._

// 実際に動作させるためにディレイの値を定義するオブジェクトを定義します
// これによって(CircuitSimulation, BasicCircuitSimulatio, Simulation)の
// 各メンバーにアクセスすることができます(`・ω・´)
scala> object MySimulation extends CircuitSimulation {
     |   def InverterDelay = 1
     |   def AndGateDelay = 3 
     |   def OrGateDelay = 5 
     | }
defined module MySimulation

// いちいちMySimulation.hoge()とか書くのがめんどいので
// インポートしますよ(´・ω・`)
scala> import MySimulation._
import MySimulation._

//// ではシミュレーションを開始します
//// 今回は半加算器のシミュレーションです

// まずは配線を生成しますよ
scala> val input1, input2, sum, carry = new Wire
input1: MySimulation.Wire = com.test.simulation.BasicCircuitSimulation$Wire@a99013
input2: MySimulation.Wire = com.test.simulation.BasicCircuitSimulation$Wire@31db04
sum: MySimulation.Wire = com.test.simulation.BasicCircuitSimulation$Wire@1220fd1
carry: MySimulation.Wire = com.test.simulation.BasicCircuitSimulation$Wire@13c952f

// 半加算器の出力先sumとcarryにプローブをくっつけますね
scala> probe("sum", sum)
sum 0 受け取った値は false
scala> probe("carry", carry)
carry 0 受け取った値は false

// 半加算器を生成します
scala> halfAdder(input1, input2, sum, carry)

// 入力元の一本の配線の信号をfalse → trueに変えます
// 入力は true, falseになってます
scala> input1.setSignal(true)

// シミュレーション開始です 
scala> run()
*** シミュレーション開始, time = 0 ***
// sumの値がfalse → trueに変更されました
// carryの値はfalse → falseなので出力されません
sum 8 受け取った値は true
scala> run()
*** シミュレーション開始, time = 8 ***
// 全ての作業が消化されたのでシミュレーションは終了です


// もう一本の配線の信号をfalse → trueに変えます
// これで入力はtrue, trueになります
scala> input2.setSignal(true)

// シミュレーションをもう一度行ないます
scala> run()                 
*** シミュレーション開始, time = 8 ***
// carryの値がfalse→trueに変更されました
carry 11 受け取った値は true
// sumの値がtrue→falseに変更されました
sum 15 受け取った値は false

scala> run()
*** シミュレーション開始, time = 15 ***
// 全ての作業が消化されたのでシミュレーションは終了です

せっかく作ったのでもうちょっといじくりまわしたいけど…時間切れで断念です(´・ω・`)

いじょー

デジタル回路のシミュレーターが実装できたのでこの章は終了ですなー。ミュータブル状態と高階関数の併用で物理的なシミュレーションをDSLライブラリーの定義と共にやってみましたよ

次回は19章の型のパラメータ化に入っていきますよー