Kanade Electronicskanaelecom
❄️

iCE40 で始める FPGA

オープンソースで FPGA を開発しよう

iCE40 は Lattice 社製の小規模 FPGA シリーズです。FPGA の中では安く、構造が比較的単純で、オープンソースツールチェーンがかなり整っているので、入門用におすすめです。

この記事では、iCE40 を題材にして、FPGA の中に何が入っているのか、Verilog からビットストリームまで何が起きているのかを見ます。

FPGA とは

FPGA (Field Programmable Gate Array) は、ユーザがあとから内部回路を書き換えられる IC です。 CPU のように命令を順番に実行するのではなく、内部の論理回路そのものを作り替えます。

ざっくり言うと、FPGA の中には次のものが大量に入っています。

要素役割
LUT任意の組合せ回路を作る
DFF1 bit の状態を保持する
配線スイッチLUT や DFF の接続を切り替える
I/O ブロック外部ピンとの入出力を行う
Block RAMまとまったメモリ
PLLクロックを生成・変換する

FPGA に書き込むビットストリームは、LUT の中身と配線スイッチの ON/OFF を指定する巨大な設定データです。 つまり、FPGA は「大量の小さな真理値表と配線スイッチ」です。

iCE40

iCE40 は小規模で低消費電力な FPGA です。巨大な画像処理や高速通信をガンガンやるというより、小さな制御、信号変換、簡単な SoC、LED 制御、VGA 出力、音源、ロジックアナライザのような用途に向いています。

代表的なボードには TinyFPGA BX や iCEBreaker があります。以前は TinyFPGA BX がかなり入門しやすかったのですが、入手性は時期によって変わります。

iCE40 の良さは、オープンソースツールチェーンが実用的なことです。Lattice の公式 IDE を使わなくても、Yosys、nextpnr、Project IceStorm で開発できます。

Logic Cell

iCE40 の基本単位は Logic Cell です。 細かい構造は品種によって違いますが、入門としては次のように見れば十分です。

+------------------+
|      LUT4        |
|        |         |
|       DFF        |
|        |         |
|    MUX / Carry   |
+------------------+

LUT4 は 4 入力 1 出力の Look Up Table です。DFF は状態を持つためのフリップフロップです。MUX やキャリー回路は、選択回路や加算器を効率よく作るために入っています。これらがタイル状に大量に並び、周囲と配線でつながります。

Look Up Table

LUT は任意の組合せ回路を作るための小さなメモリです。

3 入力の組合せ回路を考えます。 入力の組み合わせは 23=8 通りです。

INOUT
000𝑂0
001𝑂1
010𝑂2
011𝑂3
100𝑂4
101𝑂5
110𝑂6
111𝑂7

この表の入力をアドレス、出力を値として見れば、1 bit x 8 の ROM とみなせます。 ROM に真理値表の出力値を順番に書き込むことで、任意の 3 入力組合せ回路になります。

iCE40 の LUT は基本的に 4 入力なので、任意の 4 入力 1 出力の組合せ回路を作れます。 5 入力以上の論理は、複数の LUT に分割されます。

たとえば Verilog でこう書いたとします。

assign y = (a & b) | (c & d);

これは 4 入力 1 出力の組合せ回路なので、1 個の LUT4 に入ります。

D-FlipFlop

DFF は 1 bit の状態を保持する素子です。

always @(posedge clk) begin
    q <= d;
end

Verilog でこう書くと、だいたい DFF が 1 個使われます。 カウンタなら、ビット数ぶんの DFF が並びます。

reg [23:0] counter = 0;

always @(posedge clk) begin
    counter <= counter + 1'b1;
end

この場合、24 bit の状態を持つので、最低でも 24 個の DFF が必要です。 加算部分は LUT とキャリー回路で作られます。

配線スイッチ

FPGA の面白いところは、LUT や DFF だけでなく、配線も書き換えられることです。

LUT の出力を隣の Logic Cell に送るのか、遠くの I/O ピンへ送るのか、Block RAM の入力へ送るのかを、内部のスイッチで切り替えます。

配置配線ツールは、このスイッチの組み合わせを決めます。 論理的には同じ回路でも、どの LUT に置くか、どの配線を通すかで、遅延や最大周波数が変わります。

FPGA のコンパイルがソフトウェアのコンパイルより遅いのは、この配置配線問題が重いからです。 プログラムを命令列にするだけではなく、物理的にどこに置くかまで決めています。

開発の流れ

iCE40 のオープンソース開発は、だいたい次の流れです。

ステップツール入出力
HDLVerilogtop.v
SynthesisYosystop.blif / top.json
Place and Routenextpnr-ice40top.asc
Packageicepacktop.bin
Uploadiceprog 等FPGA / Flash

