1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

FIRMから入ったFPGA初学者がVerilogに抱いた疑問をまとめてみた

1
Posted at

FIRMから入ったFPGA初学者がVerilogに抱く疑問

ファームウェア(C / Python等)から入ったFPGA初学者が「なんでこう書くの?」と思ったポイントを整理しました。間違いがありましたら、コメントいただけると幸いです

■ verilogでの@ とは何か:「イベント待ち」の正体

@ は言語ごとに役割が異なりますよね
Pythonではデコレータ、Javaではアノテーション、、、、
Verilogではイベント制御で使われます

@ は「変化した瞬間だけ反応する仕組み」

@(posedge clk)

これは「クロックが 0 → 1 に変化した瞬間だけ反応する」という意味です。

  • 「1である状態(レベル)」ではなく「変化(エッジ)」を捉えるものです
  • @ はクロック専用ではなく、任意の信号の変化(エッジ)に使えます

瞬間 vs 継続

Verilog(FPGA) を理解するうえで最も重要な概念だと思います
同じalways構文でも、@がついたりつかなかったり...ややこしくないですか?
私は、エッジで変化するものは@がつき、レベルで変化するものはつけないという考えで腑に落ちました

種類 動作 概念
@ が付く場合(always_ff) 変化した瞬間だけ反応 瞬間(エッジ)
@がつかない(always_comb / assign) 条件が成立している間ずっと有効 継続(レベル)

「継続」の例

assign y = a & b;

a=1, b=1 の間はずっと y=1。a または b が変わると即座に更新される。
状態が続く限り、出力が決まり続けるのが継続の特徴です。

「瞬間」の例

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

clk が立ち上がった「瞬間」だけ実行されます。
その瞬間に d を取り込み、q を更新します。

CPUでいう「今このタイミングで保存する」という動作に近く、
FPGAではレジスタ(フリップフロップ)を作る基本形です。


wait との違い

@wait はどちらもイベント待ちですが、本質が異なります。
wait は「条件が成立するまで処理を停止する」という書き方で、
Verilogでは主にテストベンチ(シミュレーション)で使われます。

@(a)         // a が変化した瞬間だけ反応
wait(a == 1); // a が 1 の状態である間ずっと成立
構文 動作
@(a) a が変化した時だけ反応
wait(a == 1) a がすでに 1 なら即通過。1 の間ずっと成立

@(a or b) に and がない理由

最初はorがあるのだから、andもあるんじゃないの?って思いませんでしたか?

@(a or b)   // OK
@(a and b)  // 存在しない(構文エラー)

理由:イベントは「瞬間」なので、2つの信号が完全に同時に変化することを保証できない

  • OR = どちらかが起きたらOK → 保証できる
  • AND = 両方が同時 → 物理的に保証不可能

AND 相当の条件を書きたい場合は、中に条件として書きます:

always_ff @(posedge clk) begin
    if (a && b)   // 条件は中に書く(状態として扱う)
        y <= 1;
end

■ always って関数みたいなものではないの?

よく出てくるalwaysってブロック単位のように見えますし、関数みたいなものでは?って思いませんでしたか?

always = 回路そのもの

always は「関数」でも「ループ」でもなく、常時動作する回路の定義です。

always + タイミング + 処理
always_ff @(posedge clk) begin  // 順序回路 + clk 立ち上がりエッジ
    q <= d;                      // 処理内容
end
キーワード 意味
always_ff 順序回路(FF)の宣言
always_comb 組み合わせ回路の宣言
@(...) いつ動くか(瞬間)

always_comb に @ が不要な理由:イベント(瞬間)ではなく状態(継続)を扱うため、「いつ」を指定する必要がないからです。


■ = と <= の違い

=は代入演算子で、<= は比較演算子と思いませんでしたか?
「え、<= って比較じゃないの!?」って。
でもVerilogでは比較ではなく、「クロック後にまとめて代入する」という意味なんです。
最初はかなり脳がバグります。

= は「逐次処理(上から順に実行)」
<= は「同時更新(並列評価してまとめて更新)」

ブロッキング代入(=)

a = b;
c = a;  // すでに更新された a を使う

→ c は 新しい a の値になる

ノンブロッキング代入(<=)

a <= b;
c <= a;  // 元の a(更新前)を使う

→ 全右辺を先に評価してからまとめて更新
→ c は 古い a の値になる


なぜ2種類あるのか

ハードウェアの動作モデルに合わせるためです。

回路種別 使う代入 理由
組み合わせ回路 = 入力が変わったら即反映
順序回路(FF) <= クロックで同時更新

迷ったらこのルールだけ守ればOK:

always_comb → =
always_ff   → <=

■ @ と <= の関係

この2つはよく一緒に登場しますが、別概念です。

概念 意味
@ いつ更新するか(タイミング)
<= どう更新するか(並列)
always_ff @(posedge clk) begin
//         ↑ いつ(瞬間)     ↓ どう(同時更新)
    q <= d;
end

■ reg / logic の正体

reg はレジスタではない

reg って書いてあるので、最初は
「なるほど、レジスタを作る型ね!」と思うんですよね。
でも実際には、reg と書いただけではレジスタにはなりません。

  • reg → always 内で代入できる変数型(FF になるかは書き方次第)
  • wire → assign や外部出力の接続に使う型
  • logic → SystemVerilog の統合型(reg / wire を一本化)

FF になるかどうかは always の書き方 で決まります:

// これは FF になる(posedge clk でトリガ)
always_ff @(posedge clk)
    q <= d;

// これは組み合わせ回路になる(イベントトリガなし)
always_comb
    y = a & b;

回路種類は「変数の型」ではなく「always の書き方」で決まる


■ 基本回路パターン

Verilogは文法だけ覚えても、なかなか回路として読めるようになりませんでした。
大事なのは、この書き方をすると、ハード的には何ができるのかを考えることだと思います。
実際に配置配線させて見てもいいと思います。

特にFPGAでは、よく使う回路パターンが何度も登場します。
最初は呪文に見えても、「あ、これMUXか!」と読めるようになると、一気に理解しやすくなります。

● MUX(セレクタ)

assign y = sel ? a : b;

複数の入力から1つ選ぶ回路。


● FF(フリップフロップ)

always_ff @(posedge clk)
    q <= d;

値をクロック単位で保持する回路。


● カウンタ

always_ff @(posedge clk)
    cnt <= cnt + 1;

分解すると:

  • FF:現在値を保持
  • 加算器:+1 を計算して次の値を作る

カウンタ = FF(状態保持) + 演算回路(次状態計算)


● enable 付きレジスタ

always_ff @(posedge clk) begin
    if (en)
        q <= d;
    else
        q <= q;  // 保持
end

更新するか保持するかを MUX で選ぶ構造。


● MUX + FF

enable 付きレジスタと同じようなパターンですが。

always_ff @(posedge clk) begin
    if (sel)
        q <= a;
    else
        q <= b;
end
  • if → MUX(どちらを選ぶか)
  • <= + always_ff → FF(クロックで保持)

このパターンで「出力が必ず1つに決まる」が保証されます。


よくあるバグ集

Verilog初心者が一度は踏むあるあるバグを紹介します。
しかも厄介なのが、シミュレーションでは動いて見えること。。。

C言語感覚のまま書くと、FPGA特有の「同時に動く世界」にやられます。
私も最初、「なんで実機だけ変なんだ…?」を何回もやりました。

❌ 順序回路で = を使う

always @(posedge clk) begin
    a = b;
    c = a;  // シミュ:新しい a / 実ハード:同時更新なので挙動が変わる
end

シミュレーションでは動いても実機で不一致が起きる

✅ 正しい書き方

always @(posedge clk) begin
    a <= b;
    c <= a;  // 古い a を使う → ハードの挙動と一致
end

