Ollama cloud経由でdeepseek-v4 を使って,FPGAデザインを作ります.使うボードはTang Mega 138K Pro Dock(Sipeed)です.HDMIコネクタ(TX)からDVI信号で,画面データを出力します.

 deepseek-v4を使った感想として,「細かい仕様を具体的に与えれば,それにそったRTLコード生成はできそう」といった感じです.人間の意図を読み取る力は低いので,曖昧だったり,抽象的なプロンプトには弱いかもしれません.

●開発環境

 次のソフトウェアを使います.なんとWindows11でやります.

  • Ollama(ローカルLLMではなく,課金ありのクラウドLLMをOllama cloudから使う)
  • VSCode
  • VS Code Ollama拡張機能
  • Copilot Agent(VSCode拡張機能)
  • deepseek-v4(Ollama経由)
  • Gowin EDA

 Ollamaをインストールすれば,プロンプトを与えて,コード生成させられます.しかし,Ollamaが提供するローカルアプリケーションはローカルのファイルシステムにアクセスできません.ファイルの保存や編集ができないので,生成されたテキストを統合開発環境などへコピペするしかありません.当然,エージェントループが組めないので,AIエージェントを使う価値があまりありません.

 本稿では,VSCodeとCopilot Agentを使ってOllamaの機能を呼び出します.Copilot Agentはローカルシステムにアクセスできるので,ファイル保存や編集に加えコマンドも実行できます.

 これにより,AIエージェントはローカルPC内のGowin EDAを使って合成したり,合成によるエラーメッセージを取り込んで自動でRTLコードを修正したりできるようになります.

●環境構築

▲インストール

VSCodeをインストールします.

Ollamaをインストールします(OllamaSetup.exe).

VSCode上で,Ollama Copilot VSCode 拡張機能をインストールします.

Gowin EDAをインストールします(ライセンス申請して,ライセンスを適用する).

OllamaのProプラン($20/月)を契約します.

ローカルPCのOllamaアプリケーションを起動し,ログインします.

▲ローカルPC上でdeepseek-v4へアクセスするOllamaを動かす

ローカルPCのコマンドプロンプトで ollama run deepseek-v4-pro:cloud を実行します.

http://localhost:11434 で,Ollamaにアクセスできるようになります.このURIは,VSCodeにOllama拡張機能をインストールした段階で自動的に設定されるので,手動で記述する必要はありません.

▲VSCode上で使うエージェントを設定

VSCodeのChatウィンドウでアクセス先を選択します.ローカルPCでOllamaを動かしているので,ローカルを選択します.

VSCodeのChatウィンドウの下方で,モデルとしてdeepseek V4.0 Proを選択します.

所望のモデルを選択する(図では,deepseek-v4.1-flashが選択されている)

●RTLコード生成の要点

▲ポイント1

Gowin EDAでは,IPの生成はTclなどのCLIではできず,統合開発環境を起動してGUIで行う必要があります.従って,AIエージェントに対して事前に,「ベンダのIPを使ってもよいが,IPの生成作業は人間が行うので,設定パラメータのみを示す」ように指示しておく必要があります.これによってAIエージェントは,RTLコードを生成した後,「IPを生成してください.IPが準備できたら合成するので教えてください」と通知してきます.

▲ポイント2

PLLの周波数設定がうまくできず,同じやり直しを永遠に繰り返していました.埒が明かないので,ピクセルクロック74.25MHzとシリアルクロック371.25MHzという周波数のみを指定し,PLLは人間が用意する旨を指示しました.

●生成されたRTLコード

・全体構成

tang_mega_138k_dvi/
├── src/
│   ├── top.sv           … トップモジュール
│   ├── GOWIN_PLL        … IPジェネレータで手動生成
│   ├── Gowin_CLKDIV     … IPジェネレータで手動生成
│   ├── vga_timing.sv    … タイミングジェネレータ (800x600@60Hz)
│   ├── color_bar.sv     … 7色カラーバー生成
│   ├── tmds_encoder.sv  … TMDSエンコーダ
│   └── DVI_TX_TOP       … DVI送信 IPジェネレータで手動生成
└── constraints/
    └── tang_mega_138k_pro.cst

