第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] の行が最初から(コメント状態で)入っています。使う行の先頭の # を外して取り込むのが手早く確実です。信号名(sw・led)はモジュールのポート名と一致させてください。
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
- 桁の巡回(
sel): 100MHzをrefreshで数え、その上位2ビットで「いま何桁目を点けるか」を約1.3ms周期で巡回させます。速いので、4桁すべてが光って見えます。 - 桁選択(
an): アクティブLowなので、点ける桁だけ0。例:4'b1110は右端の桁(an[0]=0)だけ有効。 - セグメント表(
case): 0〜Fの各数字に対し、どのセグメントを点けるかを表で持ちます(これはLUTそのもの。第1章の「表を引く部品」が、ここで活躍します)。0が点灯なので、たとえば「8」は全点灯で7'b0000000。 - ピン: マスターXDCの
seg[0..6]・dp・an[0..3]の行のコメントを外して使います(本講座ではdpは未使用でも可)。
つまずきどころ: 「全部のセグメントが薄く点く/ちらつく」ときは、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 この章のまとめ
- 組み合わせ回路: スイッチ→LED直結で、クロックの要らない回路を書いた。
- 順序回路の応用: カウンタを16個のLEDに広げ、2進数の数えあげを“見た”。
- 7セグメント: アクティブLowとダイナミック点灯という2つのクセを押さえ、4桁に16進数を表示した。表引き(LUT)が活きた。
- アナログへの橋渡し: PWMでLEDの明るさを16段階に変えた。XADC・DSPによる入力/信号処理も概念として知った。
部品を使いこなして「作って遊ぶ」感覚が掴めてきたら、いよいよ最終段階です。次の第6章では、RISC-VのCPU(PicoRV32)をFPGAに載せ、その“中身の仕組み”を見ながら、自分のFPGAの上で本物のCPUを走らせます。本講座の醍醐味です。