2分
VerilogをC++に変換してESP32で動かす
背景
現在、8080 simulatorプロジェクトにて、Intel 8080 CPUの回路をVerilogで記述し、それをESP32上で実行して内部状態を可視化するシステムを開発しています。このプロジェクトでは、本来FPGAやASICで動作するはずのハードウェア回路を、マイクロコントローラー上でソフトウェアとして実行することで、CPUの動作を詳細に観察・デバッグできる環境を構築しています。
8080 simulatorの開発過程で、VerilogコードをESP32で動作させる手法について調査・実装を行ったため、その知見をまとめておきます。
概要
YosysのCXXRTL機能を使用して、VerilogコードをC++に変換し、ESP32上で実行する方法を紹介します。 今回は8ビットカウンタを例に、Verilogで記述したハードウェアをソフトウェアとして動作させる手順を説明します。この手法は8080 simulatorで使用している技術の基盤となるものです。
Verilogコードの作成
まず、シンプルな8ビットカウンタのVerilogコードを作成します。
module Counter8 (
input wire reset,
input wire clk,
output reg [7:0] count
);
always @(posedge clk or posedge reset) begin
if (reset) begin
count <= 8'b0;
end else begin
count <= count + 1;
end
end
endmodule
YosysでC++に変換
VerilogコードをC++に変換するため、YosysのCXXRTLを使用します。
まず、Yosysスクリプトファイルを生成し、実行します。
# ディレクトリ作成
mkdir -p build
# Yosysスクリプトファイルを生成
cat > build/synthesize.ys << 'EOF'
read_verilog Counter8.v
hierarchy -top Counter8
proc; opt
write_cxxrtl -header -O6 -g4 Counter8.h
EOF
# Yosysでスクリプトを実行
yosys -s build/synthesize.ys
Yosysコマンドの説明
read_verilog Counter8.v: Verilogファイルを読み込みhierarchy -top Counter8: トップモジュールを指定proc; opt: プロセス展開と最適化write_cxxrtl: CXXRTL形式でC++ヘッダーファイルを生成-header: ヘッダーファイルとして出力-O6: 最適化レベル6-g4: デバッグ情報レベル4
この処理により、Verilogモジュールが Counter8.h と Counter8.cc として生成されます。
ESP32での実装
生成されたC++ファイルをESP32プロジェクトにコピーし、必要なCXXRTLランタイムライブラリを追加します。
必要なファイルの準備
- Yosysで生成されたファイル:
Counter8.h、Counter8.cc - CXXRTLランタイム: YosysのGitHubリポジトリからダウンロード
プロジェクト構成
以下のようなディレクトリ構成でESP32プロジェクトを作成します。ここではPlatform IOを使用しています。
project_root/
├── src/
│ ├── main.cpp # メインのArduinoコード
│ ├── Counter8.h # Yosysで生成されたヘッダーファイル
│ ├── Counter8.cc # Yosysで生成された実装ファイル
│ └── cxxrtl/ # CXXRTLランタイムライブラリ
│ ├── cxxrtl.h
│ ├── cxxrtl_vcd.h
│ ├── cxxrtl_time.h
│ ├── cxxrtl_replay.h
│ └── capi/
│ ├── cxxrtl_capi.h
│ ├── cxxrtl_capi.cc
│ ├── cxxrtl_capi_vcd.h
│ └── cxxrtl_capi_vcd.cc
└── platformio.ini # PlatformIO設定ファイル
ヘッダーファイルの取り込み
ArduinoとCXXRTLのマクロが競合するため、少しincludeを調整します。
// CXXRTL (Yosys) の必要なヘッダーファイルを先に読み込む
#include "Counter8.h" // yosys write_cxxrtl -header で生成されたファイル
#include "cxxrtl/cxxrtl.h"
// Arduinoマクロを無効化してから、Arduinoヘッダーを読み込む
#ifdef bit
#undef bit
#endif
#ifdef INPUT
#undef INPUT
#endif
#include <Arduino.h>
#include <Bounce2.h> // thomasfredericks/Bounce2
// 必要なArduinoマクロを再定義
#define INPUT 0x01
ピン定義と初期化
ボタンのピン設定とVerilogモジュールのインスタンス化を行います。
constexpr uint8_t RST_PIN = 9;
constexpr uint8_t CLK_PIN = 10;
// ボタン用のBounceオブジェクト
Bounce rst_button = Bounce();
Bounce clk_button = Bounce();
// CXXRTL Counter8インスタンス (生成クラス名は p_<module>)
cxxrtl_design::p_Counter8 dut;
カウンタ値の表示関数
CXXRTLで生成されたカウンタの値をシリアルコンソールに出力
// カウンタ値をシリアルに出力
void printCounterValue() {
uint8_t counter_value = dut.p_count.get<uint8_t>();
Serial.print("Counter value: ");
Serial.print(counter_value);
Serial.print(" (0x");
Serial.print(counter_value, HEX);
Serial.print(") [");
// バイナリ表示
for (int i = 7; i >= 0; i--) {
Serial.print((counter_value >> i) & 1);
}
Serial.println("]");
}
setup関数
ESP32の初期化とVerilogモジュールの初期化を行います。
void setup() {
Serial.begin(115200);
Serial.println("Counter8 Test");
Serial.println("==============================");
// ボタンピンの初期化
rst_button.attach(RST_PIN, INPUT_PULLUP);
rst_button.interval(10);
clk_button.attach(CLK_PIN, INPUT_PULLUP);
clk_button.interval(10);
// CXXRTL Counter8の初期化
dut.p_clk.set<bool>(false);
dut.p_reset.set<bool>(true);
dut.step();
// リセット解除
dut.p_reset.set<bool>(false);
dut.step();
Serial.println("CXXRTL Counter8 initialized");
Serial.println("RST: Reset counter to 0");
Serial.println("CLK: Clock pulse (increment counter)");
Serial.print("Initial ");
printCounterValue();
}
loop関数
メインループでボタン入力を監視し、Verilogモジュールのクロックとリセット信号を制御します。
void loop() {
rst_button.update();
clk_button.update();
if (rst_button.fell()) {
// Counter8をリセット
dut.p_reset.set<bool>(true);
dut.step();
// リセット解除
dut.p_reset.set<bool>(false);
dut.step();
Serial.print("Reset: ");
printCounterValue();
}
if (clk_button.fell()) {
// クロック立ち上がりエッジ
dut.p_clk.set<bool>(true);
dut.step();
dut.p_clk.set<bool>(false);
dut.step();
Serial.print("Clock: ");
printCounterValue();
}
delay(10);
}
まとめ
YosysのCXXRTL機能を使用することで、VerilogコードをC++に変換し、ESP32上でハードウェア設計をソフトウェアとして実行することができました。
特に、ESP32のような高性能なマイコンを使用することで、複雑なハードウェア設計もなかなか高速に実行できるため、そこそこ実用的に使用できることがわかりました。私達の8080コードでも、Clockボタンを押してほぼ遅延なしにLEDの表示を切り替えることができました。
回路規模と、動作速度の検証を今後行いたいです。