top.sv

module top (
    input  logic clk_50m,        // 50MHz システムクロック
    input  logic rst_n,          // リセットボタン (アクティブLow)
    input  logic [7:0] leds,     // 追加: LED入力信号
    output logic hdmi_tx_clk_p,  // HDMI TX クロック +
    output logic hdmi_tx_clk_n,  // HDMI TX クロック -
    output logic[2:0] hdmi_tx_p,   // データチャネル0 (Blue) +
    output logic[2:0] hdmi_tx_n   // データチャネル0 (Blue) -
);

    logic clk_pix, clk_ser, pll_locked;
    logic rst_pix;
    logic hsync, vsync, de;
    logic [11:0] hcount, vcount;
    logic [7:0] r, g, b;

/*
    // PLL: 50MHz → 40MHz (ピクセルクロック) / 400MHz (シリアルクロック)
    pll u_pll (
        .clk_in (clk_50m),
        .clk_pix(clk_pix),
        .clk_ser(clk_ser),
        .locked (pll_locked)
    );
*/

    Gowin_PLL u_pll(
        .clkin(clk_50m), //input  clkin
        .init_clk(clk_50m), //input  init_clk
        .clkout0(clk_ser), //output  clkout0 371.25MHz
        .lock(pll_locked), //output  lock
        .reset(~rst_n) //input  reset
);

    Gowin_CLKDIV u_divider(
        .clkout(clk_pix), //output clkout 74.25MHz
        .hclkin(clk_ser), //input hclkin
        .resetn(rst_n) //input resetn
    );

    // リセット同期化 (ピクセルクロックドメイン)
    always_ff @(posedge clk_pix) begin
        rst_pix <= ~pll_locked | ~rst_n;
    end

    // タイミング生成 (800x600@60Hz)
    vga_timing u_timing (
        .clk   (clk_pix),
        .rst   (rst_pix),
        .hsync (hsync),
        .vsync (vsync),
        .de    (de),
        .hcount(hcount),
        .vcount(vcount)
    );

    // 7色カラーバー生成
    color_bar u_color_bar (
        .hcount(hcount),
        .r     (r),
        .g     (g),
        .b     (b)
    );

	DVI_TX_Top u_dvi_tx(
		.I_rst_n(rst_n), //input I_rst_n
		.I_serial_clk(clk_ser), //input I_serial_clk
		.I_rgb_clk(clk_pix), //input I_rgb_clk
		.I_rgb_vs(vsync), //input I_rgb_vs
		.I_rgb_hs(hsync), //input I_rgb_hs
		.I_rgb_de(de), //input I_rgb_de
		.I_rgb_r(r), //input [7:0] I_rgb_r
		.I_rgb_g(g), //input [7:0] I_rgb_g
		.I_rgb_b(b), //input [7:0] I_rgb_b
		.O_tmds_clk_p(hdmi_tx_clk_p), //output O_tmds_clk_p
		.O_tmds_clk_n(hdmi_tx_clk_n), //output O_tmds_clk_n
		.O_tmds_data_p(hdmi_tx_p), //output [2:0] O_tmds_data_p
		.O_tmds_data_n(hdmi_tx_n) //output [2:0] O_tmds_data_n
	);

/*
    // DVI送信
    dvi_tx u_dvi_tx (
        .clk_pix (clk_pix),
        .clk_ser (clk_ser),
        .rst     (rst_pix),
        .r       (r),
        .g       (g),
        .b       (b),
        .hsync   (hsync),
        .vsync   (vsync),
        .de      (de),
        .tx_clk_p(hdmi_tx_clk_p),
        .tx_clk_n(hdmi_tx_clk_n),
        .tx_d0_p (hdmi_tx_d0_p),
        .tx_d0_n (hdmi_tx_d0_n),
        .tx_d1_p (hdmi_tx_d1_p),
        .tx_d1_n (hdmi_tx_d1_n),
        .tx_d2_p (hdmi_tx_d2_p),
        .tx_d2_n (hdmi_tx_d2_n)
    );
*/

