ハードウェアシミュレータ
久しぶりの更新。
最近なんとなくデジタル回路などを勉強してて、しかし目的と手段が入れ替わっているので(いつものこと)、勉強した結果として何をするかという目標は無い。
そのうちDE0とかのFPGA学習ボードを買ってなんか作ってみるかもしれないし、何もしないかもしれない。わからん。
まあ、とりあえず何かやってみよう、という感じで、Rubyでハードウェアシミュレータを作ってみることにした。ひょっとしたらそのうちこれの上でCPUが動くようになるかもしれない。ならないかもしれない。わからん。
ハードウェアのシミュレータと言ってもどこまでシミュレーションするのかは大きな問題である。抵抗とかコンデンサなども計算しようと思えばできないこともないだろうが、アナログ的なところを計算するのは何かと骨が折れるのでそこはバッサリやっちゃって、電気的な部分を抽象化した単純なデジタル回路を想定する。
CPUなどの同期回路を考えると、1クロック単位で状態を計算してやるというのもアリな気がしないでもないが、論理ゲートレベルでモノを考えるならクロックは入力信号の1つでしかないので、同期回路を前提にはしない。
難しいことがやりたいわけではないので状態はHighとLowだけにして、組み込みでNANDゲートだけ作ってやれば、後はそれの組み合わせでそれっぽくできるんじゃなかろうか、などと妄想しつつ、とりあえずNANDだけ作ってみたソースが以下。
HLHASH = {true=>"H", false=>"L", nil=>"nil"} H = true L = false # 出力ピン class OutputPin attr_accessor :output_objects def initialize(v=nil) @value = nil @output_objects = [] self.set(v) if v != nil end # 出力をセットする def set(v) tmp = @value @value = v if tmp != v @output_objects.each do |i| i.update end end self end # 出力の取得 def get @value end end # NANDゲート試作 class NAND def initialize @a = @b = OutputPin.new(L) @o = OutputPin.new update end def a=(v) v.output_objects << self tmp = @a.get @a = v update if tmp != v.get end def b=(v) v.output_objects << self tmp = @b.get @b = v update if tmp != v.get end def o @o end def update @o.set(!(@a.get and @b.get)) end def inspect "#{HLHASH[o.get]}" end end
OutputPinクラスがゲートの出力ピンを表す。基本的にはNANDの出力ピンなのだが、なぜこれがNANDクラスから分離しているかと言うと、デジタル回路の出力と入力は1対nになりえるので、出力の更新を通知する対象が複数ありえて、そのロジックをNANDに組み込んでしまうと、NANDを使って他の論理ゲート(例えばNOTなど)を作ったときにまたそれを書かないといけなくなってしまうからである。クラス構成でどうにかできないかなーとも思ったが、組み合わせたものは出力が2つ以上になったりもするからなかなかうまいこと行かず、とりあえず出力ピンだけ分離して対応した。
OutputPinはついでに5V固定とかGND固定のピン単体も作れるのでテスト用の入力などはこれでよさげだ。クロックもこれで再現することができる。
NANDのほうはab2つの入力と、出力が1つある。入力にOutputPinオブジェクトを設定するとNAND#oで取得できるOutputPinオブジェクトからOutputPin#getで出力を取得できる、という寸法だ。
んで、これをどう使うかと言うと、以下のような感じ。
vcc = OutputPin.new(H) gnd = OutputPin.new(L) _nand = NAND.new _nand.a = vcc _nand.b = gnd p _nand #=> H
5VとGNDの出力ピンを作って、NANDオブジェクトにそれぞれ繋いでやると、出力としてHが出てくる。
全パターン試してみよう。
vcc = OutputPin.new(H) gnd = OutputPin.new(L) _nand = NAND.new _nand.a = gnd _nand.b = gnd p _nand #=> H _nand.a = vcc _nand.b = gnd p _nand #=> H _nand.a = gnd _nand.b = vcc p _nand #=> H _nand.a = vcc _nand.b = vcc p _nand #=> L
ちゃんと動いているようだ。NANDゲートは両方の入力に同じ信号を入れるとNOTとして扱えるのだが、さっきの結果を見ればそのようになっているので別に試す必要は無い。
しかしNOTなのに入力が2本あるのは気持ち悪いので、NOTクラスとして作ってみる。NOTはNANDを使えば作れるので、クラスの内部でNANDオブジェクトを持つようにするとコードがシンプルになる。
# NOTゲート試作 class NOT def initialize @nand = NAND.new end def a=(v) @nand.a = v @nand.b = v end def o @nand.o end def inspect "#{HLHASH[o.get]}" end end vcc = OutputPin.new(H) gnd = OutputPin.new(L) _not = NOT.new _not.a = vcc p _not #=> L _not.a = gnd p _not #=> H
めんどくさいロジックはNANDのほうに既にあるのでNANDと比べるとずいぶん簡単になった。
じゃあNANDとNOTをつないでみよう。
vcc = OutputPin.new(H) gnd = OutputPin.new(L) _nand = NAND.new _not = NOT.new _not.a = _nand.o # 接続 _nand.a = vcc _nand.b = gnd p _nand #=> H p _not #=> L _nand.a = vcc _nand.b = vcc p _nand #=> L p _not #=> H
うむ。ちゃんと動いているっぽい。更にもうちょい難しいものにチャレンジだ。
class RSFF def initialize @not1 = NOT.new @not2 = NOT.new @nand1 = NAND.new @nand2 = NAND.new @nand1.a = @not1.o @nand1.b = @nand2.o @nand2.a = @not2.o @nand2.b = @nand1.o end def s=(v) @not1.a = v end def r=(v) @not2.a = v end def q @nand1.o end def qb @nand2.o end def inspect "q=#{HLHASH[q.get]}, qb=#{HLHASH[qb.get]}" end end
NANDとNOTで構成したRSフリップフロップである。nand1の出力がnand2の入力に、nand2の出力がnand1の入力になるという回路だが、これはどうか。
vcc = OutputPin.new(H) gnd = OutputPin.new(L) rsff = RSFF.new rsff.r = gnd rsff.s = vcc # セット p rsff #=> q=H, qb=L rsff.s = gnd # 保持 p rsff #=> q=H, qb=L rsff.s = vcc # 念のためもう一度セット p rsff #=> q=H, qb=L rsff.s = gnd # 保持 p rsff #=> q=H, qb=L rsff.r = vcc # リセット p rsff #=> q=L, qb=H rsff.r = gnd # 保持 p rsff #=> q=L, qb=H
素晴らしいデスネ。
今日はここまで。