2008-01-08
■[Ruby] Rubyで8ビットCPUを作る
Perl で 8ビット CPU を作る - naoyaのはてなダイアリー
octopusをRubyで実装してみました。IO関係、オプションまわりの処理は省略しています。できるだけRubyらしいプログラムを目指してみました。あんまりトリッキーな実装はしていません。
ファイル: octopus3.rb
#!/usr/bin/env ruby # -*- compile-command: "ruby -Ks octopus3.rb" -*- class OctopusVm REG_PC = 7 REG_RET = 6 REG_SP = 5 ZFLAG = 1 OCT_INST = { 0 => 'nop', 1 => 'mov', 2 => 'in', 3 => 'out', 4 => 'movi', 5 => 'addi', 6 => 'subi', 7 => 'muli', 8 => 'divi', 9 => 'andi', 10 => 'ori', 11 => 'testi', 12 => 'jmp', 13 => 'jz', 14 => 'jnz', 15 => 'jsr', 16 => 'push', 17 => 'pop', 18 => 'call', 19 => 'ret', 31 => 'hlt' } def initialize(text) @text = Array.new(256, 0) @text[0, text.size + 1] = text + [0xf8] @reg = Array.new(16, 0) @port = Array.new(256, 0xff) @flag = 0 @steps = 0 msg(dump_format(@text[ 0, 16], ' Memory = ', "\n")) msg(dump_format(@text[16, 16], ' = ', "\n")) msg("----------------------------------------------------------\n") end def execute begin loop do case execute_op when :OCT_OK @steps += 1 when :OCT_HALTED msg("### System is halted.\n") break when :OCT_ILLCOD msg("### Illegal operation code!\n") break when :OCT_ILLMOD msg("### Illegal operation mode!\n") break when :OCT_OVRRUN msg("### Segment over-run!\n") break else msg("### Internal error!\n") break end end ensure terminate end end private def msg(*args) printf *args end def imsg(*args) printf *args end def dump_format(vals, prefix, suffix) vals.inject(prefix) {|acc, val| acc + '%02x ' % val } + suffix end def terminate msg("----------------------------------------------------------\n") msg(" 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\n") msg(dump_format(@text[ 0, 16], ' Memory = ', "\n")) msg(dump_format(@text[16, 16], ' = ', "\n")) msg(dump_format(@port[ 0, 16], ' Port = ', "\n")) msg(dump_format(@port[16, 16], ' = ', "\n")) msg(dump_format(@reg, 'Register = ', "\n")) msg(" Flag = #{(@flag & ZFLAG > 0) ? "Z\n" : "NZ\n"}") msg(" Steps = %d\n", @steps) end def execute_op ins = @text[@reg[REG_PC]] @reg[REG_PC] += 1 op = OCT_INST[(ins & 0xf8) >> 3] return :OCT_BREAK if op == 30 # OPEC_BREAK return :OCT_OVRRUN if @reg[REG_PC] > 0xff return :OCT_ILLCOD unless op imsg('%02x: %s ', @reg[REG_PC] - 1, op) ret = send('oct_' + op, ins & 7) imsg("\n") ret end def oct_nop(mod) sleep(0.1) :OCT_OK end def oct_mov(mod) case mod when 3 # Register -> Register src = (@text[@reg[REG_PC]] & 0xf0) >> 4 dst = @text[@reg[REG_PC]] & 0x0f @reg[REG_PC] += 1 imsg('r%d, r%d', src, dst) @reg[dst] = @reg[src] :OCT_OK when 4 # Register -> Memory addr = @text[@reg[REG_PC]] @reg[REG_PC] += 1 imsg('r0, [ 0x%02x ]', addr) @text[addr] = @reg[0] :OCT_OK when 5 # Memory -> Register addr = @text[@reg[REG_PC]] @reg[REG_PC] += 1 imsg('[ 0x%02x ], r0', addr) @reg[0] = @text[addr] :OCT_OK when 6 # Reg indirect -> Register src = (@text[@reg[REG_PC]] & 0xf0) >> 4 dst = @text[@reg[REG_PC]] & 0x0f @reg[REG_PC] += 1 imsg('[ r%d ], r%d', src, dst) @reg[dst] = @text[@reg[src]] :OCT_OK when 7 # Register -> Reg indirect src = (@text[@reg[REG_PC]] & 0xf0) >> 4 dst = @text[@reg[REG_PC]] & 0x0f @reg[REG_PC] += 1 imsg('r%d, [ r%d ]', src, dst) @text[@reg[dst]] = @reg[src] :OCT_OK else imsg('???') :OCT_ILLMOD end end def oct_in(mod) num = @text[@reg[REG_PC]] @reg[REG_PC] += 1 @reg[0] = @port[num] imsg('[ 0x%02x ], r0', num) :OCT_OK end def oct_out(mod) num = @text[@reg[REG_PC]] @reg[REG_PC] += 1 @port[num] = @reg[0] imsg('r0, [ 0x%02x ]', num) :OCT_OK end def calc_base(dreg) val = @text[@reg[REG_PC]] @reg[REG_PC] += 1 imsg('0x%02x, r%d', val, dreg) yield(val) if @reg[dreg].zero? @flag |= ZFLAG else @flag &= ~ZFLAG end :OCT_OK end def oct_movi(dreg) calc_base(dreg) do |val| @reg[dreg] = val end end def oct_addi(dreg) calc_base(dreg) do |val| @reg[dreg] += val @reg[dreg] -= 0x100 if @reg[dreg] > 0xff end end def oct_subi(dreg) calc_base(dreg) do |val| @reg[dreg] -= val @reg[dreg] += 0x100 if @reg[dreg] < 0 end end def oct_muli(dreg) calc_base(dreg) do |val| @reg[dreg] *= val @reg[dreg] %= 0x100 if @reg[dreg] > 0xff end end def oct_divi(dreg) calc_base(dreg) do |val| @reg[dreg] /= val end end def oct_andi(dreg) calc_base(dreg) do |val| @reg[dreg] &= val end end def oct_ori(dreg) calc_base(dreg) do |val| @reg[dreg] |= val end end def oct_testi(dreg) calc_base(dreg) do |val| end end def jmp_base(mod) if mod.zero? dst = @text[@reg[REG_PC]] @reg[REG_PC] += 1 imsg('0x%02x', dst) else dst = @reg[mod] imsg('r%d', mod) end yield(dst) :OCT_OK end def oct_jmp(mod) jmp_base(mod) do |dst| @reg[REG_PC] = dst end end def oct_jz(mod) jmp_base(mod) do |dst| @reg[REG_PC] = dst if @flag & ZFLAG != 0 end end def oct_jnz(mod) jmp_base(mod) do |dst| @reg[REG_PC] = dst if @flag & ZFLAG == 0 end end def oct_jsr(mod) jmp_base(mod) do |dst| @reg[REG_RET] = @reg[REG_PC] @reg[REG_PC] = dst end end def oct_push(mod) jmp_base(mod) do |data| @reg[REG_SP] -= 1 @text[@reg[REG_SP]] = data end end def oct_pop(mod) if mod.zero? imsg('???') :OCT_ILLMOD else imsg('r%d', mod) @reg[mod] = @text[@reg[REG_SP]] @reg[REG_SP] += 1 :OCT_OK end end def oct_call(mod) jmp_base(mod) do |dst| @reg[REG_SP] -= 1 @text[@reg[REG_SP]] = @reg[REG_PC] @reg[REG_PC] = dst end end def oct_ret(mod) if mod.zero? @reg[REG_PC] = @text[@reg[REG_SP]] @reg[REG_SP] += 1 :OCT_OK else imsg('???') :OCT_ILLMOD end end def oct_hlt(mod) :OCT_HALTED end end if $0 == __FILE__ OctopusVm.new(ARGV.map {|a| a.to_i }).execute end
以下、実行例です。これしか動かしていないので、まだまだバグがありそうです。そのうち暇を見付けてテストコードを書きたいところです。
$ ruby octopus3.rb 37 32 33 16 144 7 248 41 1 144 12 152 41 2 152 Memory = 25 20 21 10 90 07 f8 29 01 90 0c 98 29 02 98 f8 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ---------------------------------------------------------- 00: movi 0x20, r5 02: movi 0x10, r1 04: call 0x07 07: addi 0x01, r1 09: call 0x0c 0c: addi 0x02, r1 0e: ret 0b: ret 06: hlt ### System is halted. ---------------------------------------------------------- 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Memory = 25 20 21 10 90 07 f8 29 01 90 0c 98 29 02 98 f8 = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0b 06 Port = ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff = ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff Register = 00 13 00 00 00 20 00 07 00 00 00 00 00 00 00 00 Flag = NZ Steps = 8
(追記) うわー、やっぱりバグってました。id:miura1729さん、どうもありがとうございます。上のソースは修正しておきました。
- 60 http://b.hatena.ne.jp/entrylist?sort=hot
- 38 http://b.hatena.ne.jp/
- 19 http://d.hatena.ne.jp/
- 17 http://reader.livedoor.com/reader/
- 12 http://b.hatena.ne.jp/entrylist?sort=hot&of=50&threshold=5
- 12 http://www.google.co.jp/ig?hl=ja
- 11 http://secure.ddo.jp/~kaku/tdiary/
- 10 http://b.hatena.ne.jp/add?mode=confirm&title=Ruby%u30678%u30D3%u30C3%u30C8CPU%u3092%u4F5C%u308B - %u8DA3%u5473%u7684%u306B%u3063%u304D&url=http://d.hatena.ne.jp/ha-tan/20080108/1199725414
- 9 http://b.hatena.ne.jp/entry/http://d.hatena.ne.jp/ha-tan/20080108/1199725414
- 8 http://d.hatena.ne.jp/keyworddiary/Haskell