RISC-Vを目指す(3)

いまのところ、プログラムは回路埋め込み状態つまりマイクロコードとなっているわけだが、いずれRAMに書き込んでそれを読んで実行できるようにしたい。とは言えそれができるようになるにはまだまだ先が長いので、とりあえずはRAMを外部に作って値を書き込むことができるようにしよう。RISC-Vでのメモリ書き込み命令はstoreシリーズで、まずはsb(1バイト書き込み)命令を作ってみる。

即値の扱い

sb命令のフォーマットはSタイプで、また違ったデータの配置となるので、即値を扱うのにそれ用の変数を用意するようにした。

# 即値
imm_J_Type = [insn_bit_imm20_1[0]]*11 + insn_bit_imm20_1 + [gnd]
imm_I_Type = [insn_bit_imm11_0[0]]*20 + insn_bit_imm11_0
imm_S_Type = [insn_bit_imm11_5[0]]*20 + insn_bit_imm11_5 + insn_bit_imm4_0

これで少しはラクになるんじゃなかろうか。

sb命令

sb命令用にビットパターンとデコード結果の線を用意する。

# デコード用ビットパターン
opcode_sb   = _to_pin("0100011", vcc, gnd)

# sb命令でH
insn_sb  = _equal(insn_bit_opcode, opcode_sb)

sb命令はrs1と即値を足したアドレスにrs2の下位8bitを出力するので、加算器に入れるのはrs1の値と即値となる。

# 32bit全加算器
adder.d = [_if(insn_jal, pc.o, # jal命令
                         regf.o0 # それ以外
           ),
           _if(insn_jal, imm_J_Type, # jal命令
           _if(insn_add, regf.o1, # add命令
           _if(insn_sb,  imm_S_Type, # sb命令
                         imm_I_Type # それ以外
           )))
          ]

RAMを作る

さて、RAMを作って何をやりたいのかと言うと、Ruby側からデータを読み取って何かしたいのである。何かと言っても漠然としているが、CPUだけだとロジックアナライザで波形を見るぐらいしかできず、いまいち面白くないので、外部の何かをくっつけたような状態にして、目で見て動作がわかるようなことをしたいわけだ。なのでとりあえずテキストVRAMを作って、Ruby側でそれを読み込んで、RAMのアドレスと対応した位置に文字を描画するとかやってみよう、という感じ。
そういう都合上、RAMの実体はフリップフロップではなくRubyの配列にしたい。となるとCPUの組み合わせ回路に組み込む形では作れないので、外部のチップイメージでRAMを構築することにする。クロック信号の立ち上がりでアドレス線とデータ線を読み込んで対応する配列にデータを書き込む。Read/Write信号も用意しておけば読み込みもできるようになるんじゃないかなと適当に考えてはいるのだがこれはいうほど簡単ではなさそう。

# 外付けRAM
class RAM
  attr_reader :memory

  def initialize(a, d)
    @memory = Array.new(80*30){0}
    @o = Array.new(d){OutputPin.new}
  end

  def clk=(v)
    v.output_objects << self
    @clk = v
  end

  def a=(v)
    @addr = v
  end

  def d=(v)
    @data = v
  end

  def w=(v)
    @w = v
  end

  def o
    @o
  end

  def update
    # クロックがHになったら書き込む
    if @clk.get and @w.get
      address = ("0b" + @addr.map{|o|o.get ? "1" : "0"}.join).to_i(2)
      data = ("0b" + @data.map{|o|o.get ? "1" : "0"}.join).to_i(2)
      @memory[address] = data
    end
  end
end

OutputPinの1bit情報から配列に書き込む値に変換するのに微妙に手間取ったが、こんな感じでいける。

RAMとの接続

アドレス線8本とデータ線8本をとりあえず接続して、書き込みができるようにする。RAMを80*30=2400byte取っているのでほんとはもっとアドレスがいるし、データもsbだけじゃなくてshやswを作ればもちょっとややこしくなるのだが、まあそれは後の話。

# でっちあげRAM
ram = RAM.new(8, 8)
ram.a = adder.o[24..31]
ram.d = [gnd]*24 + regf.o1[24..31]
ram.clk = clk.o
ram.w = insn_sb

プログラム

ちょと長くなるのでアドレス線を5本に増やした。テキストVRAMに1文字ずつ書き込むプログラムである。

# ROM
rom = ROM.new(5, 32) # アドレス線5本、データ線32本

# 32アドレスぶんの命令
rom.d = RISCVASM.asm do
label :test
  addi r2, r0, 72
  sb r2, 0, r0
  addi r2, r0, 101
  sb r2, 1, r0
  addi r2, r0, 108
  sb r2, 2, r0
  addi r2, r0, 108
  sb r2, 3, r0
  addi r2, r0, 111
  sb r2, 4, r0
  addi r2, r0, 32
  sb r2, 5, r0
  addi r2, r0, 119
  sb r2, 6, r0
  addi r2, r0, 111
  sb r2, 7, r0
  addi r2, r0, 114
  sb r2, 8, r0
  addi r2, r0, 108
  sb r2, 9, r0
  addi r2, r0, 100
  sb r2, 10, r0
  addi r2, r0, 33
  sb r2, 11, r0
  jal r0, 0
  nop
  nop
  nop
  nop
  nop
  nop
  nop
end.map{|t|_to_pin(t, vcc, gnd)}.reverse

簡易VRAM処理

DXRubyを使ってこのような処理を書く。VRAMに書き込むのはSJIS(WindowsなのでWindows_31J)としておけば2byteで2byte文字が描画できるようになるが、このプログラムでは1文字ずつしか書いてないのでダメな気がする。そもそもSJISでは任意の位置のデータを読み込んで漢字かどうかが判定できないわけで、PC98系のテキストVRAMがJISコードだったのはそういう理由なのかしらん、などとふと思った次第。

# 簡易テキストVRAM
require 'dxruby'

clk.d = L
clrb.d = H
clrb.d = L
clrb.d = H

60.times do
  clk.d = L
  clk.d = H
end

font = Font.new(16, "MS ゴシック")
Window.loop do
  30.times do |y|
    80.times do |x|
      Window.draw_font(x*8, y*16, ram.memory[x+y*80].chr(Encoding::WINDOWS_31J), font)
    end
  end
    
  break if Input.key_push?(K_ESCAPE)
end

おしまい

さて、コードはこちらになるのだが、これを実行するとこうなる。

うん、ようやくHello worldができるようになったぞ。ずいぶん手抜きのような気がしないでもないが、まあVRAMをどうこうする回路とかディスプレイに送る信号がどうのこうのってことがやりたいわけではないのでこれでよし。
次はRAMからの読み込みをやってみたいのだが、これは思った以上に厄介ぽい。んでもRAM内のプログラムを読んで実行するのはその先にあるので避けては通れない道なのである。