Skip to content

Instantly share code, notes, and snippets.

@yuichiro-shibata
Created May 12, 2021 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuichiro-shibata/1adf92737e6cbb7364cc437927bc08f6 to your computer and use it in GitHub Desktop.
Save yuichiro-shibata/1adf92737e6cbb7364cc437927bc08f6 to your computer and use it in GitHub Desktop.
ACRi ブログ「DA コンバータがなくてもできる FPGA ピアノ (3)」のコード
`default_nettype none
module delta_sigma
#(
parameter int WIDTH = 16
)
(
input wire clk,
input wire [WIDTH-1:0] data_in,
output logic pulse_out
);
logic [(WIDTH+1)-1:0] sigma_reg = '1; // シグマレジスタ
always @(posedge clk) begin
sigma_reg <= sigma_reg + {pulse_out, data_in}; // デルタ加算とシグマ加算
end
assign pulse_out = ~sigma_reg[(WIDTH+1)-1]; // 2値化
endmodule
`default_nettype wire
`default_nettype none
module pdm_sound
#(
parameter real CLK_FREQ = 100e6, // クロック周波数 (Hz)
parameter int COUNT_WIDTH = 32, // カウンタビット幅
parameter int PDM_WIDTH = 16 // PDM ビット幅
)
(
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 BASE_FREQ = 442.0; // 基準音の周波数
localparam bit [COUNT_WIDTH-1:0] INCS[4] = '{ // カウンタの増分値
n2inc(-4), n2inc(-5), n2inc(-7), n2inc(-9) // ファ,ミ,レ,ド
};
localparam [COUNT_WIDTH-1:0] DUTY = 1'b1 << (COUNT_WIDTH-1); // デューティ50%
logic [COUNT_WIDTH-1:0] count = '0; // カウンタ
logic [PDM_WIDTH-1:0] data_for_pdm = '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 && count < DUTY) // PWM 発生用の比較器
data_for_pdm[(PDM_WIDTH-1)-:4] <= sw_in;
else
data_for_pdm <= '0;
end
// デルタシグマ変調
delta_sigma
#(
.WIDTH(PDM_WIDTH)
)
delta_sigma_inst
(
.clk(clk),
.data_in(data_for_pdm),
.pulse_out(pulse_out)
);
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 pdm_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
// PDM によるパルス波発生の回路
pdm_sound
#(
.CLK_FREQ(CLK_FREQ),
.COUNT_WIDTH(32),
.PDM_WIDTH(16)
)
pdm_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_pdm_sound_artytop;
localparam real CLOCK_PERIOD_NS = 10;
localparam int N = 1300000;
logic clk, pulse_out, led_out;
logic [3:0] btn_in, sw_in;
pdm_sound_artytop pdm_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'b0000;
sw_in = 4'b1000;
#(CLOCK_PERIOD_NS * N / 2)
btn_in = 4'b0001;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0000;
sw_in = 4'b0100;
#(CLOCK_PERIOD_NS * N / 2)
btn_in = 4'b0001;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0000;
sw_in = 4'b0010;
#(CLOCK_PERIOD_NS * N / 2)
btn_in = 4'b0010;
#(CLOCK_PERIOD_NS * N)
btn_in = 4'b0000;
sw_in = 4'b0001;
#(CLOCK_PERIOD_NS * N / 2)
btn_in = 4'b1000;
#(CLOCK_PERIOD_NS * N)
$finish;
end
// ローパスフィルタ
localparam real R = 22.2e3; // 22.2kohm
localparam real C = 100.0e-12; // 100 pf
localparam real DELTA_T_NS = 1.0;
localparam real DELTA_T = DELTA_T_NS * 1.0e-9;
real v_in, v_out = 0.0;
always #DELTA_T_NS begin
v_in = pulse_out? 3.3: 0.0;
v_out = (R * C * v_out + DELTA_T * v_in) / (R * C + DELTA_T);
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