🙆

HW高位合成ツールを使ってnand2tetrisのCPUを動かしてみる 9

に公開

の続き

VivadoでSimulation

いきなりFPGAに持っていって動かせる自信がないので、vivadoで簡単なSimulationをしてみる事にする[1]
と言っても、自分でテストベンチが書けるわけではなく、ここでもClaudeに作成をお願いする事にした。vivadoで作成したHDL wrapperを入力して、UART経由で"Hello"と文字入力させるように指示するといい感じのテストベンチを書いてくれた。そこに自分で決めたコマンド文字列を入力するように修正することでSimulationを流すことができた。
UART rx からコマンド文字列を入力すると tx から応答文字列が返ってきたので接続等は問題ないと判断した。

テストベンチ
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 03/12/2025 01:02:46 AM
// Design Name: 
// Module Name: test_bench
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module uart_tb();

  // パラメータ
  parameter CLK_PERIOD = 10; // 100MHz クロック
  parameter BAUD_RATE = 38400;
  parameter BIT_PERIOD = 1000000000 / BAUD_RATE; // ナノ秒単位

  // テストベンチの信号
  reg clk;
  reg rst_n;
  reg rx;
  wire tx;

  // AXIインターフェース信号(必要に応じて追加)
  // ...

  // UARTトランスミッタのタスク
  reg [7:0] tx_data;
  reg tx_start;
  integer i;

  task uart_send_byte;
    input [7:0] data;
    begin
      tx_data = data;
      tx_start = 1'b1;
      #(BIT_PERIOD);
      tx_start = 1'b0;
      
      // スタートビット
      rx = 1'b0;
      #(BIT_PERIOD);
      
      // データビット
      for (i = 0; i < 8; i = i + 1) begin
        rx = tx_data[i];
        #(BIT_PERIOD);
      end
      
      // ストップビット
      rx = 1'b1;
      #(BIT_PERIOD);
    end
  endtask

  // クロック生成
  initial begin
    clk = 0;
    forever #(CLK_PERIOD/2) clk = ~clk;
  end

  // テストシーケンス
  initial begin
    // 初期化
    rst_n = 0;
    rx = 1'b1; // アイドル状態
    #(CLK_PERIOD * 10);
    rst_n = 1;
    
    // テストデータの送信
    #(CLK_PERIOD * 1000);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("2");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("3");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("0");
    #(BIT_PERIOD * 2);
    uart_send_byte("3");
    #(BIT_PERIOD * 2);
    
    // テスト終了
    #(CLK_PERIOD * 1000);
    $finish;
  end

  // UART IPのインスタンス化
  design_1 design_1 (
    .sys_clock(clk),
    .reset(rst_n),
    .usb_uart_rxd(rx),
    .usb_uart_txd(tx)
    // 他のAXIインターフェース信号を接続
  );

  // 必要に応じて波形ダンプを追加
  initial begin
    $dumpfile("uart_tb.vcd");
    $dumpvars(0, uart_tb);
  end

endmodule

Bitstream作成

ここからは一気にBitstreram作成を行った。特にクリティカルなエラーもなく作成ができた。

ACRiルームのFPGAボードで実行

ここより先はACRiルームの設備をお借りして進める。
Arty-A7が使えるサーバーを予約しログイン。sftpでBitsreamを転送し、vivadoでHardware Managerを立ち上げる。FPGAボードに接続してBitstreamをプログラムする。
制御用のPythonスクリプトをsftpで転送し、PyQtなどの必要なライブラリをpip installして立ち上げる。シリアルポート /dev/ttyUSB1 と接続してコマンドを入力して行くと無事に動作した。

Debugについては課題

しかしそれはぬか喜びだった。FPGA上で一発完動のように思ったが、実際にはPongのゲームを動かしていると途中でハングアップしてしまった。C-Simulationでは動いているので、C/RTL co-simulationかvivado上でのRTL simulationで再現させてDebugする必要があるが、エラく時間がかかる上にそもそもHWの信号を見てDebugをスキルがない。
発生状況的になんとなくサブモジュール間のやり取りでデッドロックを引き起こしているように予測できたので、Cソースレベルであーでもないこーでもないと修正しているうちに直ってしまった。
今でも真因はわからず、HWに落としてからの不具合のデバッグ方法は今後の課題である。

まとめ

最終的に下の動画のようにFPGA上でもPongのゲームを動かすことができた。C-simulationよりも滑らかに速く動作している。(実際のゲームが始まるのは0:48辺りから)

https://youtu.be/1roPIIRTocs

テストベンチでverilogを使用したものの、慣れていればC言語だけでも設計してFPGA動作まで持っていける可能性があることが分かった。
自分で作成したIP以外の部分は充実したXilinxのIPで賄うことができ、ブロック図上で(ほぼ)自動配線して動作まで持って行くことができ、ツール完成度の高さに驚愕した。(他社のもそうなのだろうか)
もっと複雑なデザインだと今回のように上手くはいかないのだろうが、一つずつステップアップしていきたい。

今後は

「HW高位合成ツールを使ってnand2tetrisのCPUを動かしてみる」シリーズはここで一旦終わり。
今回はACRiルームのFPGAボードを利用させてもらったが、一通りのフローをこなしてFPGAを動かすことができたので、自分でも評価ボードを購入して手元で動かせるようにしたいと思う。

脚注
  1. 実際にこれで自作IPコアのap_start信号に入力が必要なことがわかった。 ↩︎

Discussion