ハードウェアシミュレータその7

レジスタファイルを作ろうとして、そういえば仕様的におかしいところがあるのを放置していたなあ、と思い出した。それがあるとレジスタファイルを作るのにも困るので、まずそれを直す。

マルチプレクサのデータ順

現状、ROMはマルチプレクサで構成されていて、そのデータは選択信号の小さいものから順に配列に並べることになっている。これはアドレス順にデータを書けたほうが見た目わかりやすいという理由である。んでも、bit情報は上位bitが配列の先頭だし、セレクタも選択信号が小さい場合は最下位bitつまり配列の最後の出力がHになる。
レジスタファイルを作る際、レジスタは配列に詰めるが、命令のbitで00を指定したらAレジスタを選択するようにセレクタを構成すると、出力の配列はAレジスタが最後という順番になり、それをマルチプレクサに繋ぐと逆順になってしまう。
問題の原因はコード上の見やすさを優先したところだと判断して、マルチプレクサのデータ順を逆にすることにした。ROMデータはコード上はアドレス順に配列を作って、Array#reverseすることで対処する。

レジスタファイル

CPUの中にはレジスタがたくさん入っているが、これをまとめたブロックにして管理するのが一般的らしく、これをレジスタファイルと言う。通常レジスタファイルは入力1ポート、出力2ポートという構成になり、レジスタ1つの書き込みとレジスタ2つの出力を同時にできる。インテル系の命令セットだと出力1つと入力は同じレジスタが選択されるし、MIPSなどでは3つバラバラに選択される。命令セットの定義によりできるようにするかどうかの違いである。

# レジスタファイル(出力2ポート、入力1ポート)
# 出力ポートは未実装
class RegisterFile
  def initialize(select_bit_width, data_bit_width)
    # レジスタの配列
    @reg = Array.new(2**select_bit_width){Register.new(data_bit_width)}

    # レジスタを選択するためのセレクタ
    # 出力はレジスタファイルのRW信号入力とANDを取って各レジスタのRWに入る
    @sel = Selector.new(select_bit_width)
    @w = Buffer1.new # RW入力用バッファ
    @reg.zip(@sel.o).each do |reg, sel|
      reg.w = @w.o & sel # &演算子でANDゲートが生成される。戻りはANDゲートの出力ピン
    end
  end

  # クロック入力
  # 書き込みタイミングはクロックの立ち上がり
  def clk=(v)
    @reg.each do |reg|
      reg.clk = v
    end
  end

  # 入力データ
  # wがHの場合、クロックの立ち上がりでsで選択されたレジスタにデータが書き込まれる
  def d=(v)
    @reg.each do |reg|
      reg.d = v
    end
  end

  # select_bit_width本の書き込み選択信号
  # 内部のセレクタに入力される
  def s=(v)
    @sel.s = v
  end

  # 入力ポート有効がH
  def w=(v)
    @w.d = v
  end

  # クリア信号
  def clrb=(v)
    @reg.each do |reg|
      reg.clrb = v
    end
  end

  def o
    @reg.map{|r|r.o}
  end
end

現状、出力するレジスタを選択する命令が存在しないので出力ポートを作っていない。後で命令を追加したときに作る。
また、&演算子が登場しているが、出力ピンにたいする演算で論理ゲートを生成するように機能追加をしたのだった。

出力ピンの演算子

HDLでは線同士を演算することができる。たぶんゲートを出力してるんだと思うのだが、ANDとかORとかNOTがたくさん出現するする場合に演算子が使えるとすごいラクな気がするので、これを実装してみた。

# 出力ピン
class OutputPin
  # &演算子
  def &(dst)
    tmp = AND.new
    tmp.a = self
    tmp.b = dst
    tmp.o
  end

  # |演算子
  def |(dst)
    tmp = OR.new
    tmp.a = self
    tmp.b = dst
    tmp.o
  end

  # ~演算子
  def ~
    tmp = NOT.new
    tmp.a = self
    tmp.o
  end

  # ^演算子
  def ^(dst)
    tmp = XOR.new
    tmp.a = self
    tmp.b = dst
    tmp.o
  end
end

生成されるゲートそのものの参照は捨てられるが、接続した出力ピンが参照を持っているので、返すのはOutputPinオブジェクトだけでよい。これらがあれば命令デコーダを作るときにラクができそうである。
なお、内部でANDなどのクラスを使っているが、「この演算子でANDを実装したらシンプルになるんでない?」とか思って参照ループしそうになったのは内緒。

おしまい

レジスタファイルが半分できた。セレクタを内部に持つのでレジスタ回りの回路は外側はずいぶんとシンプルになった。以下のような感じ。

# 4bitレジスタ4本のレジスタファイル生成
reg = RegisterFile.new(2, 4)
reg.clk = clk.o
reg.clrb = clrb.o
reg.d = [bus.o[2], bus.o[3], bus.o[4], bus.o[5]]
reg.s = [bus.o[6], bus.o[7]]
reg.w = vcc # 常に書き込んじゃう

各種部品が高機能化してきたおかげでそれ以外の部分に集中できる。
次は命令を追加しようと思う。