digital circuit simulator (7)

で、キタナいソレを晒しとくのも微妙なんで、ちょっとだけ見直した分をサラしてみる。
えーと、まず本体。add2agenda が微妙にモディファイ。

class Segment
  attr_accessor :t, :q
  def initialize(time, queue)
    @t = time
    @q = queue
  end
end

class Agenda
  attr_accessor :currentTime, :segments

  def initialize
    @currentTime = 0
    @segments = Array.new
  end

  def empty?
    @segments.size == 0
  end

  def firstAgenda
    seg = @segments.first
    @currentTime = seg.t
    seg.q.first
  end

  def removeFirst
    @segments.first.q.shift
    if @segments.first.q.size == 0
      @segments.shift
    end
  end

  def add2agenda(time, p)
    if empty? || @segments.first.t > time
      @segments.unshift(Segment.new(time, Array[p]))
    else
      @segments = add2segments(time, p, @segments)
    end
  end

  def add2segments(time, p, segments)
    if segments.first.t == time
      segments.first.q.push p
      segments
    else
      rest = segments.last(segments.size - 1)
      if rest.empty? || rest.first.t > time
        ret = Array[segments.first, Segment.new(time, Array[p])]
        rest.each do |obj|
          ret.push(obj)
        end
        ret
      else
        add2segments(time, p, rest).unshift(segments.first)
      end
    end
  end

  private :add2segments

end

class Wire
  attr_accessor :value
  attr_reader :action

  def initialize
    @value = 0
    @action = Array.new
  end

  def value=(v)
    if @value != v
      @value = v
      @action.each do |i|
        i.call
      end
    end
  end

  def addAction(p)
    @action.unshift(p)
    p.call
  end
end

class Inverter
  def initialize(input, output, agenda)
    @i = input; @o = output; @ag = agenda
    @d = $inverterDelay
    p = Proc.new { newVal = logicalNot(@i.value);
      afterDelay(@d, lambda{@o.value = newVal}, @ag) }
    @i.addAction p
  end

  def logicalNot(signal)
    if signal == 0
      1
    else
      0
    end
  end
end

class AndGate
  def initialize(input1, input2, output, agenda)
    @i1 = input1; @i2 = input2; @o = output; @d = $andDelay; @ag = agenda
    p = Proc.new { newVal = logicalAnd(@i1.value, @i2.value);
      afterDelay(@d, lambda{@o.value = newVal}, @ag) }
    @i1.addAction p
    @i2.addAction p
  end

  def logicalAnd(s1, s2)
    if s1 == 1 && s2 == 1
      1
    else
      0
    end
  end
end

class OrGate
  def initialize(input1, input2, output, agenda)
    @i1 = input1; @i2 = input2; @o = output; @d = $orDelay; @ag = agenda
    p = Proc.new { newVal = logicalOr(@i1.value, @i2.value);
      afterDelay(@d, lambda{@o.value = newVal}, @ag) }
    @i1.addAction p
    @i2.addAction p
  end

  def logicalOr(s1, s2)
    if s1 == 1 || s2 == 1
      1
    else
      0
    end
  end
end

class HalfAdder
  attr_reader :d, :e
  def initialize(agenda, a, b, s, c)
    @a = a; @b = b; @s = s; @c = c; @ag = agenda
    @d = Wire.new
    @e = Wire.new
    @gates = Array[OrGate.new(@a, @b, @d, @ag),
                   AndGate.new(@a, @b, @c, @ag),
                   Inverter.new(@c, @e, @ag),
                   AndGate.new(@d, @e, @s, @ag)]
  end
end

class FullAdder
  def initialize(a, b, cIn, sum, cOut)
    @a = a; @b = b; @cIn = cIn; @sum = sum; @cOut = cOut;
    @s = Wire.new
    @c1 = Wire.new
    @c2 = Wire.new
    @gates = Array[HalfAdder.new(@b, @cIn, @s, @c1),
                   HalfAdder.new(@a, @s, @sum, @c2),
                   OrGate.new(@c1, @c2, @cOut)]
  end