昔からある Project IceStorm の流れでは、Yosys で BLIF を出して、arachne-pnr や nextpnr に渡し、最後に icepack でビットストリームへ変換します。 いま使うなら nextpnr-ice40 でよいと思います。

最小の L チカ

まずは LED 点滅です。

module top(
    input wire clk,
    output wire led
);
    reg [23:0] counter = 0;

    always @(posedge clk) begin
        counter <= counter + 1'b1;
    end

    assign led = counter[23];
endmodule

クロックをカウンタで分周し、上位ビットを LED に出しています。 FPGA 入門はだいたいこれです。 CPU の Hello World に相当します。

ピン配置は PCF (Physical Constraints File) に書きます。 ボードごとにピン番号は違うので、必ず回路図を確認します。

set_io clk 35
set_io led 37

コマンド

iCE40 HX8K の例です。 ボードやチップに合わせて --hx8k--lp8k--up5k などを変えます。

yosys -p "read_verilog top.v; synth_ice40 -top top -json build/top.json"

nextpnr-ice40 \
  --hx8k \
  --package ct256 \
  --json build/top.json \
  --pcf board.pcf \
  --asc build/top.asc

icepack build/top.asc build/top.bin
iceprog build/top.bin

TinyFPGA BX のようなボードでは、専用の書き込みツールを使うこともあります。 ここはボード依存です。

ツールチェーンが入っているなら Makefile にすると楽です。

TOP := top
BUILD := build

all: $(BUILD)/$(TOP).bin

$(BUILD):
	mkdir -p $(BUILD)

$(BUILD)/$(TOP).json: $(TOP).v | $(BUILD)
	yosys -p "read_verilog $<; synth_ice40 -top $(TOP) -json $@"

$(BUILD)/$(TOP).asc: $(BUILD)/$(TOP).json board.pcf
	nextpnr-ice40 --hx8k --package ct256 --json $< --pcf board.pcf --asc $@

$(BUILD)/$(TOP).bin: $(BUILD)/$(TOP).asc
	icepack $< $@

flash: $(BUILD)/$(TOP).bin
	iceprog $<

clean:
	rm -rf $(BUILD)

Block RAM

LUT でもメモリは作れますが、大きなメモリを LUT で作るのはもったいないです。 そこで FPGA には Block RAM があります。

iCE40 には 4 Kbit 単位の RAM ブロックがあります。 FIFO、フレームバッファ、小さな ROM、CPU の命令メモリなどに使います。

Verilog では、配列として書くと Block RAM に推論されることがあります。

reg [15:0] mem [0:255];

always @(posedge clk) begin
    if (we) begin
        mem[addr] <= wdata;
    end
    rdata <= mem[addr];
end

ただし、書き方によって LUT RAM になったり、Block RAM になったりします。 合成結果を見ます。 ここで「思ったより LUT が減ってない!」となるのは FPGA あるあるです。

PLL

FPGA ボードには、12 MHz、16 MHz、25 MHz などのクロックが載っています。 しかし、回路によっては別の周波数が欲しくなります。

そこで PLL (Phase Locked Loop) を使います。 入力クロックから、内部で別のクロックを作ります。

たとえば VGA なら 25.175 MHz 付近、UART ならボーレート生成用の分周、SoC なら CPU クロックなどです。

iCE40 では icepll で PLL 設定を生成できます。

icepll -i 12 -o 25

生成されたモジュールを Verilog に取り込んで使います。 PLL は便利ですが、クロックが増えるとタイミング制約も難しくなります。 最初は単一クロックで遊ぶのがおすすめです。

タイミング制約

FPGA は、クロック周期内に組合せ回路の遅延が収まる必要があります。 たとえば 25 MHz なら周期は 40 ns です。 この 40 ns の間に、DFF から出た信号が LUT や配線を通って、次の DFF に届く必要があります。

これを満たせないと、タイミングエラーになります。

ソフトウェアなら「遅いけど動く」で済むことが多いです。 FPGA では「たまに壊れる」になります。 これが怖い。

nextpnr は最後に最大周波数やクリティカルパスを表示します。 タイミングが厳しいときは、組合せ回路を短くする、パイプラインを入れる、クロックを下げる、配置を見直す、などを考えます。

まとめ

iCE40 は、FPGA の構造と開発の流れを理解するのにちょうどよいデバイスです。

  • FPGA は LUT、DFF、配線スイッチの集合
  • LUT は小さな真理値表
  • DFF は状態を持つ
  • Yosys で合成し、nextpnr で配置配線し、icepack でビットストリームにする
  • Block RAM や PLL を使うと、CPU や映像出力も作れる
  • タイミング制約を満たさないと「たまに壊れる」

Verilog を書いて LED が点滅しただけでも、実際には合成、配置、配線、ビットストリーム生成、コンフィグレーションが全部走っています。 見た目は L チカ、裏側はかなり壮大です。

参考