FPGAを用いてPDM音源なシンセを作ってみようと思った話 その1 “音を出す” (ざっくりメモ)

概要

大学院の講義でFPGAボードを触る機会があったのでいろいろQiitaに書く前にメモっとこうと思います.今回はとりあえず音を出すまで.

なお、コードは以下Githubのレポジトリに載っけてきます(2019/01/15時点では未掲載...

https://github.com/usuwo-lumturo/pdm-synth

そもそもFPGAって?

FPGA(Field Programmable Gate Array)は


ハードウェア記述言語 (HDL) を使って指定し、その点はASICに近い。FPGAはASICで実装できる任意の論理機能を実装できる。出荷後に機能を更新でき、設計面で部分的に再構成でき[、ASIC設計よりエンジニアリングコストが低い点などが多くの用途で利点となる

WikiPedia 「FPGA」: https://ja.wikipedia.org/wiki/FPGA

とWikiの引用を適当にしますが,詰まるところ
『ANDとかORとかNOTとか論理ゲートが大量にあるブロックと、
その出力を記憶するレジスタがこれまた沢山入ってて、
そのウミアワセを書き換えるのにプログラムで弄くれるICチップ』
と私は解釈しています.
論理ゲートの繋げ方とその論理ゲートブロックとレジスタの繋げ方を、
HDLで記載するのが主に設計というかプログラミングの流れとなります.
詳しい解説はいろいろな方が記述されているのでそこにお任せさせて頂き,割愛いたします.

今回作ったシンセサイザーの概要

じつはこの手の事は先駆者がいらっしゃるみたいで,
kazunori_279さんの「DE0で正弦波を出してみた。」などを参考にさせて頂くと音を出すことが出来る。
ただ,せっかくなのでここではパルス密度変調(PDM : Pulse-density modulation)を音源に用いたシンセサイザーを実装してみたいと思います.

PDMについて

そもそもPDMとはなにかを述べていなかったので、まずはそこから.
PDMとは、入力された波形をパルスの密度信号へと変調する変調方式で、
入力信号に対し1bitでのΔΣ変換を行った場合、出力はPDMとなります.
ここもWikiに詳細が載っているのでこちらを参照したい.

パルス密度変調 Wikipedia https://ja.wikipedia.org/wiki/パルス密度変調

パルス幅変調(PWM)は密度信号の一種として捉えられるみたいで、立ち上がりのタイミングが定期的で、パルスが立ち上がってる時間(幅)を変えることで変調する密度変調と解釈できるのは余談です.

正弦波をPDMへ変換

ただ、FPGAを用いて正弦波を変換かけるのは限りあるロジックエレメントの都合上あまり嬉しくはない使い方だと考えたので、先ずここでは事前にCを用いて生成しました. 以下にコードを示します.(まぁ、他の言語でも良いきはしましたし、実際にPythonで書き直してるのでそちらもgithubに載っけておきます.)

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define BWIDTH 16
#define PI 3.14159265

int     main(int argc, char *argv[])
{
	int   out=0;
	int   max;
	double   delta = 0.0, sigma = 0.0;
	int   a1 = 1;
	double a[440];

	max = (int)pow(2, BWIDTH - 1);
	if (argc>1)
		a1 = atoi(argv[1]);

	for (int i = 0; i < 440; i++) {
		a[i] = 0.5 * sin(2 * PI*i / 440) + 0.5;
	}
	for (int i = 0; i < 440; i++) {
		delta = a[i] - out;
		sigma += delta;
		out = (sigma > 0) ? 1 : 0;
		printf("%d", out);
	}
	printf("\n");
	for (int i = 0; i < 440; i++) {
		printf("%f\n", a[i]);
	}
	return 0;
}

このコードでは、正弦波に対し遅延を取った差分とそれまでの差分値の総和を取り0か0以上なら1と記録し、それを表示するというフローを取っています.
なので、正弦波は振幅0.5で中心が0.5となるように0.5を加算してバイアスをしてます.
このとき、出力されるPDMの長さは440tapとしています.(長いほうがこの後のシステム的に、分解能が上がるので良いのですが、今回は適当にこの長さにしましたのも余談です.) この出力をVHDLに予め書き込んでおきます.

VHDLへ落とし込み

PDMの出力の仕方としては、50 MHzのクロックをPDMの長さと出力したい周波数に合わせてクロックダウンさせて、そのクロックをRead-onlyなシフトレジスタのイネーブルとし入力させることで出力させます.

とりあえず音を鳴らすだけならクロックダウンの目標周波数を固定値に設定出来るので1アーキで組める程度の内容です.実際に組んだコードを以下に示します.

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.numeric_std.all;


entity TEST_SYNTHO is 
	port(
		CLK	:	in std_logic;
		RESET	:	in std_logic;
		PULSE	:	out std_logic
	);
end TEST_SYNTHO;

architecture PLS of TEST_SYNTHO is
	
	signal COUNT		:	std_logic_vector(25 downto 0);
	signal SFTCNT		:	integer
							:=359;
	signal MAXCOUNT	:	std_logic_vector(25 downto 0)
--							:="00000000011011101111100100"; -- 50MHz/440Hz
--							:="00000000000000000000000010"; -- For sim
--							:="00000000000000010001110000"; -- 50MHz/440Hz/100
--							:="00000000000000000100000010"; -- 50MHz/440Hz/440
--							:="00000000000000001110111100"; -- 50MHz/523Hz/100
--							:="00000000000000000011011001"; -- 50MHz/523Hz/400
							:="00000000000000001001110111"; -- 50MHz/440Hz/360
	signal ZEROCOUNT	:	std_logic_vector(25 downto 0)
							:="00000000000000000000000000";
	signal START		:	std_logic_vector(1 downto 0)
							:="01";
--	signal PDM			:	std_logic_vector(439 downto 0)
--							:="11010101010110101101011011011101101110111011110111110111111011111111011111111111111111011111111111111111111111111111111111111111111111111111111111011111111110111111101111101111011110111011101101101101101101011010101011010100101010100101001010010010001001000100010000100000100000100000000010000000000000000100000000000000000000000000000000000000000000000000000000000100000000000100000001000001000010000100010001001001001001001010010101010101";

	signal PDM			:	std_logic_vector(359 downto 0)
							:="110101010101101101011011101101110111101111101111110111111111101111111111111111111111111111111111111111111111111111101111111111110111111101111101111011101110110110110110101011010101010101010010100100100100100100010000100001000001000000000100000000000000000000000000000000000000000000000010000000000000000001000000001000001000010000100100010010010010100101010100";
	
begin
	process (CLK,RESET) begin
		if (RESET='0' or START = "01")	then
			COUNT <= MAXCOUNT;
         PULSE <= '1';
			START <= "00";
		elsif (CLK' event and CLK = '1') then
            if(COUNT = ZEROCOUNT and SFTCNT > 0) then
                COUNT  <= MAXCOUNT;
                PULSE  <= PDM(SFTCNT);
					 SFTCNT <= SFTCNT - 1;
				elsif(COUNT = ZEROCOUNT and SFTCNT = 0) then
                COUNT  <= MAXCOUNT;
                PULSE  <= PDM(SFTCNT);
					 SFTCNT <= 359;
            else
                COUNT <= COUNT - 1;
            end if;
        end if;
    end process;
end PLS;

だいぶ表示か崩れていますがまぁこんな感じで書けます(この辺もgithubに載っけておきます.)

今回はここまでにして、これ以降は次回へ.

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です