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内のプログラムを読んで実行するのはその先にあるので避けては通れない道なのである。