Skip to content

Instantly share code, notes, and snippets.

@mattsan
Created September 26, 2022 04:43
Show Gist options
  • Save mattsan/7d3191de2210fb293878b7aaf07ac5d1 to your computer and use it in GitHub Desktop.
Save mattsan/7d3191de2210fb293878b7aaf07ac5d1 to your computer and use it in GitHub Desktop.
RISC-V subset simulator in Elixir
defmodule RV32Sim.Register do
def new do
<<0::32-unit(32)>>
end
def get(_, 0), do: 0
def get(registers, number) do
<<_::size(number)-unit(32), result::32, _::bytes>> = registers
result
end
def set(registers, number, value) do
<<left::size(number)-unit(32), _::32, right::bytes>> = registers
<<left::size(number)-unit(32), value::32, right::bytes>>
end
def dump(registers) do
rule()
0..31
|> Enum.zip(for <<r::32 <- registers>>, do: r)
|> Enum.each(fn {index, register} ->
:io.format("x~-2b = x~8.16.0b (~b)~s", [index, register, register, delimiter(index)])
end)
rule()
end
defp rule, do: IO.puts(String.duplicate("-", 92))
defp delimiter(index) when rem(index, 4) == 3, do: "\n"
defp delimiter(_), do: "\t"
end
defmodule RV32Sim.Memory do
def new(binary) do
rest_size = 512 - String.length(binary)
binary <> <<0::size(rest_size)-unit(8)>>
end
def load(_, 0x10000000) do
<<c, _::bytes>> = IO.read(1)
c
end
def load(memory, address) do
<<_::size(address)-unit(8), result::little-32, _::bytes>> = memory
result
end
def store(memory, 0x10000000, value) do
IO.write(<<value>>)
memory
end
def store(memory, address, value) do
<<left::size(address)-bytes, _::32, right::bytes>> = memory
<<left::bytes, value::little-32, right::bytes>>
end
end
defmodule RV32Sim.Instruction do
import RV32Sim.Register, only: [get: 2, set: 3]
import RV32Sim.Memory, only: [load: 2, store: 3]
import Bitwise
def eval(<<0x00::7, rs2::5, rs1::5, 0x0::3, rd::5, 0b0110011::7>>, registers, memory) do
registers = set(registers, rd, get(registers, rs1) + get(registers, rs2))
{4, registers, memory}
end
def eval(<<0x20::7, rs2::5, rs1::5, 0x0::3, rd::5, 0b0110011::7>>, registers, memory) do
registers = set(registers, rd, get(registers, rs1) - get(registers, rs2))
{4, registers, memory}
end
def eval(<<0x00::7, rs2::5, rs1::5, 0x6::3, rd::5, 0b0110011::7>>, registers, memory) do
registers = set(registers, rd, bor(get(registers, rs1), get(registers, rs2)))
{4, registers, memory}
end
def eval(<<0x00::7, rs2::5, rs1::5, 0x7::3, rd::5, 0b0110011::7>>, registers, memory) do
registers = set(registers, rd, band(get(registers, rs1), get(registers, rs2)))
{4, registers, memory}
end
def eval(<<0x00::7, imm::5, rs1::5, 0x1::3, rd::5, 0b0010011::7>>, registers, memory) do
registers = set(registers, rd, get(registers, rs1) <<< imm)
{4, registers, memory}
end
def eval(<<imm::signed-12, rs1::5, 0x0::3, rd::5, 0b0010011::7>>, registers, memory) do
registers = set(registers, rd, get(registers, rs1) + imm)
{4, registers, memory}
end
def eval(<<imm12::1, imm10::6, rs2::5, rs1::5, 0x0::3, imm4::4, imm11::1, 0b1100011::7>>, registers, memory) do
<<imm::signed-13>> = <<imm12::1, imm11::1, imm10::6, imm4::4, 0::1>>
relative_address = if get(registers, rs1) == get(registers, rs2), do: imm, else: 4
{relative_address, registers, memory}
end
def eval(<<imm::12, rs1::5, 0x2::3, rd::5, 0b0000011::7>>, registers, memory) do
registers = set(registers, rd, load(memory, get(registers, rs1) + imm))
{4, registers, memory}
end
def eval(<<imm11::7, rs2::5, rs1::5, 0x2::3, imm4::5, 0b0100011::7>>, registers, memory) do
<<imm::12>> = <<imm11::7, imm4::5>>
memory = store(memory, get(registers, rs1) + imm, get(registers, rs2))
{4, registers, memory}
end
end
defmodule RV32Sim.CPU do
alias RV32Sim.{Register, Memory, Instruction}
def run(pc, memory) do
run(pc, Register.new(), memory, 0)
end
defp run(pc, registers, memory, 5), do: {pc, registers, memory}
defp run(pc, registers, memory, nop_count) do
instruction = Memory.load(memory, pc)
{relative_address, registers, memory} =
Instruction.eval(<<instruction::32>>, registers, memory)
run(pc + relative_address, registers, memory, count_nop(instruction, nop_count))
end
defp count_nop(0b0000_0000_0001_0011, n), do: n + 1
defp count_nop(_, _), do: 0
end
defmodule RV32Sim do
alias RV32Sim.{Register, Memory, CPU}
def run(filename) do
memory = Memory.new(File.read!(filename))
{pc, registers, _memory} = CPU.run(0, memory)
Register.dump(registers)
:io.format("pc = x~.16b (~b)~n", [pc, pc])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment