Altera DE0で(大げさな)Lチカその1

さて、HW界のHello World、LチカをFPGA&Verilogでやってみる。
今回は単純に「ボタンを押したら、LEDがつく」だ。
これは、本当に単純に、ボタンの入力を受けるピンとLEDに出力するピンをつなぐだけでよい。


module LEDBlink(
input in_clk,
input [9:0] in_switch,
input [2:0] in_button,
output [9:0] out_led,
output[7:0] seven_segment_0,
output[7:0] seven_segment_1,
output[7:0] seven_segment_2,
output[7:0] seven_segment_3);
// wires and regs
assign out_led[0] = ~in_button[0];
endmodule


あまりに単純なので、少し細工をする。
このようなスイッチやボタンは本当に機械接点であるため、スイッチ・ボタンをオンオフする際にバネの接点が開いたり閉じたりするため、その反動で意図しないオンオフを繰り返す。
チャタリングと呼ばれるものだ。
これを論理回路に直接接続すると、意図しないオンオフのために意図しない動作をしてしまう。
これを取り除く回路をアンチ・チャタリング回路と呼ぶのだ。
これはアナログ的にはLPFを組んでやればよい。
デジタルの場合には、低速のクロックで(ダウン)サンプリングすればよい、デジタル界のLPFだ。
大げさにも、これをLチカに接続する。

メインのモジュールは、1kHzのクロックからアンチ・チャタリング用の200Hz(5msecでフィルタリングする)のクロックを生成したうえで、200Hzのクロックでアンチ・チャタリングをする。


module LEDBlink(
input in_clk,
input [9:0] in_switch,
input [2:0] in_button,
output [9:0] out_led,
output[7:0] seven_segment_0,
output[7:0] seven_segment_1,
output[7:0] seven_segment_2,
output[7:0] seven_segment_3);
// wires and regs
wire reset;
wire clk_1kHz;
wire clk_200Hz;
wire ac_q;
// switch 0 for reset
assign reset = in_switch[0];
// 1kHz clk
clk_scaler_1kHz clk_scaler_1kHz(in_clk, reset, clk_1kHz);
// 200Hz clk for anti chatter, pulse width of clk_200Hz is 1/1kHz = 1msec
clk_scaler_from_1kHz #(5) clk_scaler_200Hz(in_clk, reset, clk_1kHz, clk_200Hz);
// anti chatter for in_button[0]
anti_chatter ac_button1(clk_200Hz, reset, ~in_button[0], ac_q);
// output
assign out_led[0] = ac_q;
endmodule
次に1kHzのクロック生成モジュール、こちらは同期の順序回路で書かれている。

module clk_scaler_1kHz(input in_clk_50MHz, input in_reset, output out_clk_1kHz);
// wires and regs
reg [15:0] _16bit_counter;
//
assign out_clk_1kHz = (_16bit_counter == 16'd49_999)? 1'b1: 1'b0; // count 50MHz clk x 50,000 = 1kHz
//
always @(posedge in_clk_50MHz or posedge in_reset)
begin
if (in_reset == 1'b1)
begin
_16bit_counter <= 16'd0;
end
else
begin
if (out_clk_1kHz == 1'b1)
begin
_16bit_counter <= 16'd0;
end
else
begin
_16bit_counter <= _16bit_counter + 16'd1;
end
end
end
endmodule
お次は1kHzから200Hzを作るスケーラー、こちらもAltera DE0の内臓クロックである50MHzと同期させている。

module clk_scaler_from_1kHz(input in_clk_50MHz, input in_reset, input in_clk_1kHz, output out_clk);
// parameter
parameter scaling_factor = 100; // shall be 4096 or smaller, 2 or greater
// wires and regs
reg [11:0] _12bit_counter;
//
assign out_clk = (_12bit_counter == scaling_factor - 12'd1)? 1'b1: 1'b0;
//
always @(posedge in_clk_50MHz or posedge in_reset)
begin
if (in_reset == 1'b1)
begin
_12bit_counter <= 12'd0;
end
else
begin
if (in_clk_1kHz == 1'b1)
begin
if (out_clk == 1'b1)
begin
_12bit_counter <= 12'd0;
end
else
begin
_12bit_counter <= _12bit_counter + 12'd1;
end
end
end
end
endmodule
最後にアンチ・チャタリングモジュール。
こちらはAltera DE0の内臓クロックである50MHzとは同期せず、分周された200Hzのクロック・ドメインのみで動作する。
このほうが簡便だし消費電力は下がるが、高速な回路と接続する場合にはハザードがでるので、出力を高速な回路のクロックと同期させる必要がある。
今回はこのまま出力なので、これで問題ない。
内容は簡単で、非常に教科書的にノンブロッキング文で2回ラッチしてそのアンドを取るようにしている。
つまりクロック周期以下の0->1、1->0遷移は無視されるのだ。

module anti_chatter(input in_clk, input in_reset, input in_signal, output out_q);
// wires and regs
reg [1:0] reg_anti_chatter;

always @(posedge in_clk or posedge in_reset)
begin
if (in_reset == 1'b1)
reg_anti_chatter = 2'b0;
else
begin
reg_anti_chatter[0] <= in_signal;
reg_anti_chatter[1] <= reg_anti_chatter[0];
end
end

//output
assign out_q = reg_anti_chatter[0] & reg_anti_chatter[1];

endmodule

RTLシミュレーションの結果はこちら。

これは下記の条件のテストベンチで取った。

always #5 in_clk = ~in_clk;
initial
begin
// code that executes only once
// insert code here --> begin
// --> end
$display("Running testbench");
in_clk = 1;
in_reset = 1;
in_signal = 0;
#13
in_reset = 0;
#18
in_signal = 1;
#2
in_signal = 0;
#3
in_signal = 1;
#42
in_signal = 0;
#1
in_signal = 1;
#1
in_signal = 0;
#2
in_signal = 1;
#2
in_signal = 0;
#4
in_signal = 1;
#7
in_signal = 0;
#20
$finish;
end