Perl で 8ビット CPU を作る - naoyaのはてなダイアリー

octopusをRubyで実装してみました。IO関係、オプションまわりの処理は省略しています。できるだけRubyらしいプログラムを目指してみました。あんまりトリッキーな実装はしていません。

ファイル: octopus3.rb

#!/usr/bin/env ruby 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 = ' , "

" )) msg(dump_format( @text [ 16 , 16 ], ' = ' , "

" )) msg( " ----------------------------------------------------------

" ) end def execute begin loop do case execute_op when :OCT_OK @steps += 1 when :OCT_HALTED msg( " ### System is halted.

" ) break when :OCT_ILLCOD msg( " ### Illegal operation code!

" ) break when :OCT_ILLMOD msg( " ### Illegal operation mode!

" ) break when :OCT_OVRRUN msg( " ### Segment over-run!

" ) break else msg( " ### Internal error!

" ) 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( " ----------------------------------------------------------

" ) msg( " 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

" ) msg(dump_format( @text [ 0 , 16 ], ' Memory = ' , "

" )) msg(dump_format( @text [ 16 , 16 ], ' = ' , "

" )) msg(dump_format( @port [ 0 , 16 ], ' Port = ' , "

" )) msg(dump_format( @port [ 16 , 16 ], ' = ' , "

" )) msg(dump_format( @reg , ' Register = ' , "

" )) msg( " Flag = #{ ( @flag & ZFLAG > 0 ) ? " Z

" : " NZ

"}" ) msg( " Steps = %d

" , @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 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( "

" ) ret end def oct_nop (mod) sleep( 0.1 ) :OCT_OK end def oct_mov (mod) case mod when 3 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 addr = @text [ @reg [ REG_PC ]] @reg [ REG_PC ] += 1 imsg( ' r0, [ 0x%02x ] ' , addr) @text [addr] = @reg [ 0 ] :OCT_OK when 5 addr = @text [ @reg [ REG_PC ]] @reg [ REG_PC ] += 1 imsg( ' [ 0x%02x ], r0 ' , addr) @reg [ 0 ] = @text [addr] :OCT_OK when 6 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 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さん、どうもありがとうございます。上のソースは修正しておきました。