Skip to content

Instantly share code, notes, and snippets.

@thata
Last active June 3, 2022 08:51
Show Gist options
  • Save thata/1d0aa0dc3d177035f7f973d5f12e6a80 to your computer and use it in GitHub Desktop.
Save thata/1d0aa0dc3d177035f7f973d5f12e6a80 to your computer and use it in GitHub Desktop.
rv32sim - One file RISC-V simulator
# fibonacci for RV32
#
# $ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o fibonacci fibonacci.S
# $ riscv64-unknown-elf-objcopy -O binary fibonacci fibonacci.rom
# $ ruby rv32sim.rb fibonacci.rom
.text
.globl _start
.type _start, @function
_start:
addi x5, x0, 10 # n = 10
addi x4, x0, 0 # i = 0
addi x1, x0, 0 # f(i) = 0
addi x2, x0, 1 # f(i+1) = 1
loop:
beq x4, x5, break # goto break if i == n
add x3, x1, x2 # temp = f(i) + f(i+1)
add x1, x2, x0 # f(i) = f(i+1)
add x2, x3, x0 # f(i+1) = temp
addi x4, x4, 1 # i = i + 1
beq x0, x0, loop # goto loop
break:
addi x6, x0, 0x100
sw x1, 0(x6) # メモリの0x100番地へfib(10)の結果を格納
nop
nop
nop
nop
nop
class Memory
WORD_SIZE = 4
attr_accessor :data
def initialize(data = nil)
# バイナリデータとして扱いたいので ASCII-8BIT エンコーディングへ変換
@data = data.b
end
# 指定したアドレスから1ワード(4バイト)のデータを読み込む
def read(addr)
word = @data.slice(addr, WORD_SIZE)
# 「signed int32」でメモリの内容を読み込む
# see: https://docs.ruby-lang.org/ja/latest/doc/pack_template.html
word.unpack1("l")
end
# 指定したアドレスへ1ワード(4バイト)のデータを書き込む
def write(addr, word)
# 「signed int32」でメモリへ書き込む
@data[addr, 4] = [word].pack("l")
end
end
class Decoder
attr_reader :opcode, :rd, :funct3, :rs1, :rs2, :funct7, :i_imm, :s_imm, :b_imm
def initialize
@opcode = nil
@rd = nil
@funct3 = nil
@rs1 = nil
@rs2 = nil
@funct7 = nil
@i_imm = nil
@s_imm = nil
@b_imm = nil
end
def decode(inst)
@opcode = (inst & 0x0000007f)
@rd = (inst & 0x00000f80) >> 7
@funct3 = (inst & 0x00007000) >> 12
@rs1 = (inst & 0x000f8000) >> 15
@rs2 = (inst & 0x01f00000) >> 20
@funct7 = if opcode == 0b0110011
(inst & 0xfe000000) >> 25
else
nil
end
@i_imm = (inst & 0xfff00000) >> 20
@s_imm = ((inst & 0xfe000000) >> 20) | ((inst & 0x00000f80) >> 7)
@b_imm = ((inst & 0x80000000) >> 19) |
((inst & 0x00000080) << 4) |
((inst & 0x7e000000) >> 20) |
((inst & 0x00000f00) >> 7)
end
# nop命令(no operation 何も行わない命令)かどうかを判定(あとで使う)
def nop?
@opcode == 0b0010011 &&
@funct3 == 0 &&
@rd == 0 &&
@rs1 == 0 &&
@i_imm == 0
end
end
class Cpu
INST_TABLE = {
[0b0110011, 0x0, 0x00] => :_add,
[0b0110011, 0x0, 0x20] => :_sub,
[0b0110011, 0x6, 0x00] => :_or,
[0b0110011, 0x7, 0x00] => :_and,
[0b0010011, 0x0, nil] => :_addi,
[0b1100011, 0x0, nil] => :_beq,
[0b0000011, 0x2, nil] => :_lw,
[0b0100011, 0x2, nil] => :_sw
}
attr_accessor :pc
attr_reader :x_registers, :memory
def initialize
@pc = 0 # プログラムカウンタ
@x_registers = [0] * 32 # レジスタ
@decoder = Decoder.new
@memory = Memory.new( # メモリ
("\x00" * 512).b
)
@nop_count = 0
end
def init_memory(data)
@memory.data[0, data.size] = data
end
def run
inst = fetch
decode(inst)
# NOPが5回来たら処理を終える
return false if @nop_count >= 5
execute
true
end
def fetch
@memory.read(@pc)
end
def decode(inst)
@decoder.decode(inst)
if @decoder.nop?
@nop_count += 1
else
@nop_count = 0
end
end
def execute
key = [@decoder.opcode, @decoder.funct3, @decoder.funct7]
inst_symbol = INST_TABLE[key]
send inst_symbol
end
### Instructions
def _add
rd = @decoder.rd
rs1 = @decoder.rs1
rs2 = @decoder.rs2
@x_registers[rd] = @x_registers[rs1] + @x_registers[rs2]
@pc = @pc + 4
end
def _sub
rd = @decoder.rd
rs1 = @decoder.rs1
rs2 = @decoder.rs2
@x_registers[rd] = @x_registers[rs1] - @x_registers[rs2]
@pc = @pc + 4
end
def _or
rd = @decoder.rd
rs1 = @decoder.rs1
rs2 = @decoder.rs2
@x_registers[rd] = @x_registers[rs1] | @x_registers[rs2]
@pc = @pc + 4
@x_registers[@rd] = @x_registers[@rs1] | @x_registers[@rs2]
@pc = @pc + 4
end
def _and
rd = @decoder.rd
rs1 = @decoder.rs1
rs2 = @decoder.rs2
@x_registers[rd] = @x_registers[rs1] & @x_registers[rs2]
@pc = @pc + 4
end
def _addi
rd = @decoder.rd
rs1 = @decoder.rs1
i_imm = @decoder.i_imm
minus_flg = (i_imm & 0b100000000000) >> 11
imm = if minus_flg == 1
# TODO もっといい感じに書きたい
imm = (0b1000000000000 - i_imm) * -1
else
imm = i_imm
end
@x_registers[rd] = @x_registers[rs1] + imm
@pc = @pc + 4
end
def _beq
rd = @decoder.rd
rs1 = @decoder.rs1
rs2 = @decoder.rs2
b_imm = @decoder.b_imm
minus_flg = (b_imm & 0b1000000000000) >> 12
imm = if minus_flg == 1
# TODO もっといい感じに書きたい
imm = (0b10000000000000 - b_imm) * -1
else
imm = b_imm
end
@pc = if @x_registers[rs1] == @x_registers[rs2]
@pc + imm
else
@pc + 4
end
end
def _lw
rd = @decoder.rd
rs1 = @decoder.rs1
imm = @decoder.i_imm
@x_registers[rd] = @memory.read(@x_registers[rs1] + imm)
@pc = @pc + 4
end
def _sw
rs1 = @decoder.rs1
rs2 = @decoder.rs2
imm = @decoder.s_imm
@memory.write(@x_registers[rs1] + imm, @x_registers[rs2])
@pc = @pc + 4
end
end
class Simulator
def initialize
@cpu = Cpu.new
end
def init_memory(data)
@cpu.init_memory(data)
end
def start
loop do
@cpu.run || break
end
end
def dump_registers
puts "-" * 80
for i in 0..7
for j in 0..3
print "\t" unless j == 0
n = (i * 4) + j
print sprintf "x%02d = 0x%x (%d)", n, @cpu.x_registers[n], @cpu.x_registers[n]
end
print "\n"
end
puts "-" * 80
puts sprintf "pc = 0x%x (%d)", @cpu.pc, @cpu.pc
end
end
if $0 == __FILE__
sim = Simulator.new
mem = File.binread(ARGV.shift)
sim.init_memory(mem)
sim.start
sim.dump_registers
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment