ESM アジャイル事業部 開発者ブログ

永和システムマネジメント アジャイル事業部の開発者ブログです。

esm.fm コンサル x EM 対談『テーマ: 育成』を開催します

esm.fm コンサル x EM 対談『テーマ: 育成』を 2022年7月5日(火) 12:30-13:00 に開催します。

esminc.doorkeeper.jp

アジャイルソフトウェア開発の老舗『永和システムマネジメント』のアジャイルコンサルタントの @fkino とエンジニアリングマネージャーの @koic のオンライン対談イベントです。

今回取り上げるテーマは『育成』です。"アジャイルマニフェスト"から20年後の現代において「いまの時代にはもうないだろう」と思っているような困りごとや価値観での実践が現実世界では起きているようです。アジャイルコンサルタントが現場で見ている課題と、そのような課題に対して永和の開発とコンサルタントがどのような価値観で解決しているか取りあげるインターネット番組です。

ランチタイムの 12:30 ~ 13:00 に開催するカジュアルなイベントです。お昼の賑わいにご活用ください。

また、ESM エンジニア ショートトーク『失敗から学ぶ技術』を 2022年6月30日(木) 12:30-13:00 に開催します。こちらもぜひご視聴ください。

esminc.doorkeeper.jp


永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

ESM エンジニア ショートトーク『失敗から学ぶ技術』を開催します

ESM エンジニア ショートトーク『失敗から学ぶ技術』を 2022年6月30日(木) 12:30-13:00 に開催します。

esminc.doorkeeper.jp

Ruby とアジャイルソフトウェア開発の老舗『永和システムマネジメント』のエンジニア @9sako6@color_box 登壇のオンラインプレゼンイベントです。

今回取り上げるテーマは『失敗』です。

プロジェクトや個人でのソフトウェア開発で「失敗」はつきものです。このイベントでは、弊社エンジニアが汎用ツールを作成する過程で踏んださまざまな失敗や、逆に、実プロジェクトで遭遇した失敗をきっかけにして、同じ失敗を起こさないよう実装したツールについて紹介します。

ランチタイムの 12:30 ~ 13:00 に開催するカジュアルなイベントです。お昼の賑わいにご活用ください。


永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

Rails / OSS パッチ会オンライン 2022年6月のお知らせ

2022年6月の Rails / OSS パッチ会を 6月15日(水)に Discord でオンライン開催します。

この会をひとことでいうと、日頃のお仕事で使っている Rails をはじめとする OSS について、upstream にパッチを送る会です。

会には Ruby と Rails のコミッターである顧問の a_matsuda もいますので、例えば Rails に送るパッチのネタがあるけれど、パッチを送るに適しているかの判断やパッチを送る流れが悩ましいときなど a_matsuda に相談して足がかりにするなどできます。

開催時間は 17:00-19:00 となりますがご都合のあう方はぜひご参加下さい。

Discord の Rails/OSS パッチ会サーバーへの招待 URL は以下です👇

discord.gg

以下、前回の活動が関わる成果です。

kamipo: Rails

github.com

koic: RuboCop

github.com

先日新たに Rails コアコミッター、コミッターに就任されたレギュラーメンバーの活動や RubyKaigi 2022 に向けた話題などあるかもしれません。

rubyonrails.org

顧問の a_matsuda は RubyKaigi のチーフオーガナイザーでもあるため、RubyKaigi 2022 の CFP へのプロポーザルについて悩みを持ち寄っても良さそうです。

これからパッチ会に参加してみようという方も、ぜひどうぞ。Discord でお会いしましょう。


永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じてコミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp

Rubyでできる!RISC-Vシミュレータの作りかた 〜 From 4649 To HELLO WORLD 〜

HELLO WORLD〜

はじめに

こんにちは、永和システムマネジメントの自作CPUおじさん、はたけやまたかし( @htkymtks )です。

今回はRubyを使った小さなRISC-Vシミュレータの作り方をご紹介します(以前もシミュレータの記事を書いたのですが、シミュレータに大幅に手を入れたので、それに対応したHDリマスター版です)

リポジトリ

(今回ご紹介するシミュレータのリポジトリはこちら)

RISC-Vとは

RISC-VはCPUの命令セットアーキテクチャ(ISA)のひとつで、使用料のかからないオープンソースライセンスで提供されていることや、命令セットの美しさから最近注目を集めています。

仕様

RISC-Vの仕様にはワード幅(32ビット、64ビット)や浮動小数点数サポートの有無など、いくつかのバリエーションがありますが、今回は32ビット整数演算のみをサポートする「RV32I」のサブセットを実装します。

メモリ

では早速作っていきましょう。まずは命令やデータを格納するメモリを作ります。Memoryクラスは、バイナリデータが格納されたStringオブジェクトをメモリイメージとして受け取り、メモリイメージに対しての1ワードの読み込み(readメソッド)と1ワードの書き込み(writeメソッド)を提供します。

ちなみに「ワード」というのはCPUがデータの読み込み・書き込みを行う単位です。今回作成する32ビットRISC-Vは32ビット単位でデータの読み書きを行うため、1ワードは32ビット(4バイト)となります。

class Memory
  WORD_SIZE = 4

  attr_accessor :data

  def initialize(data = "\0" * 512)
    # バイナリデータとして扱いたいので 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

Memoryの使い方はこんな感じです。

RISC-VはリトルエンディアンなCPUなので、4バイトの 0x01010093 をメモリの 0x00000000 番地へ書き込むと「0x00000000 = 0x93, 0x00000001 = 0x0, 0x00000002 = 0x01, 0x00000003 = 0x01」のような順番に格納されます。

# メモリを初期化
memory = Memory.new("\x93\x00\x01\x01\x94\x00\x01\x01")

# アドレスを指定して読み込み
printf "0x%08x\n", memory.read(0)
#=> 0x01010093
printf "0x%08x\n", memory.read(4)
#=> 0x01010094

# アドレス0番地へ書き込み
memory.write(0, 0x00ABCDEF)
printf "0x%08x\n", memory.read(0)
#=> 0x00abcdef

# アドレス4番地へ書き込み
memory.write(4, -1)
printf "0x%08x\n", memory.read(4)
#=> 0xffffffff

命令デコーダ

次は命令デコーダです。

RISC-Vの命令は以下のようなフォーマットになっています。「opcode」「funct3」「funct7」は命令の判別に、「rd」「rs1」「rs2」は操作対象のレジスタ番号の指定に、「imm」は即値に利用されます。

命令デコーダは命令のビット列からこれらパラメータの切り出し処理を行います。

( https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf より)

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 = (inst & 0xfe000000) >> 25
    @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

命令デコーダの使い方はこんな感じです。

# 適当なバイト列を読み込ませて正しくデコードできるか確認
decoder = Decoder.new
decoder.decode(0b1000001_10111_10011_101_10001_0110011)
printf "%07b\n", decoder.opcode
#=> 0110011
printf "%05b\n", decoder.rd
#=> 10001
printf "%03b\n", decoder.funct3
#=> 101
printf "%05b\n", decoder.rs1
#=> 10011
printf "%05b\n", decoder.rs2
#=> 10111
printf "%07b\n", decoder.funct7
#=> 1000001

CPU

次はCPUです。今回作成するCPUは以下の4つの部品から構成されます。

  1. メモリ(さっき作ったMemoryクラス)
    • プログラムやデータの保存領域
  2. プログラムカウンタ(PC)
    • メモリ中の次に実行する命令のアドレスを指し示す
  3. 命令デコーダ(さっき作ったDecoderクラス)
  4. x0からx31まで32個のレジスタ
    • RV32Iはx0からx31までの32ビットの整数レジスタを持ちます
    • RISC-Vの「x0レジスタ」は常に0を返す特殊なレジスタになります

