Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ACRi ブログ「DA コンバータがなくてもできる FPGA ピアノ (2)」のコード
`default_nettype none
module pwm_sound
#(
parameter real CLK_FREQ = 100e6, // クロック周波数 (Hz)
parameter int COUNT_WIDTH = 32 // カウンタビット幅
)
(
input wire clk,
input wire [3:0] btn_in, // ボタン入力
input wire [3:0] sw_in, // スライドスイッチ入力
output logic led_out,
output logic pulse_out = 1'b0
);
localparam real BASE_FREQ = 442.0; // 基準音の周波数
localparam bit [COUNT_WIDTH-1:0] INCS[4] = '{ // カウンタの増分値
n2inc(-4), n2inc(-5), n2inc(-7), n2inc(-9) // ファ,ミ,レ,ド
};
logic [COUNT_WIDTH-1:0] count = '0; // カウンタ
logic [COUNT_WIDTH-1:0] duty_reg = '0; // デューティレジスタ
always @(posedge clk) begin
if (btn_in == '0)
count <= '0; // どのボタンも押されていなければカウンタをクリア
else begin
for (int i = 0; i < 4; i++) begin
if (btn_in[i]) begin
count <= count + INCS[i]; // 押されたボタンに対応する増分値を加える
break;
end
end
end
end
always @(posedge clk) begin
if (btn_in == '0) // どのボタンも押されていないときだけ
duty_reg[(COUNT_WIDTH-1)-:4] <= sw_in; // 上位4ビットを更新
end
always @(posedge clk) begin
if (btn_in != '0 && count < duty_reg) // PWM 発生用の比較器
pulse_out <= 1'b1;
else
pulse_out <= 1'b0;
end
assign led_out = pulse_out; // 確認用 LED 出力
// 基準音の距離からカウンタの増分値を計算する関数
function bit [COUNT_WIDTH-1:0] n2inc(int n);
return ((2.0 ** (COUNT_WIDTH + n / 12.0)) * BASE_FREQ / CLK_FREQ);
endfunction
endmodule
`default_nettype wire
`default_nettype none
module pwm_sound_artytop
(
input wire clk,
input wire [3:0] btn_in,
input wire [3:0] sw_in,
output logic led_out,
output logic pulse_out
);
localparam real CLK_FREQ = 100e6; // クロック周波数: 100MHz
localparam real STABLE_TIME = 1e-3; // 安定判定時間: 1 ms
logic [3:0] btn_sync;
logic [3:0] sw_sync;
for (genvar i = 0; i < 4; i++) begin
// シクロナイザつきデバウンサ
sync_and_debounce
#(
.CLK_FREQ(CLK_FREQ),
.STABLE_TIME(STABLE_TIME)
)
sync_and_debounce_btn
(
.clk(clk),
.din(btn_in[i]),
.dout(btn_sync[i])
);
sync_and_debounce
#(
.CLK_FREQ(CLK_FREQ),
.STABLE_TIME(STABLE_TIME)
)
sync_and_debounce_sw
(
.clk(clk),
.din(sw_in[i]),
.dout(sw_sync[i])
);
end
// PWM回路
pwm_sound
#(
.CLK_FREQ(CLK_FREQ),
.COUNT_WIDTH(32)
)
pwm_sound_inst
(
.clk(clk),
.btn_in(btn_sync),
.sw_in(sw_sync),
.led_out(led_out),
.pulse_out(pulse_out)
);
endmodule
`default_nettype wire
## Clock signal
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { clk }];
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { clk }];
## LED
set_property -dict { PACKAGE_PIN H5 IOSTANDARD LVCMOS33 } [get_ports { led_out }];
## Buttons
set_property -dict { PACKAGE_PIN D9 IOSTANDARD LVCMOS33 } [get_ports { btn_in[0] }];
set_property -dict { PACKAGE_PIN C9 IOSTANDARD LVCMOS33 } [get_ports { btn_in[1] }];
set_property -dict { PACKAGE_PIN B9 IOSTANDARD LVCMOS33 } [get_ports { btn_in[2] }];
set_property -dict { PACKAGE_PIN B8 IOSTANDARD LVCMOS33 } [get_ports { btn_in[3] }];
## Switches
set_property -dict { PACKAGE_PIN A8 IOSTANDARD LVCMOS33 } [get_ports { sw_in[0] }]; #IO_L12N_T1_MRCC_16 Sch=sw[0]
set_property -dict { PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports { sw_in[1] }]; #IO_L13P_T2_MRCC_16 Sch=sw[1]
set_property -dict { PACKAGE_PIN C10 IOSTANDARD LVCMOS33 } [get_ports { sw_in[2] }]; #IO_L13N_T2_MRCC_16 Sch=sw[2]
set_property -dict { PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports { sw_in[3] }]; #IO_L14P_T2_SRCC_16 Sch=sw[3]
## Pmod Header JA
set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { pulse_out }];
`default_nettype none
`timescale 1ns/1ps
module sim_pwm_sound_artytop;
localparam real CLOCK_PERIOD_NS = 10;
localparam int N = 2000000;
logic clk, pulse_out, led_out;
logic [3:0] btn_in, sw_in;
pwm_sound_artytop pwm_sound_artytop_inst
(
.clk(clk),
.btn_in(btn_in),
.sw_in(sw_in),
.led_out(led_out),
.pulse_out(pulse_out)
);
initial begin
clk = 1'b0;
forever begin
#(CLOCK_PERIOD_NS / 2.0)
clk = 1'b1;
#(CLOCK_PERIOD_NS / 2.0)
clk = 1'b0;
end
end
initial begin
btn_in = 4'b000;
sw_in = 4'b1000;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0001;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0010;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0100;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b1000;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0000;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0001;
#(CLOCK_PERIOD_NS * 100)
btn_in = 4'b0000;
sw_in = 4'b0100;
#(CLOCK_PERIOD_NS * 200)
btn_in = 4'b0001;
#(CLOCK_PERIOD_NS * N)
sw_in = 4'b0000;
$finish;
end
endmodule
`default_nettype wire
`default_nettype none
module sync_and_debounce
#(
parameter real CLK_FREQ = 100e6, // クロック周波数 (Hz)
parameter real STABLE_TIME = 1e-3 // 安定したと判定するまでの時間 (秒)
)
(
input wire clk,
input wire din,
output logic dout = 1'b0
);
// 2段のシフトレジスタによるシンクロナイザ
logic din_meta = 1'b0, din_sync = 1'b0;
always @(posedge clk) begin
din_meta <= din; // メタステーブルの危険あり
din_sync <= din_meta; // 同期化された入力信号
end
// デバウンス用カウンタのビット幅と最大値
localparam int COUNT_WIDTH = $clog2(int'(STABLE_TIME * CLK_FREQ));
localparam bit [COUNT_WIDTH-1:0] MAX_COUNT = STABLE_TIME * CLK_FREQ - 1;
// カウンタ
logic [COUNT_WIDTH-1:0] count = '0;
always @(posedge clk) begin
if (din_sync != dout) begin // 入力と出力が異なるならカウンタを進める
if (count == MAX_COUNT)
count <= '0;
else
count <= count + 1'b1;
end
else // 入力と出力が同じならカウンタをクリア
count <= '0;
end
// 出力レジスタ
always @(posedge clk) begin
// カウンタが最大値まで達したら入力の変化を出力に伝える
if (din_sync != dout && count == MAX_COUNT)
dout <= din_sync;
end
endmodule
`default_nettype wire
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment