← 講義ポータルへ戻る 第6回・第7回講義:実装・応用

第5章:作って遊ぶ ― スイッチ・LED・7セグメント

第4章で「書く → ピンを割り当てる → 書き込む → 動く」の流れを一巡しました。本章はその流れに乗って、Basys 3 に載っている部品――スイッチ・16個のLED・7セグメント表示器――を使い、いろいろな回路を「作って遊ぶ」段階です。最後に、FPGAで“アナログ”に触れる話(PWMで明るさを変える)にも踏み込みます。手を動かして、FPGAの楽しさを掴みましょう。

進め方: 題材は4つ。(1)スイッチでLEDを点ける、(2)複数LEDで数を見る、(3)7セグメントに数字を出す、(4)PWMで明るさを変える。それぞれ第4章と同じVivadoの手順(ソース追加→合成→書き込み)で動かせます。手順は第4章を参照し、本章はRTLの中身に集中します。

5.1 スイッチでLEDを点ける ― クロックの要らない回路

まずは一番やさしい題材。スイッチを上げるとLEDが点くだけの回路です。第4章のLチカと違い、ここにはクロックもカウンタもありません。スイッチの状態をそのままLEDにつなぐ「組み合わせ回路」です。

`default_nettype none

module sw_led (
    input  wire [15:0] sw,    // 16個のスライドスイッチ
    output wire [15:0] led    // 16個のLED
);
    // スイッチの状態を、そのままLEDへ配線するだけ
    assign led = sw;

endmodule

16本まとめて扱う

[15:0] は「16本の信号の束」。スイッチ16個・LED16個を、1本ずつ書かずにまとめて宣言できます。assign led = sw; で16組を一気に直結。

クロック不要=組み合わせ回路

記憶(フリップフロップ)も時間の概念もなく、入力(スイッチ)がそのまま出力(LED)を決めます。これを組み合わせ回路と呼びます。LチカやカウンタのようにクロックでFFを動かす回路は順序回路です。

※ピン制約はマスターXDCの“コメント外し”が便利: Digilent公式のBasys 3 マスターXDCには、sw[0..15]led[0..15] の行が最初から(コメント状態で)入っています。使う行の先頭の # を外して取り込むのが手早く確実です。信号名(swled)はモジュールのポート名と一致させてください。

5.2 複数LEDで「数」を見る ― バイナリカウンタ

次は、第4章のカウンタを16個のLEDに広げて、数が増えていく様子を“見る”題材です。カウンタの上位16ビットをLEDにつなぐと、2進数の数えあげ(オドメーター)が目に見えます。

`default_nettype none

module led_counter (
    input  wire        clk,    // 100MHz(W5)
    input  wire        rst,    // リセット(btnC:U18)
    output wire [15:0] led     // 16個のLED
);
    reg [31:0] counter;

    always @(posedge clk) begin
        if (rst) counter <= 32'd0;
        else     counter <= counter + 1'b1;
    end

    // 上位16ビットをLEDへ。下のLEDほど速く、上のLEDほどゆっくり変化する。
    assign led = counter[31:16];

endmodule
   16個のLEDに counter[31:16] を出すと…
   led[0]  = counter[16]  速く点滅(一番下の桁)
   led[1]  = counter[17]  その半分の速さ
     :              :
   led[15] = counter[31]  数十秒に1回(一番上の桁)
   ─────────────────────────────────────────────
   → 2進数の「桁上がり」がLEDの列で見える=数えあげのオドメーター

遊びどころ: counter[31:16] の範囲を変えると、見える速さが変わります。led = counter[27:12] なら全体に速く、counter[31:16] よりさらに上を使えばゆっくり。「どのビットを見せるか」で表情が変わるのを試してみてください。

5.3 7セグメントに数字を出す ― ダイナミック点灯

Basys 3 には4桁の7セグメント表示器が載っています。ここに数字を出します。少しだけ仕組みが必要ですが、つまずきやすい点を押さえれば大丈夫です。先に2つの“クセ”を知っておきましょう。

クセ1:アクティブLow(0で点灯)

Basys 3 の7セグは、各セグメント(a〜g)も桁選択(an)も、0を出すと有効(点灯/選択)です。1が消灯。直感と逆なので注意します。

クセ2:4桁は「同時に出せない」

4つの桁はセグメントの配線を共有しています。そこで、1桁ずつ高速で順番に点ける(ダイナミック点灯)。速く切り替えれば、人の目には4桁が同時に光って見えます。

スイッチの16ビット値を、4桁の16進数(0〜F)で表示する回路です。

`default_nettype none

module seg7 (
    input  wire        clk,    // 100MHz(W5)
    input  wire [15:0] sw,     // 表示する16ビット値(スイッチ)
    output reg  [6:0]  seg,    // セグメント a..g(アクティブLow)
    output reg  [3:0]  an      // 桁選択(アクティブLow)
);
    // 桁を高速で切り替えるためのカウンタ
    reg [16:0] refresh;
    always @(posedge clk) refresh <= refresh + 1'b1;
    wire [1:0] sel = refresh[16:15];   // 約1.3msで1周(4桁を巡回)

    // いま点ける桁を選び、その桁に出す4ビットを取り出す
    reg [3:0] nibble;
    always @(*) begin
        case (sel)
            2'd0: begin an = 4'b1110; nibble = sw[3:0];   end // 右端の桁
            2'd1: begin an = 4'b1101; nibble = sw[7:4];   end
            2'd2: begin an = 4'b1011; nibble = sw[11:8];  end
            2'd3: begin an = 4'b0111; nibble = sw[15:12]; end // 左端の桁
        endcase
    end

    // 4ビット(0..F) → 7セグのパターン(0が点灯。bitはg,f,e,d,c,b,aの順)
    always @(*) begin
        case (nibble)
            4'h0: seg = 7'b1000000;  4'h1: seg = 7'b1111001;
            4'h2: seg = 7'b0100100;  4'h3: seg = 7'b0110000;
            4'h4: seg = 7'b0011001;  4'h5: seg = 7'b0010010;
            4'h6: seg = 7'b0000010;  4'h7: seg = 7'b1111000;
            4'h8: seg = 7'b0000000;  4'h9: seg = 7'b0010000;
            4'hA: seg = 7'b0001000;  4'hB: seg = 7'b0000011;
            4'hC: seg = 7'b1000110;  4'hD: seg = 7'b0100001;
            4'hE: seg = 7'b0000110;  4'hF: seg = 7'b0001110;
        endcase
    end
endmodule

つまずきどころ: 「全部のセグメントが薄く点く/ちらつく」ときは、refresh の切替が遅すぎる(ちらつき)か速すぎる(残像)可能性があります。sel に使うビット位置を1〜2ビット調整してみてください。「特定の桁だけ妙に暗い」場合は、ダイナミック点灯で1桁あたりの点灯時間が短いのが原因で、正常な挙動です。

5.4 FPGAで“アナログ”に触れる ― PWMで明るさを変える

ここまではLEDの「点く/消える」だけでした。でも、LEDの明るさを滑らかに変えることもできます。FPGAの出力はデジタル(0か1)なのに、なぜでしょう。答えが PWM(パルス幅変調) です。

まず正確に: FPGA本体(Artix-7のロジック)はデジタル回路です。「FPGAでアナログ」と言うとき、実際には (1) デジタル出力で“それっぽい”アナログを作る(PWM)(2) 専用のADCでアナログ電圧を数字として取り込む(XADC)、という橋渡しを指します。FPGAそのものがアナログ信号を直接作るわけではありません。

PWMの考え方: LEDを「速く点けたり消したり」して、点いている時間の割合(デューティ比)を変えます。点灯時間が長ければ明るく、短ければ暗く見えます。速く切り替えるので、人の目にはちらつきでなく「明るさ」として見えます。

`default_nettype none

module pwm_led (
    input  wire       clk,    // 100MHz(W5)
    input  wire [3:0] sw,     // 明るさ設定(0=消灯 〜 15=最も明るい)
    output wire       led     // 明るさを変えるLED
);
    // 0〜15を高速で巡回するカウンタ(PWMの“ものさし”)
    reg [3:0] cnt;
    always @(posedge clk) cnt <= cnt + 1'b1;

    // cnt が sw より小さい間だけ点灯 → 点灯割合(デューティ)= sw/16
    assign led = (cnt < sw);

endmodule
   PWM:点いている時間の割合で明るさが決まる
   sw=4  ▇▁▁▁▇▁▁▁▇▁▁▁   点灯1/4 → 暗い
   sw=8  ▇▇▁▁▇▇▁▁▇▇▁▁   点灯1/2 → 中くらい
   sw=12 ▇▇▇▁▇▇▇▁▇▇▇▁   点灯3/4 → 明るい
        (切り替えは約6MHzと高速なので、目には“明るさ”に見える)

スイッチ4本(sw[3:0])で0〜15を指定すると、LEDの明るさが16段階で変わります。これが、デジタルしか出せないFPGAでアナログ的な表現を作る、基本テクニックです。

💡 もっと先へ:自動で明るさを変える「呼吸するLED」/本物のアナログ入力(XADC)/信号処理

呼吸するLED: 明るさ設定(上のコード sw の部分)を、スイッチではなく「ゆっくり増えて・ゆっくり減る」カウンタにすると、LEDがふわっと明るくなって暗くなる“呼吸”のような動きになります。第4章のカウンタと、本節のPWMを組み合わせるだけです。

本物のアナログ入力(XADC): Artix-7 には XADC というアナログ-デジタル変換器が内蔵されており、Basys 3 ではアナログ入力対応のPmodポートから外部のアナログ電圧(センサーの出力など)を数字として取り込めます。取り込んだあとはデジタル信号として、しきい値判定や表示に使えます。配線とIP設定が必要なので、本講座では「こういう入口がある」という紹介にとどめます。

信号処理が得意な理由: 取り込んだ信号にフィルタをかけたり周波数を調べたりする計算を、FPGAは多数同時に(並列で)こなせます。第1章で触れた DSPスライス(掛け算・足し算の専用回路が90個)が、ここで活躍します。CPUが1つずつ順番に計算するのに対し、FPGAは「掛け算器をたくさん並べて一斉に動かす」ことができるため、リアルタイムの信号処理で重宝されます。これはFPGAの大きな強みの一つです。

5.5 この章のまとめ

部品を使いこなして「作って遊ぶ」感覚が掴めてきたら、いよいよ最終段階です。次の第6章では、RISC-VのCPU(PicoRV32)をFPGAに載せ、その“中身の仕組み”を見ながら、自分のFPGAの上で本物のCPUを走らせます。本講座の醍醐味です。