INST_TABLEoptcodefunct3funct7 をキーにした命令テーブルで、デコーダから返ってきた値を使って INST_TABLE を引くことでどの命令を呼び出すかを決定します。

また、 _add_sub は実行される命令の本体になります。

class Cpu
  INST_TABLE = {
    [0b0110011, 0x0, 0x00] => :_add,
    [0b0110011, 0x0, 0x20] => :_sub,
    [0b0110011, 0x6, 0x00] => :_or,
    [0b0110011, 0x7, 0x00] => :_and,
    [0b0010011, 0x0]       => :_addi,
    [0b0010011, 0x1]       => :_slli,
    [0b1100011, 0x0]       => :_beq,
    [0b0000011, 0x2]       => :_lw,
    [0b0100011, 0x2]       => :_sw
  }

  attr_accessor :pc
  attr_reader :x_registers, :memory

  def initialize
    @pc = 0                   # プログラムカウンタ
    @x_registers = [0] * 32   # レジスタ
    class << @x_registers
      # x0は常に0を返す
      def [](nth)
        nth == 0 ? 0 : super
      end
    end

    @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
    op_f3_f7 = [@decoder.opcode, @decoder.funct3, @decoder.funct7]
    op_f3 = [@decoder.opcode, @decoder.funct3]
    inst_symbol = INST_TABLE[op_f3_f7] || INST_TABLE[op_f3]
    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
  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 _slli
    rd = @decoder.rd
    rs1 = @decoder.rs1
    i_imm = @decoder.i_imm
    @x_registers[rd] = @x_registers[rs1] << (i_imm & 0b11111)
    @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

CPUの使い方はこんな感じです。インスタンスを生成してメモリへプログラムをセット、runメソッドが呼ばれると「メモリから命令をフェッチ」→「命令のデコード」→「命令の実行」が行われ、実行結果がレジスタに反映されます。

# 命令組み立て用のユーティリティメソッド
def _add(rd, rs1, rs2)
  0b0110011 |
    (rd << 7) |
    (0x0 << 12) |
    (rs1 << 15) |
    (rs2 << 20) |
    (0x00 << 25)
end

cpu = Cpu.new
cpu.x_registers[1] = 10
cpu.x_registers[2] = 20
cpu.x_registers[3] = 30

# 命令メモリをセット
mem = [
  _add(4, 1, 2), # x4 = x1 + x2
  _add(5, 4, 3), # x5 = x4 + x3
].pack("l*")

cpu.init_memory(mem)

# 実行前
puts cpu.pc
#=> 0
puts cpu.x_registers[4]
#=> 0
puts cpu.x_registers[5]
#=> 0

cpu.run # 1つめの命令を実行
cpu.run # 2つめの命令を実行

# 実行後
puts cpu.pc
#=> 8
puts cpu.x_registers[4]
#=> 30
puts cpu.x_registers[5]
#=> 60

シミュレータ

最後はシミュレータです。渡されたプログラムファイルを読み込んでCPUを初期化した後、CPUのrunメソッドを回し続けます。

本物のコンピュータではCPUは止まることなく回り続けますが、シミュレータのCPUはいつかは止める必要があります。CPUの止め方はシミュレータによってマチマチで「サポート外の命令を呼び出す」とか「nop命令(no operation なにもしない命令)を5回連続で呼び出す」など、いろいろな方法があります。今回はnop命令を5回連続で呼び出したら停止することにします。

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

シミュレータを実行

それではシミュレータを実行してみましょう。rv32simのリポジトリ内にサンプルプログラムが用意されているのでそちらを利用します。

以下のサンプルプログラムはフィボナッチ数の第10項の値を求めるプログラムです。 x1 レジスタに第10項のフィボナッチ数である 55 が格納されています。

プログラムファイルの作り方

シミュレータが完成したので、次はオリジナルのプログラムを作成してみましょう。

今回はgccを利用してプログラムをビルドするため、RISC-V向けのGNUツールチェインをインストールします。