end


class Sim
  attr_accessor :ag
  
  def initialize
    $inverterDelay = 2
    $andDelay = 3
    $orDelay = 5
    @ag = Agenda.new
  end

  def simulate
    @input1 = Wire.new
    @input2 = Wire.new
    @sum = Wire.new
    @carry = Wire.new
    probe("carry", @carry)
    probe("sum", @sum)
    @haobj = HalfAdder.new(@ag, @input1, @input2, @sum, @carry)
    @input1.value = 1
    propagate
    @input2.value = 1
    propagate
  end

  def probe(name, wire)
    wire.addAction(lambda {print name; print " "; print @ag.currentTime;
                     print " New-value = "; print wire.value; puts ""})
  end

  def propagate
    if !@ag.empty?
      @ag.firstAgenda.call
      @ag.removeFirst
      propagate
    end
  end
end

def afterDelay(delay, p, ag)
  ag.add2agenda(delay + ag.currentTime, p)
end

もっと良さげな書き方できるんでしょうが、ruby な書き方とゆーか語彙が (以下略
しかし、java で上記を、なソレは実装可能なのだろうか。

試験

蛇足な上に微妙ですが試験も。ちなみに require してる test2 ですが、本体のファイル名が test2.rb という名前によります。特に意味なし。

Segment
require 'test2'

class TestSegment < Test::Unit::TestCase

 def setup
   @seg = Segment.new(1, 2)
 end

 def test_segment_initialize
   assert_not_nil @seg
 end

 def test_segment_accessor
   assert_equal 1, @seg.t
   assert_equal 2, @seg.q
   @seg.t = 2; @seg.q = 3
   assert_equal 2, @seg.t
   assert_equal 3, @seg.q
 end

end
Agenda
require 'test2'


class TestAgenda < Test::Unit::TestCase

 def setup
   @ag = Agenda.new
 end

 def test_agenda_initialize
   assert_not_nil @ag
 end

 def test_agenda_accessor
   assert_equal 0, @ag.currentTime
   assert_not_nil @ag.segments
   assert_instance_of Array, @ag.segments
 end

 def test_agenda_setSegments
   seg = Segment.new 1, 2
   @ag.segments.push seg
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.last.t
   assert_equal 2, @ag.segments.last.q
   seg = Segment.new 3, 4
   @ag.segments.push seg
   assert_equal 2, @ag.segments.size
   assert_equal 1, @ag.segments.first.t
   assert_equal 2, @ag.segments.first.q
   assert_equal 3, @ag.segments.last.t
   assert_equal 4, @ag.segments.last.q
 end

 def test_agenda_empty?
   assert @ag.empty?
   seg = Segment.new 1, 2
   @ag.segments.push seg
   assert !@ag.empty?
 end

 def test_agenda_firstAgenda
   ary = Array[1, 2]
   seg = Segment.new 5, ary
   @ag.segments.push seg
   ary = Array[3, 4]
   seg = Segment.new 8, ary
   @ag.segments.push seg
   f = @ag.firstAgenda
   assert_equal 1, f
   assert_equal 5, @ag.currentTime
 end

 def test_agenda_removeFirst
   ary = Array[1, 2]
   seg = Segment.new 5, ary
   @ag.segments.push seg
   ary = Array[3, 4]
   seg = Segment.new 8, ary
   @ag.segments.push seg
   @ag.removeFirst
   assert_equal 2, @ag.segments.first.q.first
   @ag.removeFirst
   assert_equal 3, @ag.segments.first.q.first
   @ag.removeFirst
   assert_equal 4, @ag.segments.first.q.first
   @ag.removeFirst
   assert @ag.empty?
 end

 def test_agenda_add2segments
   @ag.segments.push(Segment.new(5, Array[1, 2]))
   @ag.segments.push(Segment.new(8, Array[3, 4]))

   @ag.add2agenda(1, 5)

   assert_equal 1, @ag.segments.first.t
   assert_equal [5], @ag.segments.first.q

   assert_equal 3, @ag.segments.size
   assert_equal 5, @ag.segments[1].t
   assert_equal 8, @ag.segments[2].t
   assert_equal [1, 2], @ag.segments[1].q
   assert_equal [3, 4], @ag.segments[2].q

   @ag.add2agenda(5, 3)

   assert_equal 3, @ag.segments.size
   assert_equal 1, @ag.segments[0].t
   assert_equal 5, @ag.segments[1].t
   assert_equal 8, @ag.segments[2].t
   assert_equal [5], @ag.segments[0].q
   assert_equal [1, 2, 3], @ag.segments[1].q
   assert_equal [3, 4], @ag.segments[2].q

   @ag.add2agenda(6, 6)
   assert_equal 4, @ag.segments.size
   assert_equal 1, @ag.segments[0].t
   assert_equal 5, @ag.segments[1].t
   assert_equal 6, @ag.segments[2].t
   assert_equal 8, @ag.segments[3].t
   assert_equal [5], @ag.segments[0].q
   assert_equal [1, 2, 3], @ag.segments[1].q
   assert_equal [6], @ag.segments[2].q
   assert_equal [3, 4], @ag.segments[3].q

   @ag.add2agenda(10, 10)
   assert_equal 5, @ag.segments.size
   assert_equal 1, @ag.segments[0].t
   assert_equal 5, @ag.segments[1].t
   assert_equal 6, @ag.segments[2].t
   assert_equal 8, @ag.segments[3].t
   assert_equal 10, @ag.segments[4].t
   assert_equal [5], @ag.segments[0].q
   assert_equal [1, 2, 3], @ag.segments[1].q
   assert_equal [6], @ag.segments[2].q
   assert_equal [3, 4], @ag.segments[3].q
   assert_equal [10], @ag.segments[4].q

 end

 def test_agenda_add2agenda_first
   @ag.add2agenda 5, 1
   assert_equal 5, @ag.segments.first.t
   assert_equal 1, @ag.segments.first.q.first
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.add2agenda 2, 3
   assert_equal 2, @ag.segments.first.t
   assert_equal 5, @ag.segments.last.t
   assert_equal 3, @ag.segments.first.q.first
   assert_equal 1, @ag.segments.first.q.size
   assert_equal 1, @ag.segments.last.q.first
   assert_equal 1, @ag.segments.last.q.size

   @ag.add2agenda 5, 2
   assert_equal 1, @ag.segments.last.q.first
   assert_equal 2, @ag.segments.last.q.last

   assert_equal 2, @ag.segments.first.t
   @ag.add2agenda 3, 6
   assert_equal 2, @ag.segments[0].t
   assert_equal 3, @ag.segments[1].t
   assert_equal 5, @ag.segments[2].t
   assert_equal 3, @ag.segments[0].q.first
   assert_equal 1, @ag.segments[0].q.size
   assert_equal 6, @ag.segments[1].q.first
   assert_equal 1, @ag.segments[1].q.size
   assert_equal 1, @ag.segments[2].q.first
   assert_equal 2, @ag.segments[2].q.size
   assert_equal 2, @ag.segments[2].q.last
 end

end
Wire
require 'test2'


class TestWire < Test::Unit::TestCase

 def setup
   @wire = Wire.new
 end

 def test_wire_initialize
   assert_not_nil @wire
 end

 def test_wire_getValue
   assert_equal 0, @wire.value
 end

 def test_wire_addAction
   assert_equal "call", @wire.addAction(lambda{"call"})
 end

 def test_wire_setValue
   assert_equal 0, @wire.value
   p = lambda{"call"}
   @wire.addAction p
   @wire.value = 1
   assert_equal 1, @wire.value
   assert_equal p, @wire.action.first # ...
 end

end
Inverter
require 'test2'


class TestInverter < Test::Unit::TestCase

 def setup
   $inverterDelay = 2
   @ag = Agenda.new
   @i = Wire.new
   @o = Wire.new
   @obj = Inverter.new @i, @o, @ag
 end

 def test_inverter_initialize
   assert_not_nil @obj
 end

 def test_inverter_action
   @ag.firstAgenda.call
   assert_equal 1, @o.value
   @ag.removeFirst
   assert @ag.empty?

   @i.value = 1
   assert_equal 1, @ag.segments.size
   @ag.firstAgenda.call
   assert_equal 0, @o.value
   @ag.removeFirst
   assert @ag.empty?
 end

end
AndGate
require 'test2'


class TestAndGate < Test::Unit::TestCase

 def setup
   $andDelay = 3
   @ag = Agenda.new
   @i1 = Wire.new
   @i2 = Wire.new
   @o = Wire.new
   @obj = AndGate.new @i1, @i2, @o, @ag
 end

 def test_andgate_initialize
   assert_not_nil @obj
   assert_not_nil @i1
   assert_not_nil @i2
   assert_not_nil @ag
   assert_not_nil @o

   assert_instance_of AndGate, @obj
   assert_instance_of Wire, @i1
   assert_instance_of Wire, @i2
   assert_instance_of Wire, @o
   assert_instance_of Agenda, @ag
 end

 def test_andgate_action
   assert_equal 1, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 3, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   @ag.firstAgenda.call
   assert_equal 3, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i1.value = 1
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 6, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i2.value = 1
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 9, @ag.currentTime
   assert_equal 1, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i1.value = 0
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 12, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?
 end

end
OrGate
require 'test2'


class TestOrGate < Test::Unit::TestCase

 def setup
   $orDelay = 5
   @ag = Agenda.new
   @i1 = Wire.new
   @i2 = Wire.new
   @o = Wire.new
   @obj = OrGate.new @i1, @i2, @o, @ag
 end

 def test_orgate_initialize
   assert_not_nil @obj
   assert_not_nil @ag
   assert_not_nil @i1
   assert_not_nil @i2
   assert_not_nil @o

   assert_instance_of OrGate, @obj
   assert_instance_of Wire, @i1
   assert_instance_of Wire, @i2
   assert_instance_of Wire, @o
   assert_instance_of Agenda, @ag
 end

 def test_orgate_action
   assert_equal 1, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 5, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   @ag.firstAgenda.call
   assert_equal 5, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i1.value = 1
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 10, @ag.currentTime
   assert_equal 1, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i1.value = 0
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 15, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i2.value = 1
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 20, @ag.currentTime
   assert_equal 1, @o.value
   @ag.removeFirst

   assert @ag.empty?

   @i2.value = 0
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   assert_equal 25, @ag.currentTime
   assert_equal 0, @o.value
   @ag.removeFirst

   assert @ag.empty?
 end

end
HalfAdder
require 'test2'


class TestHalfAdderGate < Test::Unit::TestCase

 def setup
   $inverterDelay = 2
   $andDelay = 3
   $orDelay = 5
   @ag = Agenda.new
   @i1 = Wire.new
   @i2 = Wire.new
   @s = Wire.new
   @c = Wire.new
   @obj = HalfAdder.new @ag, @i1, @i2, @s, @c
 end

 def test_halfadder_initialize
   $inverterDelay = 2
   $andDelay = 3
   $orDelay = 5
   assert_not_nil @obj
   assert_not_nil @ag
   assert_not_nil @i1
   assert_not_nil @i2
   assert_not_nil @s
   assert_not_nil @c

   assert_instance_of HalfAdder, @obj
   assert_instance_of Wire, @i1
   assert_instance_of Wire, @i2
   assert_instance_of Wire, @s
   assert_instance_of Wire, @c
   assert_instance_of Agenda, @ag
 end

 def test_halfadder_init
   assert_equal 0, @ag.currentTime

   assert_equal 3, @ag.segments.size
   assert_equal 1, @ag.segments[0].q.size
   assert_equal 4, @ag.segments[1].q.size
   assert_equal 2, @ag.segments[2].q.size

   assert_equal 0, @obj.e.value

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 1, @obj.e.value

   assert_equal 2, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 4, @ag.segments[0].q.size
   assert_equal 3, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 3, @ag.segments.first.q.size
   assert_equal 3, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size
   assert_equal 3, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size
   assert_equal 3, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 3, @ag.segments.first.q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 0, @ag.segments.size

   assert_equal 0, @i1.value
   assert_equal 0, @i2.value
   assert_equal 0, @s.value
   assert_equal 0, @c.value
 end

 def propagate_sim
   if !@ag.empty?
     @ag.firstAgenda.call
     @ag.removeFirst
     propagate_sim
   end
 end

 def test_halfadder_action_after_initialize
   assert_equal 3, @ag.segments.size

   propagate_sim

   assert_equal 5, @ag.currentTime
   assert_equal 0, @ag.segments.size

   assert_equal 0, @i1.value
   assert_equal 0, @i2.value
   assert_equal 0, @s.value
   assert_equal 0, @c.value
 end

 def test_halfadder_action_propagate
   propagate_sim

   assert_equal 5, @ag.currentTime

   @i1.value = 1

   propagate_sim

   assert_equal 13, @ag.currentTime
   assert_equal 0, @ag.segments.size

   assert_equal 1, @i1.value
   assert_equal 0, @i2.value
   assert_equal 1, @s.value
   assert_equal 0, @c.value
 end

 def test_halfadder_action
   assert_equal 3, @ag.segments.size
   assert_equal 1, @ag.segments[0].q.size
   assert_equal 4, @ag.segments[1].q.size
   assert_equal 2, @ag.segments[2].q.size

   @i1.value = 1
   assert_equal 3, @ag.segments.size
   assert_equal 1, @ag.segments[0].q.size
   assert_equal 5, @ag.segments[1].q.size
   assert_equal 3, @ag.segments[2].q.size

   assert_equal 0, @obj.d.value
   assert_equal 0, @obj.e.value

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 0, @obj.d.value
   assert_equal 1, @obj.e.value

   assert_equal 2, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 5, @ag.segments[0].q.size  # 3
   assert_equal 4, @ag.segments[1].q.size  # 5 (include and)

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 4, @ag.segments.first.q.size
   assert_equal 4, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 3, @ag.segments.first.q.size
   assert_equal 4, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size
   assert_equal 4, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 3, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size
   assert_equal 4, @ag.segments[1].q.size

   @ag.firstAgenda.call
   @ag.removeFirst
   
   assert_equal 3, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 4, @ag.segments.first.q.size # doubt

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 3, @ag.segments.first.q.size

   assert_equal 0, @obj.d.value
   assert_equal 1, @obj.e.value

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 2, @ag.segments.first.q.size

   assert_equal 0, @obj.d.value
   assert_equal 1, @obj.e.value

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 1, @obj.d.value
   assert_equal 1, @obj.e.value

   assert_equal 5, @ag.currentTime
   assert_equal 2, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 5, @ag.currentTime
   assert_equal 1, @ag.segments.size
   assert_equal 1, @ag.segments.first.q.size

   @ag.firstAgenda.call
   @ag.removeFirst

   assert_equal 8, @ag.currentTime
   assert_equal 0, @ag.segments.size

   assert @ag.empty?

   assert_equal 1, @i1.value
   assert_equal 0, @i2.value
   assert_equal 1, @s.value
   assert_equal 0, @c.value
 end

end

Sim については試験を作ってません。FullAdder も動作確認してません。威張って出せるナニではない事は十分承知してますが (しかもバグってる可能性大)、とりあえずサラしてみる、という事を御理解頂ければ幸いッス。

蛇足

test/run-test.rb も以下に貼っておく

require 'test/unit'

test_file = "test/test_*.rb"

$:.unshift(File.join(File.expand_path("."), "lib"))
$:.unshift(File.join(File.expand_path("."), "test"))  

Dir.glob(test_file) do |file|
  require file.sub(/\.rb$/, '')
end