endmodule
広げる

color_bar.sv

// 7色カラーバー生成 (白・黄・シアン・緑・マゼンタ・赤・青)
module color_bar (
    input  logic [11:0] hcount,
    output logic [7:0] r,
    output logic [7:0] g,
    output logic [7:0] b
);

    logic [2:0] bar_index;

    // 1280ピクセルを7分割: 183×6 + 182 = 1280
    always_comb begin
        if      (hcount < 183) bar_index = 3'd0;  // 白
        else if (hcount < 366) bar_index = 3'd1;  // 黄
        else if (hcount < 549) bar_index = 3'd2;  // シアン
        else if (hcount < 732) bar_index = 3'd3;  // 緑
        else if (hcount < 915) bar_index = 3'd4;  // マゼンタ
        else if (hcount < 1098) bar_index = 3'd5; // 赤
        else                   bar_index = 3'd6;  // 青
    end

    always_comb begin
        case (bar_index)
            3'd0: begin r = 8'hFF; g = 8'hFF; b = 8'hFF; end  // 白
            3'd1: begin r = 8'hFF; g = 8'hFF; b = 8'h00; end  // 黄
            3'd2: begin r = 8'h00; g = 8'hFF; b = 8'hFF; end  // シアン
            3'd3: begin r = 8'h00; g = 8'hFF; b = 8'h00; end  // 緑
            3'd4: begin r = 8'hFF; g = 8'h00; b = 8'hFF; end  // マゼンタ
            3'd5: begin r = 8'hFF; g = 8'h00; b = 8'h00; end  // 赤
            3'd6: begin r = 8'h00; g = 8'h00; b = 8'hFF; end  // 青
            default: begin r = 8'h00; g = 8'h00; b = 8'h00; end
        endcase
    end

endmodule
広げる

vga_timing.sv

// 1280x720@60Hz タイミングジェネレータ (74.25MHz)
module vga_timing #(
    parameter H_ACTIVE = 1280,
    parameter H_FP     = 110,
    parameter H_SYNC   = 40,
    parameter H_BP     = 220,
    parameter V_ACTIVE = 720,
    parameter V_FP     = 5,
    parameter V_SYNC   = 5,
    parameter V_BP     = 20
) (
    input  logic       clk,
    input  logic       rst,
    output logic       hsync,
    output logic       vsync,
    output logic       de,
    output logic [11:0] hcount,
    output logic [11:0] vcount
);

    localparam H_TOTAL = H_ACTIVE + H_FP + H_SYNC + H_BP;  // 1056
    localparam V_TOTAL = V_ACTIVE + V_FP + V_SYNC + V_BP;  // 628

    logic [11:0] h_cnt;
    logic [11:0] v_cnt;

    always_ff @(posedge clk) begin
        if (rst) begin
            h_cnt <= 12'd0;
            v_cnt <= 12'd0;
        end else begin
            if (h_cnt == H_TOTAL - 1) begin
                h_cnt <= 12'd0;
                if (v_cnt == V_TOTAL - 1)
                    v_cnt <= 12'd0;
                else
                    v_cnt <= v_cnt + 1'b1;
            end else begin
                h_cnt <= h_cnt + 1'b1;
            end
        end
    end

    // アクティブLowの同期信号
    assign hsync = ~(h_cnt >= H_ACTIVE + H_FP && h_cnt < H_ACTIVE + H_FP + H_SYNC);
    assign vsync = ~(v_cnt >= V_ACTIVE + V_FP && v_cnt < V_ACTIVE + V_FP + V_SYNC);

    // データ有効期間
    assign de = (h_cnt < H_ACTIVE) && (v_cnt < V_ACTIVE);

    assign hcount = h_cnt;
    assign vcount = v_cnt;

endmodule
広げる

●実機で動作を確認

▲その1

カラーバーを表示できた

▲その2

 位置x, yと時刻の関数で各ピクセル値を決めるシェーダーでグラデーションカラーを表示させました.

コメントを残す