macOSではHomebrewを使って以下の手順でインストールできます(macOS Monterey ではバイナリがインストールされますが、そうでない場合はソースからビルドされるためインストール完了するまで数時間かかります…)

$ brew tap riscv-software-src/riscv
$ brew install riscv-tools

macOS以外の環境では以下を参考にすると良いかも。

RISC-V 用のクロスコンパイラを使ってみる | Hassy's Tech Blog

(追記)Dockerもあるよ

GNUツールチェイン構築済みのDockerイメージを使う方法もあります(後述の「【おまけ】Dockerを使ったビルド手順」をご覧ください)

gccを使ってアセンブル

GNUツールチェインをインストールしたらgccを使ってアセンブルしてみましょう。アセンブルするソースファイルを用意して、

# 4649
# https://github.com/thata/rv32sim/blob/master/sample/4649.S

  .text
  .globl _start
  .type _start, @function
_start:
  addi x1, x0, 0x46 # x1 = ヨロ(46)
  addi x2, x0, 0x49 # x2 = シク(49)
  # シミュレータを停止させるための nop
  nop
  nop
  nop
  nop
  nop

アセンブルを行い実行ファイル(ELFファイル)を生成、ELFファイルを機械語のみのバイナリ形式(ROMファイル)に変換、作成したROMファイルを引数にシミュレータを実行します。

レジスタ x01x02 に「ヨロシク (0x46, 0x49)」がセットされていれば成功です。

$ cd sample
$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o 4649.elf 4649.S
$ riscv64-unknown-elf-objcopy -O binary 4649.elf 4649.rom
$ cd ..
$ ruby rv32sim.rb sample/4649.rom

4649〜

【おまけ】Dockerを使ったビルド手順

GNUツールチェイン構築済みのDockerイメージ(15GBくらいあるよ...)を使ってビルドする手順は以下の通り。「riscv64-unknown-elf-gcc」が「/opt/riscv/bin/riscv32-unknown-elf-gcc」になってるのでご注意ください。

# Dockerイメージをpullしてくる
$ docker pull kamiyaowl/riscv-gnu-toolchain-docker

# ビルド
$ cd sample
$ docker run --rm \
  -v $PWD:/usr/src/myapp \
  -w /usr/src/myapp kamiyaowl/riscv-gnu-toolchain-docker \
  /opt/riscv/bin/riscv32-unknown-elf-gcc \
  -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o 4649.elf 4649.S
$ docker run --rm \
  -v $PWD:/usr/src/myapp \
  -w /usr/src/myapp kamiyaowl/riscv-gnu-toolchain-docker \
  /opt/riscv/bin/riscv32-unknown-elf-objcopy -O binary 4649.elf 4649.rom

# シミュレータで実行
$ cd ..
$ ruby rv32sim.rb sample/4649.rom

シミュレータ外部とのデータのやりとり

これでRISC-Vシミュレータが完成したわけですが、これだけだとレジスタに値を書き込むことしかできず、ちょっと物足りないですよね。

ということで、シミュレータへ仮想シリアルデバイスを導入し、仮想シリアルデバイスを通じて標準入出力へアクセスできるようにシミュレータを拡張してみましょう。

メモリマップドI/O

メモリのアドレス空間の一部を外部デバイスに割り当てて、メモリのアクセスと同じ方法で外部デバイスへアクセスする方法を「メモリマップドI/O」と呼びます。

今回は仮想シリアルデバイスの入出力インターフェースをメモリの0x10000000番地へ割り当てて、0x10000000番地へ書き込みが行われたら標準出力へ書き込み、0x10000000番地から読み込みを行ったら標準入力から読み込みを行えるようにします。

仮想シリアルデバイスの組み込み

まずはSerialクラスを作成します。このクラスを介して標準入出力とのデータのやり取りを行います。

class Serial
  def initialize(input = $stdin, output = $stdout)
    @input = input
    @output = output
  end

  def write(word)
    @output.putc(word & 0xFF)    
  end

  def read
    c = @input.getc until c
    # getc は String が返ってくるので、ASCIIコードに変換
    c.ord
  end