❌ always_comb で <= を使う

always_comb begin
    a <= b;  // 同時更新という概念がない / ツールによってはエラー
end

✅ 正しい

always_comb begin
    a = b;
end

■ 初学者あるある

PGAは「プログラム」ではなく「回路」を書く世界でなんですね。
既出の内容もありますが、再度初学者が一度は踏む Verilogあるある をまとめます。

① if は分岐だと思う

実際は MUX。複数の候補から1つ選ぶ回路の記述。


② 上から順に処理されると思う

実際は並列動作。ハードウェアは全ブロックが同時に動く。


③ reg はレジスタだと思う

ただの変数型。FF になるかは always の書き方次第。


④ 分岐網羅を忘れて、ラッチを生成させてしまう

always @(*) begin
    if (en)
        y = a;  // else がない!
end

else がないと「en=0 のときどうするか」が未定義 → ラッチが自動生成されます
system_verilogでは改善された構文が用意されています

修正:以下のように条件網羅させないといけません

always @(*) begin
    if (en)
        y = a;
    else
        y = 0;  // 必ず全条件に代入する
end

⑤ とりあえず全部に reset を入れてしまう

最初の頃、私も「レジスタには全部 reset 必須」と思っていました。
昔のVHDL文化の影響もあり、とにかく全部初期化したくなるんですよね。

always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
        data <= 0;
    else
        data <= next_data;
end

これはfirm出身者とか直接違う話ですが。
最近のFPGAでは、必要ないレジスタにまで reset を入れるのは、あまり推奨されません。
理由は単純で、reset配線が増えるとファンアウトが巨大になり、配置配線が苦しくなる、
タイミング悪化の原因になるからです。

データパス系は、初回だけ無効化すれば十分なことも多く、「本当に reset が必要な制御系だけ入れる」という設計が増えています。
つまり、reset は“とりあえず全部入れるもの”ではないというのは、FPGA設計で重要な考え方です。


⑥ 複数ドライバ

always_ff @(posedge clk) q <= a;
always_ff @(posedge clk) q <= b;  // 同じ q に 2 か所から代入!

「FF が 2 個できるだけだからいいのでは?」と思うかもしれませんが:
1つのレジスタに複数ドライバと解釈され、競合・合成エラーとなります

修正:

always_ff @(posedge clk) begin
    if (sel)
        q <= a;
    else
        q <= b;  // MUX で 1 つに統合
end

組み合わせ回路も同様です:

always_comb y = a;
always_comb y = b;  // NG:ドライバ競合

⑦ #delay が実機で動くと思う

→ 最初、firmの感覚で使っていましたが、効果ありませんでした。
シミュレーション専用。実機では無視されます。


⑧ シミュレーション = 実機だと思う

別物です。代表的な不一致は以下の通りです:

  • = の使い方によるタイミング差
  • 初期値の扱い(シミュは 0 スタートが多いが実機は不定)
  • 意図しないラッチ・複数ドライバはシミュでは気づかず実機で壊れる

■ まとめ:設計のルール化

  • 1信号 = 1ドライバ(複数 always から同じ信号に代入しない)
  • 出力は必ず1つに決まる構造にする
  • 状態は FF で持つ
  • 組み合わせ回路は全条件に代入する(ラッチ防止)

■ 感想

最初、Verilogは「C言語っぽい見た目の別物」だと感じました。
文法自体はそこまで難しくないのに、考え方が根本から違います。

特に混乱したのは、「順番に実行される世界」ではなく、「全部同時に存在する回路を書く世界」だということでした。
if は分岐ではなくMUX、always は処理ではなく回路、<= は比較ではなく同時更新。
ソフトウェア感覚で読むと、かなり脳が混乱します。

ただ、ある時から「これはプログラムではなく、回路図を文章で書いているだけなんだ」と考えるようになって、一気に理解しやすくなった気がします。

この記事が、同じように firmware / software 側から FPGA に入った人の助けになれば嬉しいです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?