SoC 2nd

C → FPGA 実行までの設計フロー

C code (main_led.c)


RISC-V compiler (riscv32-unknown-elf-gcc)


ELF binary (main.elf)


[Optional] HEX/BIN for FPGA memory init


Verilog SoC design (soc_top.v, led.v, picorv32_wrapper.v)


Synthesis & Place-and-Route (Yosys + nextpnr)


Netlist → Bitstream (soc_top.bit)


FPGA

ステップ 1: Cコード → ELFバイナリ

  • 入力: software/main_led.c

  • ツール: riscv32-unknown-elf-gcc

  • 目的: CPU(PicoRV32)用のバイナリ生成

# Makefile rule
main.elf: software/main_led.c
riscv32-unknown-elf-gcc -O2 -march=rv32im -mabi=ilp32 -Ttext 0x10000 -o main.elf software/main_led.c
  • 出力: main.elf

  • この ELF は PicoRV32 が実行可能な形式


ステップ 2: ELF → HEX/BIN (Optional)

  • FPGA の RAM/ROM初期化用に変換

  • ツール: riscv32-unknown-elf-objcopy

# Convert ELF to Intel HEX for FPGA
riscv32-unknown-elf-objcopy -O ihex main.elf main.hex
  • FPGA内のメモリにロードするためのフォーマット


ステップ 3: SoCのVerilog設計

  • 入力: soc_top.v, 10_led.v, 01_picorv32_wrapper.v

  • 役割:

    • PicoRV32 CPU をラップして接続

    • MMIO LED を接続

    • スタック、メモリマップを定義

// soc_top.v: CPU + LED MMIO top
soc_top cpu (.clk(clk), .rstn(rstn), .led(led));
  • 出力: FPGA設計としての RTL(Register-Transfer Level)コード


ステップ 4: Synthesis → Netlist → Place & Route

  • ツール: yosys + nextpnr-ecp5 (例: Lattice ECP5 FPGA)

synthesize.sh

#!/bin/bash
# ==========================================================
# synthesize.sh: Verilog -> Bitstream
# ==========================================================

# Step 1: Synthesis with Yosys
yosys -p "
read_verilog 00_soc_top.v 01_picorv32_wrapper.v 10_led.v
synth_ecp5 -top soc_top
write_json soc_top.json
"

# Step 2: Place-and-route with nextpnr
nextpnr-ecp5 --json soc_top.json --textcfg soc_top.txt --package tq144:4k

# Step 3: Bitstream generation
ecppack soc_top.txt soc_top.bit

echo "Bitstream ready: soc_top.bit"

  • 役割:

    1. Yosys: RTL → ロジックゲートレベルネットリスト

    2. nextpnr: ネットリストをFPGAの物理セルに配置(Place & Route)

    3. ecppack: 最終的な .bit ビットストリームを生成

  • 出力: soc_top.bit → FPGA に書き込む


ステップ 5: FPGAに書き込み

# Example FPGA flash command (device-specific)
fpgashell -f soc_top.bit
  • CPU は SoC上で ELF バイナリを実行

  • LED がメモリマップに従って点灯


まとめ: 各ステップの役割

ステップ 入力 出力 ツール 目的
1. C → ELF main_led.c main.elf riscv32-unknown-elf-gcc CPU用実行バイナリ作成
2. ELF → HEX/BIN main.elf main.hex objcopy FPGA RAM初期化用
3. SoC設計 Verilog RTL RTLネットリスト 設計ファイル CPU+周辺回路設計
4. Synthesis + P&R RTL Bitstream yosys + nextpnr + ecppack FPGA実装用
5. FPGA書き込み bitstream 実機動作 FPGA programmer 実際にLEDなどが動作

💡 ポイント

  • このフローでは ソフトウェアとハードウェアが並行して流れる

  • Makefile は ソフトウェア用、synthesize.sh は ハードウェア用

  • スタック、MMIO、CPU動作の理解があると、C→FPGAまでの挙動を追いやすい