end

次にCpuクラスを以下のように修正します。lw(Load Word)命令で 0x10000000 番地からのデータの読み込みが指示された場合はSerialからデータを読み込み、sw(Store Word)命令で 0x10000000 番地へデータの書き込みが指示された場合はSerialへデータを書き込むようにします。

class Cpu
  def initialize
    ...
    #--- 追加ここから ---
    @serial = Serial.new
    #--- 追加ここまで ---
  end

  #--- 追加ここから ---
  SERIAL_ADDRESS = 0x1000_0000
  def serial_address?(address)
    address == SERIAL_ADDRESS
  end
  #--- 追加ここまで ---

  #--- _lwの修正(ここから) ---
  def _lw
    rd = @decoder.rd
    rs1 = @decoder.rs1
    imm = @decoder.i_imm
    address = @x_registers[rs1] + imm
    @x_registers[rd] =
      if serial_address?(address)
        # 標準入力から読み込む
        @serial.read
      else
        @memory.read(address)
      end
    @pc = @pc + 4
  end
  #--- _lwの修正(ここまで) ---
  
  #--- _swの修正(ここから) ---
  def _sw
    rs1 = @decoder.rs1
    rs2 = @decoder.rs2
    imm = @decoder.s_imm
    address = @x_registers[rs1] + imm
    if serial_address?(address)
      # 標準出力へ書き込む
      @serial.write(@x_registers[rs2])
    else
      @memory.write(address, @x_registers[rs2])
    end
    @pc = @pc + 4
  end
  #--- _swの修正(ここまで) ---
end

標準出力へ出力

まずは標準出力へ文字を出力してみましょう。

出力したい文字をt0レジスタへセットして0x10000000番地へ書き込むことで標準出力への出力を行います。

# hello
# https://github.com/thata/rv32sim/blob/master/sample/hello.S

  .text
  .globl _start
  .type _start, @function
_start:
  // シリアル通信の送受信レジスタのアドレス ( 0x10000000 ) を gp レジスタにセット
  // 1024を18ビットシフトさせて 0x10000000 を作成する
  addi gp, zero, 1024
  slli gp, gp, 18

  addi t0, zero, 'H'
  sw t0, 0(gp)
  addi t0, zero, 'E'
  sw t0, 0(gp)
  addi t0, zero, 'L'
  sw t0, 0(gp)
  addi t0, zero, 'L'
  sw t0, 0(gp)
  addi t0, zero, 'O'
  sw t0, 0(gp)
  addi t0, zero, ' '
  sw t0, 0(gp)
  addi t0, zero, 'W'
  sw t0, 0(gp)
  addi t0, zero, 'O'
  sw t0, 0(gp)
  addi t0, zero, 'R'
  sw t0, 0(gp)
  addi t0, zero, 'L'
  sw t0, 0(gp)
  addi t0, zero, 'D'
  sw t0, 0(gp)
  addi t0, zero, '!'
  sw t0, 0(gp)
  addi t0, zero, '!'
  sw t0, 0(gp)
  addi t0, zero, '\n'
  sw t0, 0(gp)
  nop
  nop
  nop
  nop
  nop

上記のソースをアセンブルして、

$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nost
dlib -o sample/hello.elf sample/hello.S
$ riscv64-unknown-elf-objcopy -O binary sample/hello.elf sample/hello.rom

実行してみます。「HELLO WORLD!!」が出力されればOKです。

標準入力から入力

次は標準入力から入力を受け取り、それをそのまま標準出力へ出力してみます。

# loopback
# https://github.com/thata/rv32sim/blob/master/sample/loopback.S

  .text
  .globl _start
  .type _start, @function
_start:
  // シリアル通信の送受信レジスタのアドレス ( 0x10000000 ) を gp レジスタにセット
  // 1024を18ビットシフトさせて 0x10000000 を作成する
  addi gp, zero, 1024
  slli gp, gp, 18
