Skip to content

Instantly share code, notes, and snippets.

@yuichiro-shibata
Created May 26, 2021 05:02
Show Gist options
  • Save yuichiro-shibata/fcd7788334376e2b22b619cc1439d4b8 to your computer and use it in GitHub Desktop.
Save yuichiro-shibata/fcd7788334376e2b22b619cc1439d4b8 to your computer and use it in GitHub Desktop.
ACRi ブログ「DA コンバータがなくてもできる FPGA ピアノ (4)」のコード
`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
`timescale 1ns/1ps
module sim_sine_sound_artytop;
localparam real CLOCK_PERIOD_NS = 10;
localparam int N = 1000000;
logic clk, pulse_out, led_out;
logic [3:0] btn_in, sw_in;
sine_sound_artytop sine_sound_artytop_inst
(
.clk(clk),
.btn_in(btn_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'b0001;
#(CLOCK_PERIOD_NS * 2 * N)
btn_in = 4'b0000;
#(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 sine_sound
#(
parameter real CLK_FREQ = 100e6, // クロック周波数 (Hz)
parameter int PDM_WIDTH = 16, // PDM ビット幅
parameter int SINE_WIDTH = 25, // 正弦波ビット幅
parameter int SINE_K_WIDTH = 18, // 正弦波発生式の係数ビット幅
parameter real SINE_FREQ = 440, // 正弦波周波数
parameter real SINE_AMP = 0.98 // 正弦波振幅 ( < 1)
)
(
input wire clk,
input wire [3:0] btn_in, // ボタン入力
output logic led_out,
output logic pulse_out
);
logic [SINE_WIDTH-1:0] sin;
logic [PDM_WIDTH-1:0] data_for_pdm = '0;
// 正弦波生成
sine_wave_generator
#(
.CLK_FREQ(CLK_FREQ),
.WIDTH(SINE_WIDTH),
.K_WIDTH(SINE_K_WIDTH),
.TARGET_FREQ(SINE_FREQ),
.AMP(SINE_AMP)
)
sine_wave_generator_inst
(
.clk(clk),
.dout(sin)
);
always @(posedge clk) begin
if (btn_in[0]) // ボタン押下なら正弦波をオフセットバイナリへ変換
data_for_pdm <= {~sin[SINE_WIDTH-1], sin[(SINE_WIDTH-2)-:(PDM_WIDTH-1)]};
else // 固定値 (MSB だけ1)
data_for_pdm <= 1'b1 << (PDM_WIDTH-1);
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 出力
endmodule
`default_nettype wire
`default_nettype none
module sine_sound_artytop
(
input wire clk,
input wire [3:0] btn_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;
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])
);
end
// 正弦波発生とデルタシグマ変調
sine_sound
#(
.CLK_FREQ(CLK_FREQ),
.PDM_WIDTH(16),
.SINE_WIDTH(25),
.SINE_K_WIDTH(18),
.SINE_FREQ(440),
.SINE_AMP(0.98)
)
sine_sound_inst
(
.clk(clk),
.btn_in(btn_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] }];
## Pmod Header JA
set_property -dict { PACKAGE_PIN G13 IOSTANDARD LVCMOS33 } [get_ports { pulse_out }];
`default_nettype none
module sine_wave_generator
#(
parameter real CLK_FREQ = 100e6, // クロック周波数 (Hz)
parameter int WIDTH = 25, // データビット幅
parameter int K_WIDTH = 18, // 係数 k のビット幅
parameter real TARGET_FREQ = 100, // 生成波の周波数
parameter real AMP = 0.98 // 生成波の振幅 ( < 1)
)
(
input wire clk,
output logic [WIDTH-1:0] dout
);
localparam real PI = 3.14159265358979323846; // 円周率
localparam real K = 4.0 * PI * TARGET_FREQ / CLK_FREQ; // 係数 k
localparam real G0 = AMP * $cos(2.0 * PI * TARGET_FREQ / CLK_FREQ); // 初期値
localparam int K_Q = calc_shift_amount(K, K_WIDTH - 1); // k の小数部ビット幅
localparam bit signed [K_WIDTH-1:0] K_V = K * (2.0 ** K_Q); // 正規化された k
logic signed [WIDTH-1:0] freg = '0; // sin レジスタ
logic signed [WIDTH-1:0] greg = G0 * (2.0 ** (WIDTH-1)); // cos レジスタ
logic signed [WIDTH-1:0] kterm; // 係数 k の乗算結果
logic turn = 1'b0; // 状態レジスタ
// クロックごとに状態を交代
always @(posedge clk) begin
turn <= !turn;
end
// 乗算
assign kterm = ((K_WIDTH+WIDTH)'(K_V) * (!turn? greg: freg)) >>> K_Q;
// レジスタ更新
always @(posedge clk) begin
if (!turn)
freg <= freg + kterm;
else
greg <= greg - kterm;
end
// 出力
assign dout = freg;
// 数値 x を width ビットで正規化するのに必要なシフト量を求める関数
function int calc_shift_amount(real x, int width);
longint t;
int q;
for (t = x, q = 0; !t[width-1] && q < 128; q++) // シフトの最大値を128に制限
t = x * (2.0 ** (q + 1));
return q;
endfunction
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