もし希望なら、次に このフローを図解 して、

  • Cコードアセンブラ / ELFSoC / Verilogネットリスト / FPGA
    までを一枚の図で見せることもできます。

作りますか?

あなた:
バイナリーをメモリモジュールに書き込むやり方を丁寧に。
ChatGPT:

もちろんです。Cで作ったバイナリ(ELFファイルやHEX/BINファイル)を FPGA上のメモリモジュール(ROM/RAM)に書き込む 方法を、順を追って丁寧に解説します。SoC演習で非常に重要なステップです。


1. 目的の整理

  • FPGA上で CPU が Cプログラムを実行するためには、プログラムをFPGA内部のメモリに置く必要があります。

  • 典型的には以下のいずれか:

    1. 初期化用ROMにHEX/BINを書き込む(Power-on時に自動ロード)

    2. 外部RAMにプログラムをロードする(FPGA起動後にDMAなどでロード)

演習用では通常 ROMに書き込む方法 が簡単です。


2. ELF → HEX/BIN変換

ステップ 2-1: ELF確認

riscv32-unknown-elf-objdump -d main.elf | less
  • ELF内の命令を逆アセンブルして確認できる

  • メモリ上の配置アドレスを把握するのに便利

ステップ 2-2: HEX/BIN変換

# Intel HEX形式
riscv32-unknown-elf-objcopy -O ihex main.elf main.hex

# バイナリ形式
riscv32-unknown-elf-objcopy -O binary main.elf main.bin

  • Intel HEX: FPGAツールで直接ロードできることが多い

  • Binary: 純粋なバイナリデータ(FPGA内部ROM初期化用)


3. Verilogメモリモジュールへの組み込み

FPGA上では、通常 $readmemh または $readmemb を使ってメモリを初期化します。

例: ROM モジュール

module rom #(
parameter ADDR_WIDTH = 12, // 4096 words
parameter DATA_WIDTH = 32
)(
input wire [ADDR_WIDTH-1:0] addr,
output reg [DATA_WIDTH-1:0] data
);

// メモリ本体
reg [DATA_WIDTH-1:0] mem [0:(1<<ADDR_WIDTH)-1];

// 初期化: HEXファイルをロード
initial begin
$readmemh("main.hex", mem);
end

always @(*) begin
data = mem[addr];
end

endmodule

  • ポイント

    1. mem が ROM/メモリ配列

    2. $readmemh("main.hex", mem)FPGAコンパイル時にバイナリを読み込む

    3. アドレスを指定して CPU が読み込むと、命令が返る


4. CPU(PicoRV32)からの接続例

wire [31:0] instr;
rom rom_inst(
.addr(mem_addr[11:0]), // CPUアドレスの下位12bit
.data(instr)
);
  • CPUの命令フェッチアドレス mem_addr に基づき ROM から命令が返る

  • これで CPUがCプログラムを実行可能


5. 実機FPGAでの手順

  1. main.elf → main.hex に変換

  2. Verilog ROMモジュール$readmemh("main.hex", mem) を記述

  3. soc_top.v に ROM を接続

  4. synthesize.sh で bitstream 作成

  5. FPGAに書き込む

  6. CPUがROMからプログラムを読み込み実行


6. 補足ポイント

  • $readmemhコンパイル/シミュレーション時にファイルを読み込む

  • $readmemb は同じですが 2進形式

  • FPGAベンダーツールによっては ROMの初期値をHEX/BINから直接指定可能

  • 大きなプログラムでは、ブロックRAM(BRAM)を複数使うこともある


💡 まとめ

  • C → ELF → HEX/BIN → Verilog ROM → FPGA

  • $readmemh で簡単に初期化可能

  • CPU(PicoRV32)は ROM のアドレスから命令をフェッチ

  • ソフトウェアとハードウェアを結ぶ重要な橋渡し


もし希望なら、私は main.elf → ROM → FPGA実行までの図解フロー を作って、
CPUが命令をフェッチする様子とスタック/LEDアクセス も可視化できます。

作りますか?