loop:
  // 標準入力から入力を受け取り
  lw a0, 0(gp)
  // 標準出力へ出力する
  sw a0, 0(gp)
    // 無限ループなので、終了させる場合は Ctrl-c で
  beq zero, zero, loop

上記のソースをアセンブルして、

$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o sample/loopback.elf sample/loopback.S
$ riscv64-unknown-elf-objcopy -O binary sample/loopback.elf sample/loopback.rom

実行してみます。入力した文字がそのまま画面へ表示されれば成功です。

C言語で書きたいんだけど...

今回はソースコードの短さを優先したためいくつか命令が足りず、C言語でプログラムを書くことができませんでした。次回は足りない命令を追加してC言語を使ったベアメタルプログラミングに挑戦する予定です。

終わりに

以上、Rubyを使ったシンプルなRISC-Vシミュレータの作り方のご紹介しました。

皆さんも梅雨の時期のおうち時間にお好きなプログラム言語でCPUシミュレータ自作なんていかがですか?

参考

フィヨルドブートキャンプのオンライン合同会社説明ドリンクアップでお話してきました

ワドルディ集めにいそしんでいる nsgc です。

2022年6月2日(木)に、フィヨルドブートキャンプさん主催のオンライン合同会社説明ドリンクアップで、@fugakkbnと一緒にお話させていただきました。

今年3月入社の@fugakkbnは、フィヨルドブートキャンプ卒業生で入社後ちょうど3ヵ月というタイミングでした。

身近なOBの先輩のふりかえりを通して、どんな感じの会社か、どういうことを学べるのか、どんな不安があってそれは解消されるか、といった生の声をライブ感を持ってお伝えできればという想いから、弊社で開発運用しており、日頃現場のプロジェクトでも活用している『Continuous KPTA (CKPTA) 』を使ったふりかえり実演をしました。

kpta.agile.esm.co.jp

発表

成果物はこちら。

ふーがメンのKPTA

当日は、それぞれの項目について、@fugakkbn からの回答を、私が付箋に入力するという流れで行ないました。

普段のふりかえり司会進行と同じ感覚で行なえたため、プレゼン発表よりは緊張は少なく感じました。また、@fugakkbn の相槌やコメントフォロー、タイピングのタイミングに合わせた回答にだいぶ救われました。

打鍵音は聞いてる方によってはうるさく感じたかもしれないので、次回行なう場合は単一指向性のマイク変えるなど改善したいです。

フリートークタイム

この後、他の2社様の発表のあと各テーブル毎でフリートークタイムでした*1
フリートークでは興味を持っていただけた多くの方と、受託開発に関する疑問や、採用フロー、会社の技術イベントといった参加された受講生の気になりポイントについてお酒を飲みながらざっくばらんに話せて良かったです。

最後に

最後になりましたがフィヨルドの皆さん素晴しい機会をありがとうございました。 そして、ご参加されたフィヨルドブートキャンプの皆さんありがとうございました!


株式会社永和システムマネジメント アジャイル事業部では、エンジニアを絶賛募集しています。

さまざまな背景を持つ方々も歓迎です。 応募エントリお待ちしております!

agile.esm.co.jp

*1:会場は Remo というオンラインイベントサービスで会社毎にテーブルが分かていました。便利。

部分SPAのRalisアプリにおけるフロントエンドからAuth0のアクセストークンを取得する方法

こんにちは、nand2tetrisにハマっているyuki0920です。

今回は業務で得たTipsである「部分SPAのRalisアプリにおけるフロントエンドからAuth0のアクセストークンを取得する方法」を紹介します。

前提: フロントエンドからアクセストークンを使いたい

一部SPA構成のRails製のアプリケーションを運用しており、認証基盤にAuth0を利用しています。 Railsアプリはユーザーから受け取ったメールアドレスとパスワードをAuth0へ送信するなどの認証処理を担っており、認証後のアクセストークンはRailsアプリが受け取っています。

今回、部分SPAのフロントエンドから、ログイン画面を経ずにアクセストークンを使いたい事情がありました。しかし、ドキュメントにはこのようなユースケースは記載されていませんでした。

Auth0にフロントエンド用のアプリケーションを追加する

Railsサーバー用のAuth0アプリケーションと同一テナントにフロントエンド用のアプリケーションを作成することで実現できました。

下記の画像のようにフロントエンド用のAuth0アプリケーションを、Railsサーバー用のアプリケーションと同一のテナント内に作成します。ここが最大の要点となります。

Auth0のアプリケーション作成後の設定は、ドキュメントを参考に、コールバックURL等を設定します。フロントエンドでは、Auth0のSDKをインストールし実装します。

このようにすることで、フロントエンドから、ログイン画面を介さずにアクセストークンを取得することができました。

なぜフロントエンドではログイン画面を介さずにアクセストークンを取得できるのか

なぜフロントエンドではログイン画面を介さずにアクセストークンを取得できるのでしょうか?

それは、Auth0のログイン状態の保持の仕方と関係があります。

Auth0のログイン状態は、Auth0のテナントに設定されたドメインに紐づくクッキーに保持されています。Auth0は同一テナント内であれば、どのアプリケーションも共通のドメインが設定されています。

今回のケースではRailsアプリにログインした時点でAuth0のログイン状態が保持されているため、フロントエンドからはログイン不要でアクセストークンを取得できた、というわけです。

参考: Application Session Management

まとめ

Auth0を利用したアクセストークンの取得方法について紹介させていただきました。 この記事を書くにあたり、クッキーなどのWEB技術やAuth0を深堀りするきっかけとなり、勉強になりました。

@kasumi8ponと@ima1zumiのインタビューが公開されました

弊社エンジニアの @kasumi8pon@ima1zumiのフィヨルドブートキャンプ卒業生インタビューが公開されました。

bootcamp.fjord.jp

この記事は、インタビューを受けた2人のコメントを載せたライナーノーツになっております。インタビュー本編とあわせてお楽しみください。

@kasumi8pon コメント

今回のインタビューを受けて、フィヨルドブートキャンプで学習していた時代のことを思い出しました。自分の経験の話が、今学んでいる人や学ぼうとしている人が未来を描くのに役に立てば嬉しいです。

また、自分自身のふりかえりにもなりました。当時わからなくて苦労したプラクティスが、今では身について、仕事に活かせているなという自信を持てたり、当時楽しいと感じたチーム開発について、今も変わらず仕事でのチーム開発を楽しんでいることに気がつけました。そして、フィヨルドブートキャンプで学んだときを思い出して、これからも同じように学んでいきたいと思うことができました。 とてもよい機会をいただき、ありがとうございました!

@ima1zumi コメント

今回のインタビューでは、フィヨルドブートキャンプで学習したことと、働きはじめて何をしているか、どう感じているかということを話しました。フィヨルドブートキャンプ卒業と永和システムマネジメント入社からほぼ1年という節目で、自分を見つめ直すいい機会になりました。フィヨルドブートキャンプで学んだ学習とアウトプットの姿勢を続けていこうと気持ちを新たにしました。

インタビューを受けるというのは初めての経験ですこし緊張しましたが、今の率直な気持ちを言葉に残せたと思います。フィヨルドブートキャンプの雑談タイムで話していたころと同じような雰囲気で懐かしくリラックスして話せました。

このインタビューが何かの参考になれば幸いです。ありがとうございました。


"フィヨルドというコミュニティ" で過ごす時間によって、ソフトウェアを作る技術だけではなく、人と人の繋がり方や関係性の大切さが伝えられていることが伝わるインタビューでした。ぜひご一読ください。

永和システムマネジメントでは、Ruby とアジャイルソフトウェア開発を通じて、コミュニティと成長したいエンジニアを絶賛募集しています。

agile.esm.co.jp