目次

PSG(Programable Sound Generator)

 PSG(Programable Sound Generator)を、単純に周波数を変更する
 矩形波発振回路から、FM音源まで変化させてみます。

 PSGは、与える数値で出力される音の周波数を可変にする電子回路です。
 与える数値は、ほしい周波数になるように、元の周波数を分周するために
 必要になります。この与える数値を、分周比と呼びます。

 ブロック図で示すと、下記のように単純です。



 元の周波数を分周するためには、少し計算が必要です。
 目的とする周波数の分周比は、利用する発振器の周波数に変わります。

 いつも電卓を叩くのは面倒なので、Tcl/Tkで利用周波数と
 目的周波数を入力すると、分周比を計算するスクリプトを
 作成しました。

#!/bin/sh

wm title . "generate divider"

# clear file names
set o_frequency 0
set t_frequency 0
set result 0

# add menu on TopLevel
. configure -menu .mnuTop
menu .mnuTop

# add sub form
.mnuTop add cascade  -label File -underline 0 -menu .mnuTop.file
menu .mnuTop.file -tearoff no

# add sub menu "Quit"
.mnuTop.file  add command -label "Quit" -command {exit}

# set window size
set w 1000
set h 1000

label .lblOfreq  -text "origin frequency(Hz)"
label .lblTfreq  -text "target frequency(Hz)"
label .lblResult -textvariable result
entry .edtOfreq  -textvariable o_frequency
entry .edtTfreq  -textvariable t_frequency
button .btnCalc  -text "calculate" -command {Calc}

pack .lblOfreq
pack .edtOfreq
pack .lblTfreq
pack .edtTfreq
pack .btnCalc
pack .lblResult

#######################
# calculate
#######################
proc Calc { } {
  global o_frequency t_frequency result
  # flag
  set flag 0
  # get origin frequency
  set y $o_frequency
  # get target frequency
  set x $t_frequency
  # judge
  if { $x < 19 } {
    tk_messageBox -type ok -message "frequency must be greater than 19Hz"
    set flag 1
  }
  if { $x > 20000 } {
    tk_messageBox -type ok -message "frequency must be less than 20kHz"
    set flag 1
  }
  if { $flag == 0 } {
    set result [expr $y / $x]
  }
}

 100kHzから、800Hzを生成する計算をしてみると、次のようになります。



 人間が耳で音として認識できる周波数は、20Hz〜20kHzと言われています。
 この範囲の周波数を、CPLD/FPGAで生成し、スピーカに接続すると音がでます。

 これから、1チャネル矩形波発振器を作り、3チャネルに拡張し
 さらにFM音源へと展開してみます。


1チャネル矩形波発振

 もっとも単純なブロック図から、矩形波を発振するVHDLコードを記述します。  ブロック図を作成します。  ブロック図は、次のような視点で設計しました。 4MHzのシステムクロックを使います。  分周比を16ビットの範囲にまとめるために、1MHzに落とします。 4MHz→1MHzの変換にPRESCALERを利用します。  分周比を保存するために16ビットのレジスタREGを使います。  入力シンクロナイザで、同期化してトリガーを与えます。  分周比を保存するために16ビットのレジスタREGを使います。  入力シンクロナイザで、同期化してトリガーを与えます。  1MHzを入力クロックとするカウンタとREGの値を比較して  一致で1を出力し、それ以外は0を出力します。  ここが、矩形波発振回路の要になります。  音が常に出ていると、騒々しいので、イネーブル端子を用意し  音の停止ができるようにします。  各ブロックをまとめと、以下となります。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity stst is Port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- system clock (4MHz) -- fdiv TRGH : in std_logic ; -- upper byte TRGL : in std_logic ; -- lower byte DIN : in std_logic_vector(7 downto 0) ; -- I/O ENA : in std_logic ; SOUT : out std_logic --; ); end stst; architecture Behavioral of stst is -- sound clock signal iSCNT : std_logic_vector(1 downto 0) ; signal iSCLK : std_logic ; -- input synchronizer signal iTRGHS : std_logic_vector(3 downto 0) ; signal iTRGH : std_logic ; signal iTRGLS : std_logic_vector(3 downto 0) ; signal iTRGL : std_logic ; -- frequency divider signal iFDIV : std_logic_vector(15 downto 0) ; -- frequency counter signal iFCNT : std_logic_vector(15 downto 0) ; signal iSOUT : std_logic ; begin -- prescaler (generate 1MHz) process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCNT <= "00" ; elsif rising_edge( CLOCK ) then -- increment iSCNT <= iSCNT + '1' ; end if ; end process ; iSCLK <= '1' when ( iSCNT = "00" ) else '0' ; -- input synchronizer process ( nRESET , iSCLK ) begin if ( nRESET = '0' ) then iTRGHS <= "1111" ; iTRGLS <= "1111" ; elsif rising_edge( iSCLK ) then -- shift in iTRGHS <= iTRGHS(2 downto 0) & TRGH ; iTRGLS <= iTRGLS(2 downto 0) & TRGL ; end if ; end process ; iTRGH <= '1' when ( iTRGHS = "0000" ) else '0' ; iTRGL <= '1' when ( iTRGLS = "0000" ) else '0' ; -- frequency divider latch process ( nRESET , iSCLK ) begin if ( nRESET = '0' ) then iFDIV <= (others => '0') ; elsif rising_edge( iSCLK ) then -- latch if ( iTRGH = '1' ) then iFDIV(15 downto 8) <= DIN ; end if ; if ( iTRGL = '1' ) then iFDIV( 7 downto 0) <= DIN ; end if ; end if ; end process ; -- frequency divider process ( nRESET , iSCLK ) begin if ( nRESET = '0' ) then iFCNT <= (others => '0') ; elsif rising_edge( iSCLK ) then -- increment iFCNT <= iFCNT + '1' ; -- judge if ( iFCNT = iFDIV ) then iFCNT <= (others => '0') ; end if ; end if ; end process ; iSOUT <= '1' when ( conv_integer(iFCNT) = 0 ) else '0' ; -- sound out SOUT <= iSOUT when ( ENA = '1' ) else '0' ; end Behavioral;  このVHDLコードは、CoolRunnerIIのボードにスピーカを  接続してテストしました。  分周比は、8ビットのDIPスイッチを1個利用して設定します。  また、トリガーには、プッシュスイッチを2個使いました。  矩形波を出すので、音は綺麗ではありませんが、与える分周比で  音階を作りだすので、PSGの雛形と呼べるでしょう。

3チャネル矩形波発振

 1チャネル分の矩形波発振ができたので、和音がでるように  拡張します。  往年のPSGであるAY-3-8910は、発振器を3チャネルもつので  敬意を表し、3チャネル分の矩形波発振を作成します。  音の周波数を生成器は、3チャネルでも、利用する発振器が  1個であることが重要です。  言い換えると、音の周波数を生成する発振器が1個でも  デジタル回路を並べれば、好きな数の音の周波数発振器  を作れることです。  1チャネルの矩形波発振器はあるので、分周回路をコピー  して3チャネル分用意すると、3チャネル矩形波発振器の  完成です。  VHDLでは、1つのブロックを定義しておき、使い回すときには  component記述を利用します。  component記述により、同一回路ブロックを複数用意し、利用できます。  1チャネル矩形発振器で作成した、次のブロックを複数用意します。  各ブロックを定義してみます。 分周比レジスタ    16ビットレジスタに、値を設定すればよいので    内部にsignalでレジスタを確保します。     signal iREG : std_logic_vector(15 downto 0) ;    16ビットレジスタ値の入力をDINと出力DOUTとして    TRGHが1のとき、上位8ビットにラッチします。    TRGLが1のとき、下位8ビットにラッチします。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iREG <= (others => '0') ; elsif rising_edge( CLOCK ) then -- latch if ( TRGH = '1' ) then iREG(15 downto 8) <= DIN ; end if ; if ( TRGL = '1' ) then iREG( 7 downto 0) <= DIN ; end if ; end if ; end process ; DOUT <= iREG ;    コンポーネントとして定義します。    library IEEE;    use IEEE.STD_LOGIC_1164.ALL;    use IEEE.STD_LOGIC_ARITH.ALL;    use IEEE.STD_LOGIC_UNSIGNED.ALL;    entity fdlatch is     Port (     -- system     nRESET : in std_logic ;     CLOCK : in std_logic ;     -- trigger     TRGH : in std_logic ;     TRGL : in std_logic ;     -- data     DIN : in std_logic_vector(7 downto 0) ;     -- result     DOUT : out std_logic_vector(15 downto 0) --;     );    end fdlatch;    architecture Behavioral of fdlatch is     signal iREG : std_logic_vector(15 downto 0) ;    begin     process ( nRESET , CLOCK )     begin     if ( nRESET = '0' ) then     iREG <= (others => '0') ;     elsif rising_edge( CLOCK ) then     -- latch     if ( TRGH = '1' ) then iREG(15 downto 8) <= DIN ;     end if ;     if ( TRGL = '1' ) then iREG( 7 downto 0) <= DIN ;     end if ;     end if ;     end process ;     DOUT <= iREG ;    end Behavioral; 分周ブロック    16ビットレジスタの値と比較するので、カウンタを    16ビットバイナリとします。 signal iCNT : std_logic_vector(15 downto 0) ;    比較した結果の論理値を格納するレジスタを用意します。 signal iSOUT : std_logic ;    16ビットバイナリカウンタのインクリメントとゼロクリア    を同期して実行します。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCNT <= (others => '0') ; elsif rising_edge( CLOCK ) then -- increment iCNT <= iCNT + '1' ; -- judge if ( iCNT = DIN ) then iCNT <= (others => '0') ; end if ; end if ; end process ;    比較処理は、単純なデコーダにします。 iSOUT <= '1' when ( conv_integer(iCNT) = 0 ) else '0' ;    デコーダの結果をENABLEにより、出力あるいは停止します。 SOUT <= iSOUT when ( ENABLE = '1' ) else '0' ;    コンポーネントとして定義します。    library IEEE;    use IEEE.STD_LOGIC_1164.ALL;    use IEEE.STD_LOGIC_ARITH.ALL;    use IEEE.STD_LOGIC_UNSIGNED.ALL;    entity fcnt is     Port (     -- system     nRESET : in std_logic ;     CLOCK : in std_logic ;     -- data     DIN : in std_logic_vector(15 downto 0) ;     -- enable     ENABLE : in std_logic ;     -- result     SOUT : out std_logic --;     );    end fcnt;    architecture Behavioral of fcnt is     signal iCNT : std_logic_vector(15 downto 0) ;     signal iSOUT : std_logic ;    begin     process ( nRESET , CLOCK )     begin     if ( nRESET = '0' ) then     iCNT <= (others => '0') ;     elsif rising_edge( CLOCK ) then     -- increment     iCNT <= iCNT + '1' ;     -- judge     if ( iCNT = DIN ) then     iCNT <= (others => '0') ;     end if ;     end if ;     end process ;     iSOUT <= '1' when ( conv_integer(iCNT) = 0 ) else '0' ;     SOUT <= iSOUT when ( ENABLE = '1' ) else '0' ;    end Behavioral; 入力シンクロナイザ    1チャネル分のトリガーを、シフトレジスタでラッチします。     signal iTRGS : std_logic_vector(3 downto 0);    シフトレジスタに格納した内容から、出力を確定します。     signal iTRGH : std_logic;     signal iTRGL : std_logic;    クロックで、トリガーの値をラッチしていき、4ビットの    内容で、トリガー値を確定します。     process ( nRESET , CLOCK )     begin     if ( nRESET = '0' ) then     iTRGHS <= "1111" ;     iTRGLS <= "1111" ;     elsif rising_edge( CLOCK ) then     -- shift in     iTRGHS <= iTRGHS(2 downto 0) & TRGH ;     iTRGLS <= iTRGLS(2 downto 0) & TRGL ;     end if ;     end process ;     iTRGH <= '1' when ( iTRGHS = "0000" ) else '0' ;     iTRGL <= '1' when ( iTRGLS = "0000" ) else '0' ;    コンポーネントとして定義します。    library IEEE;    use IEEE.STD_LOGIC_1164.ALL;    use IEEE.STD_LOGIC_ARITH.ALL;    use IEEE.STD_LOGIC_UNSIGNED.ALL;    entity isync is     Port (     -- system     nRESET : in std_logic ;     CLOCK : in std_logic ;     -- trigger     TRGH : in std_logic ;     TRGL : in std_logic ;     -- result     RTRGH : out std_logic ;     RTRGL : out std_logic --;     );    end isync;    architecture Behavioral of isync is     signal iTRGHS : std_logic_vector(3 downto 0);     signal iTRGH : std_logic;     signal iTRGLS : std_logic_vector(3 downto 0);     signal iTRGL : std_logic;    begin     -- input synchronizer     process ( nRESET , CLOCK )     begin     if ( nRESET = '0' ) then     iTRGHS <= "1111" ;     iTRGLS <= "1111" ;     elsif rising_edge( CLOCK ) then     -- shift in     iTRGHS <= iTRGHS(2 downto 0) & TRGH ;     iTRGLS <= iTRGLS(2 downto 0) & TRGL ;     end if ;     end process ;     iTRGH <= '1' when ( iTRGHS = "1000" ) else '0' ;     iTRGL <= '1' when ( iTRGLS = "1000" ) else '0' ;     RTRGH <= iTRGH ;     RTRGL <= iTRGL ;    end Behavioral;  各ブロックを定義したので、入出力信号とプリスケーラの動作を  確定します。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity sound0 is Port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- system clock (4MHz) -- fdiv TRGH : in std_logic_vector(2 downto 0) ; TRGL : in std_logic_vector(2 downto 0) ; DIN : in std_logic_vector(7 downto 0) ; -- I/O ENA : in std_logic ; ENB : in std_logic ; ENC : in std_logic ; SOUT : out std_logic_vector(2 downto 0) --; ); end sound0; architecture Behavioral of sound0 is -- sound clock signal iSCNT : std_logic_vector(1 downto 0) ; signal iSCLK : std_logic ; -- input synchronizer component isync Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- trigger TRGH : in std_logic ; TRGL : in std_logic ; -- result RTRGH : out std_logic ; RTRGL : out std_logic --; ); end component ; signal iTRGHA : std_logic ; signal iTRGHB : std_logic ; signal iTRGHC : std_logic ; signal iTRGLA : std_logic ; signal iTRGLB : std_logic ; signal iTRGLC : std_logic ; -- frequency latch component fdlatch Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- trigger TRGH : in std_logic ; TRGL : in std_logic ; -- data DIN : in std_logic_vector(7 downto 0) ; -- result DOUT : out std_logic_vector(15 downto 0) --; ); end component ; signal iFDIVA : std_logic_vector(15 downto 0) ; signal iFDIVB : std_logic_vector(15 downto 0) ; signal iFDIVC : std_logic_vector(15 downto 0) ; -- frequency counter component fcnt Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- data DIN : in std_logic_vector(15 downto 0) ; -- enable ENABLE : in std_logic ; -- result SOUT : out std_logic --; ); end component; signal iSOUT : std_logic_vector(2 downto 0) ; begin -- generate sound clock (100kHz) process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCNT <= "00" ; elsif rising_edge( CLOCK ) then -- increment iSCNT <= iSCNT + '1' ; end if ; end process ; iSCLK <= '1' when ( iSCNT = "00" ) else '0' ; -- input synchronizer ISA : isync port map (nRESET,iSCLK,TRGH(0),TRGL(0),iTRGHA,iTRGLA) ; ISB : isync port map (nRESET,iSCLK,TRGH(1),TRGL(1),iTRGHB,iTRGLB) ; ISC : isync port map (nRESET,iSCLK,TRGH(2),TRGL(2),iTRGHC,iTRGLC) ; -- frequency divider latch FD_A : fdlatch port map (nRESET,iSCLK,iTRGHA,iTRGLA,DIN,iFDIVA); FD_B : fdlatch port map (nRESET,iSCLK,iTRGHB,iTRGLB,DIN,iFDIVB); FD_C : fdlatch port map (nRESET,iSCLK,iTRGHC,iTRGLC,DIN,iFDIVC); -- frequency counter FCT_A : fcnt port map (nRESET,iSCLK,iFDIVA,ENA,iSOUT(0)); FCT_B : fcnt port map (nRESET,iSCLK,iFDIVB,ENB,iSOUT(1)); FCT_C : fcnt port map (nRESET,iSCLK,iFDIVC,ENC,iSOUT(2)); -- sound out SOUT <= iSOUT(2) & iSOUT(1) & iSOUT(0) ; end Behavioral;  4つのVHDLコードを利用して、回路情報を生成したなら、実機に  ダウンロードしてテストします。  3チャネル矩形波発振回路に、3つの分周比を設定し、音を出す止める  を信号線で設定する作業は、結構しんどいものです。  この作業をマイコンにやらせると楽です。  Music Sequencerと呼ばれる装置は、3〜6個程度の分周比の設定  と継続時間をメモリに保存します。  保存してある情報を使い、楽器の演奏をします。

シリアルインタフェース3チャネル矩形波発振

 マイコンから、3チャネル矩形波発振を制御する場合  各チャネルの分周比をパラレルで入力すると、ピンが  不足します。  ピン不足を解消する目的で、各チャネルの分周比をシリアルで  転送する仕様に変更します。  3チャネルのレジスタで、クロックとLOAD信号を共通にし  データだけを別に用意して、分周比を設定します。  このブロックの動作を定義してみます。 process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iSA_REG <= (others => '0') ; iSB_REG <= (others => '0') ; iSC_REG <= (others => '0') ; elsif rising_edge( SCLK ) then if ( LOAD = '1' ) then iSA_REG <= iSA_REG(14 downto 0) & SA ; iSB_REG <= iSB_REG(14 downto 0) & SB ; iSC_REG <= iSC_REG(14 downto 0) & SC ; end if ; end if ; end process ;  LOAD信号が'1'のときに、シリアル1ビット入力の値をラッチして  同時にシフトします。  この処理を入れたトップレベルを定義します。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity sound1 is Port ( -- system nRESET : in std_logic ; -- system reset CLOCK : in std_logic ; -- system clock (4MHz) -- fdiv SCLK : in std_logic ; LOAD : in std_logic ; SA : in std_logic ; SB : in std_logic ; SC : in std_logic ; -- I/O ENA : in std_logic ; ENB : in std_logic ; ENC : in std_logic ; SOUT : out std_logic_vector(2 downto 0) --; ); end sound1; architecture Behavioral of sound1 is -- sound clock signal iSCNT : std_logic_vector(1 downto 0) ; signal iSCLK : std_logic ; -- data latch signal iSA_REG : std_logic_vector(15 downto 0) ; signal iSB_REG : std_logic_vector(15 downto 0) ; signal iSC_REG : std_logic_vector(15 downto 0) ; -- frequency counter component fcnt Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- data DIN : in std_logic_vector(15 downto 0) ; -- enable ENABLE : in std_logic ; -- result SOUT : out std_logic --; ); end component; signal iSOUT : std_logic_vector(2 downto 0) ; begin -- generate sound clock (100kHz) process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCNT <= "00" ; elsif rising_edge( CLOCK ) then -- increment iSCNT <= iSCNT + '1' ; end if ; end process ; iSCLK <= '1' when ( iSCNT = "00" ) else '0' ; -- fdiv process ( nRESET , SCLK ) begin if ( nRESET = '0' ) then iSA_REG <= (others => '0') ; iSB_REG <= (others => '0') ; iSC_REG <= (others => '0') ; elsif rising_edge( SCLK ) then if ( LOAD = '1' ) then iSA_REG <= iSA_REG(14 downto 0) & SA ; iSB_REG <= iSB_REG(14 downto 0) & SB ; iSC_REG <= iSC_REG(14 downto 0) & SC ; end if ; end if ; end process ; -- frequency counter FCT_A : fcnt port map (nRESET,iSCLK,iSA_REG,ENA,iSOUT(0)); FCT_B : fcnt port map (nRESET,iSCLK,iSB_REG,ENB,iSOUT(1)); FCT_C : fcnt port map (nRESET,iSCLK,iSC_REG,ENC,iSOUT(2)); -- sound out SOUT <= iSOUT(2) & iSOUT(1) & iSOUT(0) ; end Behavioral;  分周処理は、既に定義してあるブロックを流用しました。  20ピンのマイコンATtiny2313で、動かせるかを検証してみます。  合計9ピンあれば、音を出す制御はできます。  ATtiny2313のシリアル接続を利用し、パーソナルコンピュータから  このマイコンのSRAMに、分周比、音を出すチャネル指定、音を出す  時間を保存すれば、マイコンをMusic Sequencerに仕立てることも  可能です。  Music Sequencerを、CPLD/FPGAで作成してみます。

Music Sequencer

 シリアルインタフェースをもつ3チャネル矩形波発振器  を制御する、Music Sequencerの仕様を考えます。  この程度の大雑把な仕様から、ブロック図を作成し、段階的に  詳細を詰めていきます。  ブロック図から、以下のインタフェースブロックが必要だと  思いつくでしょう。  SRAMに情報を書き込む処理は、Music Sequencerではなく  マイコンにやってもらうことにします。  マイコンが、制御、アドレス、データの各信号を使えるよう  Music Sequencerは、これらの信号をハイインピーダンスに  します。そのための信号が、Enableです。  Enableを'1'にした場合、Music SequencerがSRAMにアクセス  できるようにします。  Enableが'0'では、制御、アドレス、データは、ハイインピーダンス  とします。  SRAMから情報を取得して、PSGを制御するため  トリガー信号triggerを利用します。  ここまで仕様を固めると、Entity定義ができます。 entity musicseq is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- SRAM interface ENABLE : in std_logic ; nCS : out std_logic ; nRD : out std_logic ; ADR : out std_logic_vector(14 downto 0) ; SDATA : in std_logic_vector( 7 downto 0) ; -- trigger TRG : in std_logic ; -- clock SCLOCK : out std_logic ; -- fdiv LOAD : out std_logic ; SA : out std_logic ; SB : out std_logic ; SC : out std_logic ; -- I/O ENA : out std_logic ; ENB : out std_logic ; ENC : out std_logic --; ) ; end musicseq;  SRAMを32kバイトとして、Music Sequencerの動作を定義します。  SRAMにいれる情報のフォーマットを、決めておきます。 +0 channel_A upper divider +1 channel_A lower divider +2 channel_B upper divider +3 channel_B lower divider +4 channel_C upper divider +5 channel_C lower divider +6 enable channel bit +7 time count  8バイトで、1回の動作を指定します。  32kバイトのSRAMでは、4096通りの音の出し方を記述できます。  Music SequencerのPSG制御シーケンスを考えます。
  1. トリガー待ち
  2. SRAMから情報取得
  3. 分周比転送
  4. 動作チャネル指定
  5. 動作時間待ち
  6. 4096回処理したなら1に、それ以外では2にもどる
 シーケンスをVHDLで記述します。 type stype is (S0,S1,S2,S3,S4,S5,S6,S7,S8); signal iSTATE : stype ; signal iCOUNT : std_logic_vector(12 downto 0); signal iADR : std_logic_vector(14 downto 3); signal iGET_TRG : std_logic ; signal iGET_END : std_logic ; signal iSTORE_TRG : std_logic ; signal iSTORE_END : std_logic ; signal iTIME_TRG : std_logic ; signal iTIME_END : std_logic ; process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSTATE <= S0 ; iCOUNT <= (others => '0'); iADR <= (others => '0'); elsif rising_edge( CLOCK ) then case iSTATE is -- trigger wait when S0 => if ( TRG = '1' and ENABLE = '1' ) then iCOUNT <= (others => '0') ; iADR <= (others => '0'); iSTATE <= S1 ; end if ; -- judge when S1 => if ( iCOUNT(12) = '1' ) then iSTATE <= S8 ; else iSTATE <= S2 ; end if ; -- get information when S2 => iSTATE <= S3 ; -- wait when S3 => if ( iGET_END = '1' ) then iSTATE <= S4 ; else iSTATE <= S3 ; end if ; -- store frequency divider when S4 => iSTATE <= S5 ; -- wait when S5 => if ( iSTORE_END = '1' ) then iSTATE <= S6 ; else iSTATE <= S5 ; end if ; -- enable channel when S6 => iSTATE <= S7 ; -- sustine time when S7 => if ( iTIME_END = '1' ) then iSTATE <= S6 ; iCOUNT <= iCOUNT + '1' ; iADR <= iADR + 1 ; else iSTATE <= S1 ; end if ; -- return first state when S8 => iSTATE <= S0 ; -- default when others => iSTATE <= S0 ; end case ; end if ; end process ; SRAMから情報を取得するブロックを定義します。  SRAMのリードアクセスは、次のシーケンスになっています。
  1. nCSをイネーブル
  2. アドレス出力
  3. nRDをイネーブル
  4. 出力データラッチ
  5. nRDをディセーブル
  6. nCSをディセーブル
 8バイトのデータを取得するので、アドレスを出力して  nRDを制御しながら、SRAMが出力するデータをラッチします。 type atype is (AS0,AS1,AS2,AS3,AS4,AS5,AS6,AS7) ; signal iACUR : atype ; signal iADRX : std_logic_vector(14 downto 0); signal iACNT : std_logic_vector( 3 downto 0); process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iACUR <= AS0 ; iADRX <= (others => '0'); nCSi <= '1' ; elsif rising_edge( CLOCK ) then case iSTATE is -- wait trigger when AS0 => if ( iGET_TRG = '1' ) then iADRX(14 downto 3) <= iADR ; iADRX( 2 dwonto 0) <= "000" ; iACNT <= "0000" ; iACUR <= AS1 ; end if ; -- judge when AS1 => if ( iACNT(3) = '1' ) then iACUR <= AS7 ; else iACUR <= AS2 ; end if ; -- enable nCS when AS2 => nCSi <= '0' ; iACUR <= AS3 ; -- enable nRD when AS3 => iACUR <= AS4 ; -- latch when AS4 => iACUR <= AS5 ; -- disable nRD when AS5 => case iACNT is when "0000" => iSA_REG(15 downto 8) <= SDATA ; when "0001" => iSA_REG( 7 downto 0) <= SDATA ; when "0010" => iSB_REG(15 downto 8) <= SDATA ; when "0011" => iSB_REG( 7 downto 0) <= SDATA ; when "0100" => iSC_REG(15 downto 8) <= SDATA ; when "0101" => iSC_REG( 7 downto 0) <= SDATA ; when "0110" => iCHANNEL <= SDATA(2 downto 0) ; when "0111" => iTIME <= SDATA ; when others => NULL ; end case ; iACUR <= AS6 ; -- increment when AS6 => iACNT <= iACNT + '1' ; iADR <= iADR + '1' ; iACUR <= AS1 ; -- disable nCS when AS7 => nCSi <= '1' ; iACUR <= AS0 ; -- default when others => iACUR <= AS0 ; end case ; end if ; end process ; iGET_END <= '1' when ( iACUR = AS7 ) else '0' ; nRDi <= '0' when ( iACUR = AS3 or iACUR = AS4 ) else '1' ; nCS <= nCSi when ( ENABLE = '1' ) else 'Z' ; nRD <= nRDi when ( ENABLE = '1' ) else 'Z' ; ADR <= iADRX when ( ENABLE = '1' ) else (others => 'Z') ; SRAMから、音を出すチャネルを指定されたならば、それを  PSGに転送します。  ENA <= iCHANNEL(0) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ;  ENB <= iCHANNEL(1) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ;  ENC <= iCHANNEL(2) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ; 音を出している時間を制御するブロックを定義します。  単純にカウンタをデクリメントして対応します。  ただし、100ms単位で考えます。  動作シーケンスを定義します。 type ctype is (CS0,CS1,CS2,CS3,CS4,CS5) ; signal iCCUR : ctype ; signal iCCNT : std_logic_vector(7 downto 0); process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCCUR <= CS0 ; iCCNT <= (others => '0'); elsif rising_edge( CLOCK ) then case iCCUR is -- wait trigger when CS0 => if ( iTIME_TRG = '1' ) then iCCNT <= (others => '0') ; iCCUR <= CS1 ; end if ; -- judge when CS1 => if ( iCCNT = iTIME ) then iCCUR <= CS4 ; else iCCUR <= CS2 ; end if ; -- wait 10ms when CS2 => if ( conv_integer(iTECNTX) = 625 ) then iCCUR <= CS3 ; end if ; -- increment when CS3 => iCCNT <= iCCNT + '1' ; iCCUR <= CS1 ; -- set end flag when CS4 => iCCUR <= CS5 ; -- return first state when CS5 => iCCUR <= CS0 ; -- default when others => iCCUR <= CS0 ; end case ; end if ; end process ;  100msの時間経過は、他のブロックのカウンタの値を参照して  確認します。  時間待ち処理は、2段階で実現します。  1MHzでシーケンサを動かしているとして、1段目で8分周します。  125kHzのクロックができるので、100msは100Hzであることから  2段目で1250分周します。  100msの時間経過は、分周カウンタが625になったときで判定します。  処理内容は、単純に2つのカウンタを動かすだけです。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iTECNT <= "000" ; elsif rising_edge( CLOCK ) then iTECNT <= iTECNT + '1' ; end if ; end process ; iTCLOCK <= '1' when ( iTECNT = "000" ) else '0' ; process ( nRESET , iTCLOCK ) begin if ( nRESET = '0' ) then iTECNTX <= (others => '0') ; elsif rising_edge( iTCLOCK ) then iTECNTX <= iTECNTX + '1' ; if ( conv_integer(iTECNTX) = 1250 ) then iTECNTX <= (others => '0') ; end if ; end if ; end process ;  全ソースコードは、以下となります。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity musicseq is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- SRAM interface ENABLE : in std_logic ; nCS : out std_logic ; nRD : out std_logic ; ADR : out std_logic_vector(14 downto 0) ; SDATA : in std_logic_vector( 7 downto 0) ; -- trigger TRG : in std_logic ; -- clock SCLOCK : out std_logic ; -- fdiv LOAD : out std_logic ; SA : out std_logic ; SB : out std_logic ; SC : out std_logic ; -- I/O ENA : out std_logic ; ENB : out std_logic ; ENC : out std_logic --; ) ; end musicseq; architecture Behavioral of musicseq is -- sequencer type stype is (S0,S1,S2,S3,S4,S5,S6,S7,S8); signal iSTATE : stype ; signal iCOUNT : std_logic_vector(12 downto 0); signal iADR : std_logic_vector(14 downto 3); signal iGET_TRG : std_logic ; signal iGET_END : std_logic ; signal iSTORE_TRG : std_logic ; signal iSTORE_END : std_logic ; signal iTIME_TRG : std_logic ; signal iTIME_END : std_logic ; -- frequency divider registers component store_seq Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- data PSA : in std_logic_vector(15 downto 0) ; PSB : in std_logic_vector(15 downto 0) ; PSC : in std_logic_vector(15 downto 0) ; -- trigger TRG : in std_logic ; -- fdiv SCLK : out std_logic ; LOAD : out std_logic ; SA : out std_logic ; SB : out std_logic ; SC : out std_logic ; SEND : out std_logic --; ) ; end component ; signal iSA_REG : std_logic_vector(15 downto 0); signal iSB_REG : std_logic_vector(15 downto 0); signal iSC_REG : std_logic_vector(15 downto 0); signal iCHANNEL: std_logic_vector( 2 downto 0); signal iTIME : std_logic_vector( 7 downto 0); -- SRAM access type atype is (AS0,AS1,AS2,AS3,AS4,AS5,AS6) ; signal iACUR : atype ; signal iADRX : std_logic_vector(14 downto 0); signal iACNT : std_logic_vector( 3 downto 0); signal nCSi : std_logic ; signal nRDi : std_logic ; -- time count type ctype is (CS0,CS1,CS2,CS3,CS4,CS5) ; signal iCCUR : ctype ; signal iCCNT : std_logic_vector(7 downto 0); -- time edge 100ms signal iTECNT : std_logic_vector(2 downto 0); signal iTCLOCK : std_logic ; signal iTECNTX : std_logic_vector(10 downto 0); begin process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSTATE <= S0 ; iCOUNT <= (others => '0'); iADR <= (others => '0'); elsif rising_edge( CLOCK ) then case iSTATE is -- trigger wait when S0 => if ( TRG = '1' and ENABLE = '1' ) then iCOUNT <= (others => '0') ; iADR <= (others => '0'); iSTATE <= S1 ; end if ; -- judge when S1 => if ( iCOUNT(12) = '1' ) then iSTATE <= S8 ; else iSTATE <= S2 ; end if ; -- get information when S2 => iSTATE <= S3 ; -- wait when S3 => if ( iGET_END = '1' ) then iSTATE <= S4 ; else iSTATE <= S3 ; end if ; -- store frequency divider when S4 => iSTATE <= S5 ; -- wait when S5 => if ( iSTORE_END = '1' ) then iSTATE <= S6 ; else iSTATE <= S5 ; end if ; -- enable channel when S6 => iSTATE <= S7 ; -- sustine time when S7 => if ( iTIME_END = '1' ) then iSTATE <= S6 ; iCOUNT <= iCOUNT + '1' ; iADR <= iADR + 1 ; else iSTATE <= S1 ; end if ; -- return first state when S8 => iSTATE <= S0 ; -- default when others => iSTATE <= S0 ; end case ; end if ; end process ; -- trigger iGET_TRG <= '1' when ( iSTATE = S2 ) else '0' ; iSTORE_TRG <= '1' when ( iSTATE = S4 ) else '0' ; iTIME_TRG <= '1' when ( iSTATE = S6 ) else '0' ; -- get information process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iACUR <= AS0 ; iADRX <= (others => '0'); iACNT <= "0000" ; iSA_REG <= (others => '0') ; iSB_REG <= (others => '0') ; iSC_REG <= (others => '0') ; iCHANNEL<= "000" ; iTIME <= (others => '0') ; nCSi <= '1' ; elsif rising_edge( CLOCK ) then case iACUR is -- wait trigger when AS0 => if ( iGET_TRG = '1' ) then iADRX(14 downto 3) <= iADR ; iADRX( 2 downto 0) <= "000" ; iACNT <= "0000" ; iACUR <= AS1 ; end if ; -- judge when AS1 => if ( iACNT(3) = '1' ) then iACUR <= AS6 ; else nCSi <= '0' ; iACUR <= AS2 ; end if ; -- enable nRD when AS2 => iACUR <= AS3 ; -- latch when AS3 => case iACNT is when "0000" => iSA_REG(15 downto 8) <= SDATA ; when "0001" => iSA_REG( 7 downto 0) <= SDATA ; when "0010" => iSB_REG(15 downto 8) <= SDATA ; when "0011" => iSB_REG( 7 downto 0) <= SDATA ; when "0100" => iSC_REG(15 downto 8) <= SDATA ; when "0101" => iSC_REG( 7 downto 0) <= SDATA ; when "0110" => iCHANNEL <= SDATA(2 downto 0) ; when "0111" => iTIME <= SDATA ; when others => NULL ; end case ; iACUR <= AS4 ; -- disable nRD when AS4 => iACUR <= AS5 ; -- increment when AS5 => iACNT <= iACNT + '1' ; iADRX <= iADRX + '1' ; iACUR <= AS1 ; -- disable nCS when AS6 => nCSi <= '1' ; iACUR <= AS0 ; -- default when others => iACUR <= AS0 ; end case ; end if ; end process ; iGET_END <= '1' when ( iACUR = AS6 ) else '0' ; nRDi <= '0' when ( iACUR = AS2 or iACUR = AS3 ) else '1' ; nCS <= nCSi when ( ENABLE = '1' ) else 'Z' ; nRD <= nRDi when ( ENABLE = '1' ) else 'Z' ; ADR <= iADRX when ( ENABLE = '1' ) else (others => 'Z') ; ENA <= iCHANNEL(0) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ; ENB <= iCHANNEL(1) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ; ENC <= iCHANNEL(2) when ( iSTATE = S6 or iSTATE = S7 ) else '0' ; -- frequency divider registers STORE_SEQP : store_seq port map (nRESET,CLOCK, iSA_REG,iSB_REG,iSC_REG,iSTORE_TRG,SCLOCK,LOAD,SA,SB,SC,iSTORE_END) ; -- time count process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iCCUR <= CS0 ; iCCNT <= (others => '0'); elsif rising_edge( CLOCK ) then case iCCUR is -- wait trigger when CS0 => if ( iTIME_TRG = '1' ) then iCCNT <= (others => '0') ; iCCUR <= CS1 ; end if ; -- judge when CS1 => if ( iCCNT = iTIME ) then iCCUR <= CS4 ; else iCCUR <= CS2 ; end if ; -- wait 10ms when CS2 => if ( conv_integer(iTECNTX) = 625 ) then iCCUR <= CS3 ; end if ; -- increment when CS3 => iCCNT <= iCCNT + '1' ; iCCUR <= CS1 ; -- set end flag when CS4 => iCCUR <= CS5 ; -- return first state when CS5 => iCCUR <= CS0 ; -- default when others => iCCUR <= CS0 ; end case ; end if ; end process ; iTIME_END <= '1' when ( iCCUR = CS5 ) else '0' ; -- time edge 100ms process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iTECNT <= "000" ; elsif rising_edge( CLOCK ) then iTECNT <= iTECNT + '1' ; end if ; end process ; iTCLOCK <= '1' when ( iTECNT = "000" ) else '0' ; process ( nRESET , iTCLOCK ) begin if ( nRESET = '0' ) then iTECNTX <= (others => '0') ; elsif rising_edge( iTCLOCK ) then iTECNTX <= iTECNTX + '1' ; if ( conv_integer(iTECNTX) = 1250 ) then iTECNTX <= (others => '0') ; end if ; end if ; end process ; end Behavioral;  この他に、componentで1ブロック利用しているので、その  VHDLコードも記しておきます。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity store_seq is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- data PSA : in std_logic_vector(15 downto 0) ; PSB : in std_logic_vector(15 downto 0) ; PSC : in std_logic_vector(15 downto 0) ; -- trigger TRG : in std_logic ; -- fdiv SCLK : out std_logic ; LOAD : out std_logic ; SA : out std_logic ; SB : out std_logic ; SC : out std_logic ; SEND : out std_logic --; ) ; end store_seq; architecture Behavioral of store_seq is -- sequencer type stype is (S0,S1,S2,S3,S4,S5); signal iSTATE : stype ; signal iCOUNT : std_logic_vector(4 downto 0); begin process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSTATE <= S0 ; iCOUNT <= "00000" ; elsif rising_edge( CLOCK ) then case iSTATE is -- trigger wait when S0 => if ( TRG = '1' ) then iSTATE <= S1 ; iCOUNT <= "00000" ; end if ; -- judge when S1 => if ( iCOUNT(4) = '1' ) then iSTATE <= S5 ; else iSTATE <= S2 ; end if ; -- impress data when S2 => iSTATE <= S3 ; -- clock H when S3 => iSTATE <= S4 ; -- clock L and count up when S4 => iCOUNT <= iCOUNT + '1' ; iSTATE <= S1 ; -- return first state when S5 => iSTATE <= S0 ; -- default when others => iSTATE <= S0 ; end case ; end if ; end process ; SCLK <= '1' when ( iSTATE = S3 ) else '0' ; LOAD <= '1' when ( iSTATE > S0 ) else '0' ; SA <= PSA(15 - conv_integer(iCOUNT)) when ( iSTATE > S0 ) else '0' ; SB <= PSB(15 - conv_integer(iCOUNT)) when ( iSTATE > S0 ) else '0' ; SC <= PSC(15 - conv_integer(iCOUNT)) when ( iSTATE > S0 ) else '0' ; SEND <= '1' when ( iSTATE > S5 ) else '0' ; end Behavioral;  CPLDの中に、Music sequencerを入れる場合、マクロセルが問題になります。  VHDLコードで作成した、このMusic sequencerは、179マクロセル利用して  手持ちのCoolRunnerIIでは、インプリメントできませんでした。  手持ちのFPGAの中にある、Spartan3の5万ゲートクラスでインプリメントできました。  ワンチップマイコンが¥500程度で入手できるので、Music sequencerを  これらのワンチップマイコンで実現する方が、楽ですし、安価です。  Music sequencerで、音階を構成するため、逐次電卓で周波数を計算して  打ち込みするのは面倒です。  楽器のキーボードをみるとわかりますが、五譜紙に記される音階は  周波数は固定です。  周波数が固定されていることを利用して、周波数とその周波数を出す時間を  記述できるように考案された言語があります。  MML(Music Macro Language)と呼ばれ、MSXなどでは標準で使えた言語です。  MMLは、OMRONのWorkStationであるLunaでも標準で使えました。  Lunaは、UNIXを利用したWorkStationで、コストパフォーマンスがよい  マシンでした。  自分もMMLで、高価なWorkStationで音を鳴らして遊んだものです。  MMLについて、説明します。

MML(Music Macro Language)

 Wikiペディアなどで、MMLの説明があるので、簡単に説明します。 MMLは、コンピュータ上にある楽譜をイメージした簡易言語です。  元々は、音楽演奏機能を持つ8ビットパーソナルコンピュータ(以下PC)の  BASICに含まれていました。  PLAYコマンドがあり、そのコマンドのパラメータを表現していました。  BASICがPC標準の言語でなくなっても、楽譜記述用として、しぶとく生き残りました。  現在では、PCと音源や楽器を接続するためにMIDI規格を利用しますが  Standard MIDI File形式や各種ソフトのファイル形式などに変換できる  フィルタプログラムを利用して、MMLから必要な形式に変換し利用されて  います。  利用する英数字には、以下のような意味を持たせています。  (英字では、大文字、小文字の区別はありません。) C D E F G A B それぞれ、ドレミファソラシの音符です。   Aは、440Hzの整数倍、整数分の1に設定されます。   Aを基準として、1オクターブ(2倍の周波数に相当)は   12平均律で、1オクターブを12乗根で等比数列になるように   周波数を割当てます。 # + - 音符の後につけて半音上げ下げを表します。   #と+は同じ意味になります。   #と♭は、楽譜で利用されますが、♭をキーボードで入力   できないので、+と−で半音の上げ下げを指定します。 R 休符。 数字 . 音符や休符の後につけ、音の長さを表します。 2=2分音符。.は付点で長さを1.5倍する。4.=付点4分音符。 A4B4.C2だと、ラの4分音符、シの付点4分音符、ドの2分音符です。  & 二つの音符を連結します。   場合によっては、タイを表します。   システムにより解釈が異なります。  O オクターブを指定します。   一度、音階の前にオクターブを指定すると、次にオクターブを   変更しない限り、そのオクターブを維持します。 L A〜GやRの後に数字をつけない場合の音の長さを指定します。   初期値は、4になっているシステムが多いです。 V 音量(ボリューム)を指定します。   システムにより、対数指定で1〜10であったり、リニアに   0〜255のようにしたりで、違いがあります。 @ FM音源などでの音色指定に使います。   StandardMIDIでは、FM音源で出力する楽器の音が規定されています。   楽器の音を、1〜256に分類してあるので、@に続けて楽器の番号   を指定します。 T テンポを指定。たとえば「T120」なら120BPMで演奏します。   コンピュータによっては、テンポのずれが発生します。   テンポずれは、非同期処理をしているシステムで発生します。  人間が打ち込んだMMLで記述した楽譜を、CPLD/FPGAで周波数や時間待ち  の数値を生成する機械を実現できます。しかし、ワンチップマイコンが  ワンコインで購入できる時代なので、データ変換処理はマイコンが担当  する仕事にした方が、開発期間は短くなります。

音に変化をつける方法

 これまでに説明してきた音は、矩形波です。  スピーカで聞いてみると、汚い音です。  矩形波は、基本波の他に倍音を大量に含むので  汚い音にしかなりません。  澄んだ音を出す場合には、4つのフィルタを組合わせる方法があります。  フィルタは、扱う周波数により、以下の4つに分類できます。  最近は、これらのフィルタはDSP(Discrete Signal Processor)を使い  計算で実現します。  DSPを構成する方法は、別のページで紹介することにして、ここでは  アナログ回路でフィルタを作ってみます。  OPアンプを利用し、パッシブタイプのLPF、HPFを作ると以下となります。  どの周波数まで通すかを決めるには、R1とC1による時定数を計算します。  R1=1kohm、C1=0.1uFでは、R1xC1=0.1msとなり、約10kHzのLPF、HPFを  作れます。  C1を小さくすると、高い周波数のLPF、HPFを構成できます。  2段目のOPアンプでは、反転増幅しています。  1段目で逆相になった波形を、2段目で正相に戻しています。  また、RCのパッシブでは、電圧利得が6dB落ちるので、2段目で  6dB回復させます。  BPF(Band Pass Filter)は、LPFとHPFを接続すると実現できます。  ここまでは、矩形波から澄んだ音を得るための処理でした。  複数の矩形波を加算、乗算すると、面白い音を作れます。  加算処理は、OPアンプによる加算回路を構成します。  同一振幅の信号を加算すると飽和するので、一度アッティネータで  振幅を小さくします。その後、反転加算します。  乗算処理は、アナログスイッチを利用したデータセレクタを  利用します。  スイッチング処理は、乗算になります。  この回路は、振幅変調をデジタルで実現します。  スイッチングに利用する信号を、530kHz〜1600kHzの矩形波に  すると、AMワイヤレスマイクの基本回路になります。  デジタル出力を、アナログ回路で操作すると  面白い音を簡単に作れます。  CPLD/FPGAに封入したデジタル回路と外付けアナログ回路で  面白い音を作れますが、FM音源の利用で、デジタルだけで  いろいろな音を作り出せます。  FM音源に必要となる、正弦波生成をDDSで作成し  FM音源を作ってみます。

DDS(Direct Digital Synthesizer)の活用

 DDSは、日本語ではディジタル直接合成発振器と呼ばれています。  加算器、ラッチ、アキュムレータを構成し、クロックに同期して  周波数に相当する値N(周波数設定値)を累積します。  累積値を、波形ROMのアドレスとして、デジタルデータを取得します。  デジタルデータをD/Aコンバータでアナログ値に変換します。  D/Aコンバータ出力は階段波なので、LPFで高調波成分を除去して  綺麗なアナログ値にします。  周波数は、加算器のビット数をnとすると、次のように計算できます。   発振周波数=周波数設定値×クロック周波数÷(2のn乗)  波形ROMの内容を、正弦波、三角波等にすれば、任意の波形を生成できます。  D/Aコンバータは、R-2Rラダー型にすると、CPLD/FPGAに抵抗を 接続して簡単に実現できます。  D/Aコンバータは、外付け抵抗で実現できるので、ROMを  どうにかしなければなりません。  FM音源を実現する場合は、正弦波を1波長分用意すれば  使い回せるので、CPLD/FPGAの中にROMを定義し、その中  に、1波長の正弦波データを入れます。  ROMは、次のように定義します。 subtype DTYPE is std_logic_vector(3 downto 0) ; type DEC is array(0 to 15) of DTYPE ; constant DECX : DEC := ( "0000", "0001", "0001", "0010", "0001", "0010", "0010", "0011", "0001", "0010", "0010", "0011", "0010", "0011", "0011", "0100" ) ;  最初に、subtypeでデータサイズを定義します。  定義したデータサイズで、配列を作り、その中に  固定データを入れるとROMになります。  constantは、固定データを意味します。従って  この修飾子をつけないと、RAMを定義することに  なります。(RAMでは、データ定義をしません。)  正弦波の1波長データを作成します。電卓を叩くのは  面倒なので、プログラムに生成させます。  振幅が最大255、256個で正弦波の1波長になるような  データを生成するTcl/Tkコードは、以下です。 #!/usr/local/bin/wish # procedure proc h2b {x} { set tmp $x set result "" # set result "$result[expr $tmp / 8]" set tmp [expr $tmp % 8] # set result "$result[expr $tmp / 4]" set tmp [expr $tmp % 4] # set result "$result[expr $tmp / 2]" set result "$result[expr $tmp % 2]" return $result } proc hh2bb {x} { set ud [expr $x / 16] set ld [expr $x % 16] set result "\"[h2b $ud][h2b $ld]\"" return $result } # calculate pi set xpi [expr 4 * atan(1)] set xpi2 [expr 2 * $xpi] set fd_out [open "sincode.txt" "w"] set tmp "" # generate code for {set i 0} {$i < 256} {incr i} { # calculate set val0 [expr 127*(sin(($xpi2*$i)/256)+1)] # convert float to integer set val [expr int($val0)] # concatenate set tmp "$tmp[hh2bb $val]," # store code to file if { [expr $i % 8] == 7 } { puts $fd_out $tmp set tmp "" } } close $fd_out  Tcl/Tkで求めた値は、次のようになります。 "01111111","10000010","10000101","10001000","10001011","10001110","10010001","10010100", "10010111","10011010","10011101","10100000","10100011","10100110","10101001","10101100", "10101111","10110010","10110101","10111000","10111010","10111101","11000000","11000010", "11000101","11001000","11001010","11001101","11001111","11010001","11010100","11010110", "11011000","11011010","11011101","11011111","11100001","11100011","11100101","11100110", "11101000","11101010","11101011","11101101","11101111","11110000","11110001","11110011", "11110100","11110101","11110110","11110111","11111000","11111001","11111010","11111010", "11111011","11111100","11111100","11111101","11111101","11111101","11111101","11111101", "11111110","11111101","11111101","11111101","11111101","11111101","11111100","11111100", "11111011","11111010","11111010","11111001","11111000","11110111","11110110","11110101", "11110100","11110011","11110001","11110000","11101111","11101101","11101011","11101010", "11101000","11100110","11100101","11100011","11100001","11011111","11011101","11011010", "11011000","11010110","11010100","11010001","11001111","11001101","11001010","11001000", "11000101","11000010","11000000","10111101","10111010","10111000","10110101","10110010", "10101111","10101100","10101001","10100110","10100011","10100000","10011101","10011010", "10010111","10010100","10010001","10001110","10001011","10001000","10000101","10000010", "01111111","01111011","01111000","01110101","01110010","01101111","01101100","01101001", "01100110","01100011","01100000","01011101","01011010","01010111","01010100","01010001", "01001110","01001011","01001000","01000101","01000011","01000000","00111101","00111011", "00111000","00110101","00110011","00110000","00101110","00101100","00101001","00100111", "00100101","00100011","00100000","00011110","00011100","00011010","00011000","00010111", "00010101","00010011","00010010","00010000","00001110","00001101","00001100","00001010", "00001001","00001000","00000111","00000110","00000101","00000100","00000011","00000011", "00000010","00000001","00000001","00000000","00000000","00000000","00000000","00000000", "00000000","00000000","00000000","00000000","00000000","00000000","00000001","00000001", "00000010","00000011","00000011","00000100","00000101","00000110","00000111","00001000", "00001001","00001010","00001100","00001101","00001110","00010000","00010010","00010011", "00010101","00010111","00011000","00011010","00011100","00011110","00100000","00100011", "00100101","00100111","00101001","00101100","00101110","00110000","00110011","00110101", "00111000","00111011","00111101","01000000","01000011","01000101","01001000","01001011", "01001110","01010001","01010100","01010111","01011010","01011101","01100000","01100011", "01100110","01101001","01101100","01101111","01110010","01110101","01111000","01111011",  求められた値を、VHDLコードでROMデータとします。 subtype DTYPE is std_logic_vector(7 downto 0) ; type SINTABLE is array(0 to 255) of DTYPE ; constant SINDATA : SINTABLE := ( "01111111","10000010","10000101","10001000","10001011","10001110","10010001","10010100", "10010111","10011010","10011101","10100000","10100011","10100110","10101001","10101100", "10101111","10110010","10110101","10111000","10111010","10111101","11000000","11000010", "11000101","11001000","11001010","11001101","11001111","11010001","11010100","11010110", "11011000","11011010","11011101","11011111","11100001","11100011","11100101","11100110", "11101000","11101010","11101011","11101101","11101111","11110000","11110001","11110011", "11110100","11110101","11110110","11110111","11111000","11111001","11111010","11111010", "11111011","11111100","11111100","11111101","11111101","11111101","11111101","11111101", "11111110","11111101","11111101","11111101","11111101","11111101","11111100","11111100", "11111011","11111010","11111010","11111001","11111000","11110111","11110110","11110101", "11110100","11110011","11110001","11110000","11101111","11101101","11101011","11101010", "11101000","11100110","11100101","11100011","11100001","11011111","11011101","11011010", "11011000","11010110","11010100","11010001","11001111","11001101","11001010","11001000", "11000101","11000010","11000000","10111101","10111010","10111000","10110101","10110010", "10101111","10101100","10101001","10100110","10100011","10100000","10011101","10011010", "10010111","10010100","10010001","10001110","10001011","10001000","10000101","10000010", "01111111","01111011","01111000","01110101","01110010","01101111","01101100","01101001", "01100110","01100011","01100000","01011101","01011010","01010111","01010100","01010001", "01001110","01001011","01001000","01000101","01000011","01000000","00111101","00111011", "00111000","00110101","00110011","00110000","00101110","00101100","00101001","00100111", "00100101","00100011","00100000","00011110","00011100","00011010","00011000","00010111", "00010101","00010011","00010010","00010000","00001110","00001101","00001100","00001010", "00001001","00001000","00000111","00000110","00000101","00000100","00000011","00000011", "00000010","00000001","00000001","00000000","00000000","00000000","00000000","00000000", "00000000","00000000","00000000","00000000","00000000","00000000","00000001","00000001", "00000010","00000011","00000011","00000100","00000101","00000110","00000111","00001000", "00001001","00001010","00001100","00001101","00001110","00010000","00010010","00010011", "00010101","00010111","00011000","00011010","00011100","00011110","00100000","00100011", "00100101","00100111","00101001","00101100","00101110","00110000","00110011","00110101", "00111000","00111011","00111101","01000000","01000011","01000101","01001000","01001011", "01001110","01010001","01010100","01010111","01011010","01011101","01100000","01100011", "01100110","01101001","01101100","01101111","01110010","01110101","01111000","01111011" ) ;  人間の可聴帯域は、20Hz〜20kHzなので、最高周波数の20kHzを  出すためには、20kHz x 256 = 5120kHz = 5.12MHz が必要です。  デジタル回路で利用するクリスタルとして、10.24MHzを入手できる  ので、CPLD/FPGAで使う周波数として、この値を利用します。  20kHzの正弦波が得られると、その整数倍の正弦波を得ることは  DDSを使うと簡単にできます。  ROMのアドレスを1ずつ増やすと、20kHzの正弦波になります。  ROMのアドレスを2ずつ増やすと(偶数アドレスを生成すると)  40kHzの20kHzの正弦波になります。  増分を1からN(N=2,3,4,..)に変えると、MHzの正弦波から  MxNHzの正弦波を生成できます。  M、Nの組み合わせは、無限に存在しますが、M、N単独では  有限個の選択肢になります。  Nの場合は、ROMのアドレスを0〜255にしてあるので、1〜63  の自然数になります。正弦波のグラフを描くと、この範囲になる  ことがわかります。  正弦波のグラフから、1/4波長のデータがあれば、振幅は再現できると  読み取れます。   0〜1/4波長までは、振幅は増加していきます。1/4波長を超えると   振幅は減少していきます。これで、1/2波長まで振幅を再生できます。   正の1/2波長の振幅を、負の方向に折り返すと、負の1/2波長の振幅を   再生できます。  少し頭を捻ると、1/4波長までをカバーするNの値でないと、振幅の再生は  できないと理解できるでしょう。  0〜255には、256個のアドレスがあるので、アドレスにNを加算していくと  1/4波長に相当する63が最大になります。  1/4波長に相当する値を超えると、その値より小さいNの値を加算している  のと同じになります。  Mの値は、利用しているクロック周波数に依存します。  人間の可聴帯域は、20Hzから20kHzなので、5.12MHzを利用  している場合、5.12MHz/256=20kHzです。  10kHzを得たい場合には、5.12MHz/(256x2)=10kHzになります。  ほしい周波数MHzを決めて、次の式でmを計算します。   20000Hz/M = m   この式は、次の計算で算出しました。    5120000Hz/(256xm)= M Hz    20000Hz/m = M Hz  Mをmに換算してみると、20Hzから20kHzなので  mは、1〜1000になります。  人間の可聴帯域を生成する場合には、M(m)の値を変化  させ、そのN倍の周波数を得たい時は、Nの値を操作します。  Mで発振する正弦波の周波数を決定し、Nでその整数倍の  正弦波を生成すると考えればよいでしょう。  DDSを利用すると、使っているクロックの整数倍の周波数を  生成できます。  DDSを作成するため、ブロック図を描きました。  ブロック図を見ながら、必要な信号と内部ブロックを検討します。  ROM内データを出力するため、ROMにアドレスを与えます。  アドレス生成のために、Adderを使います。  必要なときに音を出せるよう、ENABLE信号を設けます。  Adderは、DREGに格納された値を、クロックに同期させて加算します。  加算結果を、ROMのアドレスとします。  DREGの値は、外部から設定します。  (DREGに設定する値が、Nに相当)  Adderに与えるクロックを作成しなければなりません。  クロックは、FDIVに設定した値とCOUNTERの値が一致した  ときに、出力されるよう構成します。  (FDIVに設定する値が、Mに相当)  ここまで検討したので、Entityを定義します。 entity dds0 is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- 10.24MHz -- fdiv LCLK : in std_logic ; FLOAD : in std_logic ; DLOAD : in std_logic ; SIN : in std_logic ; -- dds out ENABLE : in std_logic ; DDSOUT : out std_logic_vector(7 downto 0) -- ; ) ; end dds0;  FDIV、DREGに値を設定するには、シリアル転送にします。  パラレルで転送すると、CPLD/FPGAの入出力ピンを大量に  消費するので、シリアル転送で、利用ピンを減らします。  内部レジスタへの転送処理は、次のように定義しました。 process ( nRESET , LCLK ) begin if ( nRESET = '0' ) then iFDIV <= (others => '0') ; iDREG <= "000001" ; elsif rising_edge( LCLK ) then -- divider if ( FLOAD = '1' ) then iFDIV <= iFDIV(14 downto 0) & SIN ; end if ; -- difference if ( DLOAD = '1' ) then iDREG <= iDREG(4 downto 0) & SIN ; end if ; end if ; end process ;  FDIVに設定した値で、MHzのMに相当する周波数のクロックを生成します。  このクロックを生成するブロックを定義します。 process ( nRESET , iSCLK ) begin if ( nRESET = '0' ) then iCOUNT <= (others => '0') ; elsif rising_edge( iSCLK ) then iCOUNT <= iCOUNT + '1' ; if ( iCOUNT = iFDIV ) then iCOUNT <= (others => '0') ; end if ; end if ; end process ; iSSCLK <= '1' when ( conv_integer(iCOUNT) = 0 ) else '0' ;  可聴帯域の周波数を生成するため、システムクロックから  iSCLKを生成します。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCNT <= "00" ; elsif rising_edge( CLOCK ) then iSCNT <= iSCNT + '1' ; end if ; end process ; iSCLK <= '1' when ( iSCNT(1) = '0' ) else '0' ;  最後に、ROMのアドレスを生成するブロックを定義します。 process ( nRESET , iSSCLK ) begin if ( nRESET = '0' ) then iROMADR <= (others => '0') ; elsif rising_edge( iSSCLK ) then iROMADR <= iROMADR + ('0' & iDREG) ; end if ; end process ; iDDSOUT <= SINDATA( conv_integer(iROMADR) ) ;  DDSを実現するVHDLコード全体は、以下となります。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity dds0 is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- 10.24MHz -- fdiv LCLK : in std_logic ; FLOAD : in std_logic ; DLOAD : in std_logic ; SIN : in std_logic ; -- dds out ENABLE : in std_logic ; DDSOUT : out std_logic_vector(7 downto 0) -- ; ) ; end dds0; architecture Behavioral of dds0 is -- sound clock signal iSCNT : std_logic_vector(1 downto 0); signal iSCLK : std_logic ; -- fdiv signal iFDIV : std_logic_vector(15 downto 0); signal iDREG : std_logic_vector( 5 downto 0); -- M divider signal iCOUNT : std_logic_vector(15 downto 0); signal iSSCLK : std_logic ; -- ROM subtype DTYPE is std_logic_vector(7 downto 0) ; type SINTABLE is array(0 to 255) of DTYPE ; constant SINDATA : SINTABLE := ( "01111111","10000010","10000101","10001000","10001011","10001110","10010001","10010100", "10010111","10011010","10011101","10100000","10100011","10100110","10101001","10101100", "10101111","10110010","10110101","10111000","10111010","10111101","11000000","11000010", "11000101","11001000","11001010","11001101","11001111","11010001","11010100","11010110", "11011000","11011010","11011101","11011111","11100001","11100011","11100101","11100110", "11101000","11101010","11101011","11101101","11101111","11110000","11110001","11110011", "11110100","11110101","11110110","11110111","11111000","11111001","11111010","11111010", "11111011","11111100","11111100","11111101","11111101","11111101","11111101","11111101", "11111110","11111101","11111101","11111101","11111101","11111101","11111100","11111100", "11111011","11111010","11111010","11111001","11111000","11110111","11110110","11110101", "11110100","11110011","11110001","11110000","11101111","11101101","11101011","11101010", "11101000","11100110","11100101","11100011","11100001","11011111","11011101","11011010", "11011000","11010110","11010100","11010001","11001111","11001101","11001010","11001000", "11000101","11000010","11000000","10111101","10111010","10111000","10110101","10110010", "10101111","10101100","10101001","10100110","10100011","10100000","10011101","10011010", "10010111","10010100","10010001","10001110","10001011","10001000","10000101","10000010", "01111111","01111011","01111000","01110101","01110010","01101111","01101100","01101001", "01100110","01100011","01100000","01011101","01011010","01010111","01010100","01010001", "01001110","01001011","01001000","01000101","01000011","01000000","00111101","00111011", "00111000","00110101","00110011","00110000","00101110","00101100","00101001","00100111", "00100101","00100011","00100000","00011110","00011100","00011010","00011000","00010111", "00010101","00010011","00010010","00010000","00001110","00001101","00001100","00001010", "00001001","00001000","00000111","00000110","00000101","00000100","00000011","00000011", "00000010","00000001","00000001","00000000","00000000","00000000","00000000","00000000", "00000000","00000000","00000000","00000000","00000000","00000000","00000001","00000001", "00000010","00000011","00000011","00000100","00000101","00000110","00000111","00001000", "00001001","00001010","00001100","00001101","00001110","00010000","00010010","00010011", "00010101","00010111","00011000","00011010","00011100","00011110","00100000","00100011", "00100101","00100111","00101001","00101100","00101110","00110000","00110011","00110101", "00111000","00111011","00111101","01000000","01000011","01000101","01001000","01001011", "01001110","01010001","01010100","01010111","01011010","01011101","01100000","01100011", "01100110","01101001","01101100","01101111","01110010","01110101","01111000","01111011" ) ; -- dds out signal iROMADR : std_logic_vector(7 downto 0); signal iDDSOUT : std_logic_vector(7 downto 0); begin -- sound clock process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSCNT <= "00" ; elsif rising_edge( CLOCK ) then iSCNT <= iSCNT + '1' ; end if ; end process ; iSCLK <= '1' when ( iSCNT(1) = '0' ) else '0' ; -- fdiv and difference process ( nRESET , LCLK ) begin if ( nRESET = '0' ) then iFDIV <= (others => '0') ; iDREG <= "000001" ; elsif rising_edge( LCLK ) then -- divider if ( FLOAD = '1' ) then iFDIV <= iFDIV(14 downto 0) & SIN ; end if ; -- difference if ( DLOAD = '1' ) then iDREG <= iDREG(4 downto 0) & SIN ; end if ; end if ; end process ; -- M divider process ( nRESET , iSCLK ) begin if ( nRESET = '0' ) then iCOUNT <= (others => '0') ; elsif rising_edge( iSCLK ) then iCOUNT <= iCOUNT + '1' ; if ( iCOUNT = iFDIV ) then iCOUNT <= (others => '0') ; end if ; end if ; end process ; iSSCLK <= '1' when ( conv_integer(iCOUNT) = 0 ) else '0' ; -- generate ROM address process ( nRESET , iSSCLK ) begin if ( nRESET = '0' ) then iROMADR <= (others => '0') ; elsif rising_edge( iSSCLK ) then iROMADR <= iROMADR + ('0' & iDREG) ; end if ; end process ; iDDSOUT <= SINDATA( conv_integer(iROMADR) ) ; -- dds out DDSOUT <= iDDSOUT when ( ENABLE = '1' ) else (others => '0') ; end Behavioral;  テストは、CoolRunnerIIのXC2C256が載ったボードを  使いました。M、Nの設定には、CPLDボードにマイコン  を接続し、PCからシリアルで設定しました。  DDSを利用すると、PSGだけでなく、FM音源や無線機用  発振器を作成できます。  FM音源を作成してみます。

FM音源の原理

 Fouier変換で有名な、Fourierはすべての波は、正弦波の  組合せでできていると証明しました。  正弦波を組合わせて、どんな波でも生成できるので  複数の正弦波を作り、合成すると音ができます。  合成をどう実現するのかが、問題です。  大学では、Fourier級数を利用した、基本波の整数倍の正弦波を  複数用意し、各正弦波の振幅を変え、加算する方法を説明され  演習で確認します。  スプレッドシート(表計算ソフト)を利用すると、一瞬で結果が  出てくるので、簡単な方法です。  スプレッドシートを利用して、基本波、3倍高調波、5倍高調波  を生成し、加算してみると、以下となります。  (振幅は、1、0.5、0.25として加算してあります。)  この方法は、簡単なのですが、問題があります。  複数の正弦波を用意し、振幅を変えるために、掛算が  必要になり計算が大変です。  掛算をしつつ加算する処理になるので、DSPを使えば、それほど  大変ではないのですが、CPLD/FPGAにDSPを入れるのは面倒です。  DSPをCPLD/FPGAの中に封入すれば、実現できますが、利用する  マクロセル数やゲート数が膨大になります。  マクロセル数やゲート数が膨大になると、CPLD/FPGAのマクロセル数や  ゲート数が大きなチップを採用することになります。  何とか、マクロセル数やゲート数を減らし、豊かな音を作り  出したいと考えた、技術者が選択した方式が、FM音源です。  豊かな音作りに挑戦した技術者は、楽器メーカのYAMAHAにいました。  FM音源の特許は、YAMAHAが所有しています。  YAMAHAは、FM音源のICを設計し、小型安価なPortaSoundという  ミュージックシンセサイザを製造しました。  ミュージックシンセサイザDX-7にも、FM音源を載せたので  DX-7は大物ミュージシャンが使う名器になりました。  ただし、DX-7に載せたFM音源ICは、市販されませんでしたが。  最近では、携帯電話で着メロにFM音源が利用されています。  FM音源は、ソフトでもハードでも実現できるので、小さな  マイコンでも1音程度は生成できます。  最近の携帯電話は、OSとしてAndroidを載せていることが多い  ですが、OSを載せられる程度のマイコンを利用しているので  FM音源をソフトウエアで実現するのは簡単です。  歴史や応用例は、このくらいにして、FM音源の原理を説明します。  FM音源の原理はとても単純で、次の式をソフトか  ハードで実現するだけです。 sin(f+Rsin(Nf))  正弦波を生成する式の中に、また正弦波の式が出てきます。  この式を少し、整理してみましょう。 sin(f+p) p=Rsin(Nf)  左は、基本波の式で、周波数はfです。  pが位相になります。  右は、基本波の整数倍で発振して、振幅に相当する値を  掛けています。  FM音源は、基本波の位相を、基本波の整数倍の正弦波で  変化させているということです。  どんな波形になるのかを、スプレッドシートで描いて  みると、次のようになります。   (sin(f+0.5sin(3f)) を生成しています。)  上には、基本波と3倍高調波を描いてあります。  合成波形が、白で描かれているのですが、見えにくい  ので、合成波形だけを取り出して、下に描きました。  たった2つの周波数を組合わせるだけで、複雑な波形を  生成できるので、基本波と高調波を加算するよりも簡便  です。  同じ式を利用して、Rに相当する部分を変化させると  次のような波形になります。  この波形をどこかで見た記憶があるかも知れません。  FM放送の周波数変調波形が、このようになります。   ※FM音源は、周波数変調の親戚と言えるでしょう。  FM音源を実現するために、式を見直してみます。 sin(f+p)   p=Rsin(Nf)  基本周波数の整数倍の正弦波が必要と判断できます。  要するにDDSです。

FM音源の実現

 FM音源で音色を作るためには、3つの概念を利用します。  その3つの概念は、以下です。  FM音源を実現するための式で見ると、  キャリア、モジュレータは単純です。 sin(f+p) p=Rsin(Nf)  があれば、pがモジュレータです。  キャリアは、sin(f+p)になります。  アルゴリズムは、キャリアとモジュレータの  組合せを指します。  キャリア、モジュレータを式で表すのは面倒なので  キャリア、モジュレータを箱として表現します。  最初、FM音源のシンセサイザを触ったとき、アルゴリズムの  概念を理解していなかったので、とんでもない音を出して、  合奏者に驚かれたことがありました。  アルゴリズムは、変調の掛け具合を決める  重要なパラメータです。  楽器の音をシミュレートする場合、どんなアルゴリズムを  使うのかは、だいたい決まるのですが、楽器の音には千差  万別の個性があるので、無限個の組合せが存在します。  ピアノであればYAMAHAとSTAINWAYの違いを出すことも  原理上可能ですが、グランドとアップライトのタイプ  のちがいや音響環境にも影響されます。  アルゴリズムの表現は、次のようにします。  モジュレータ、キャリアをオペレータとまとめて呼びます。  各オペレータを矢印で結いで描き、どのオペレータの出力が  どのオペレータに 入力されるのかを記します。  FM音源の基本となる式を、アルゴリズムで表現すると  次のようになります。 sin(f+p)とp=Rsin(Nf)を接続しています。  アルゴリズムは、出力信号が右になるように描きますが  キャリアとモジュレータの関係がわかるように、出力は  左に描いて、わかりやすくしておきます。  オペレータのうち、モジュールには、掛算が必要です。  デジタル回路の中に掛算をするときは、2のべき乗で  操作できるようにします。  2から10倍の生成方法を考えてみます。  10倍までならば、補助レジスタを2つ用意すると対応できます。  補助レジスタが、reg0、reg1とreg2の3つあるとし  倍数処理を検討します。  2倍   左に1ビットシフトすればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に1ビットシフト reg0 + reg1 + reg2 を計算  3倍   2倍値と元の値を加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に1ビットシフト reg0 + reg1 + reg2 を計算  4倍   左に2ビットシフトすればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = 0 , reg2 = 0 と設定 reg0の内容を左に2ビットシフト reg0 + reg1 + reg2 を計算  5倍   4倍値と元の値を加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に2ビットシフト reg0 + reg1 + reg2 を計算  6倍   4倍値と2倍値の加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に2ビットシフト、reg1の内容を左に1ビットシフト reg0 + reg1 + reg2 を計算  7倍   4倍値、2倍値、元の値を加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = x と設定 reg0の内容を左に2ビットシフト、reg1の内容を左に1ビットシフト reg0 + reg1 + reg2 を計算  8倍   左に3ビットシフトすればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に3ビットシフト reg0 + reg1 + reg2 を計算  9倍   8倍値、元の値を加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に3ビットシフト reg0 + reg1 + reg2 を計算  10倍   8倍値、2倍値を加算を加算すればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を左に3ビットシフト、reg1の内容を左に1ビットシフト reg0 + reg1 + reg2 を計算  各処理のシーケンスを眺めると、次の処理で倍数処理を  実現していることがわかります。
  1. reg0、reg1の値を初期化
  2. 左シフト処理
  3. 加算
 2〜10までの整数倍は、上のシーケンスで実現できます。  整数で割る処理を考えてみます。  0.5倍、0.25倍、0.125倍は、シフト処理で対応できます。  この組合せで他にできる掛算は、つぎのようになります。  整数倍同様に、乗算処理を考えてみます。  0.875倍   0.5倍値、0.25倍値、0.125倍値を加算すればよいので   元の値をxとして次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = x と設定 reg0の内容を右に1ビットシフト、reg1の内容を右に2ビットシフト、   reg2の内容を右に3ビットシフト reg0 + reg1 + reg2 を計算  0.750倍   0.5倍値、0.25倍値を加算すればよいので   元の値をxとして次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を右に1ビットシフト、reg1の内容を右に2ビットシフト reg0 + reg1 + reg2 を計算  0.625倍   0.5倍値、0.125倍値を加算すればよいので   元の値をxとして次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を右に1ビットシフト、reg1の内容を右に3ビットシフト reg0 + reg1 + reg2 を計算  0.5倍   0.5倍値を求めればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = 0 , reg2 = 0 と設定 reg0の内容を右に1ビットシフト reg0 + reg1 + reg2 を計算  0.375倍   0.25倍値、0.125倍値を加算すればよいので   元の値をxとして次のシーケンスを使います。 reg0 = x , reg1 = x , reg2 = 0 と設定 reg0の内容を右に2ビットシフト、reg1の内容を右に3ビットシフト reg0 + reg1 + reg2 を計算  0.25倍   0.25倍値を求めればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = 0 , reg2 = 0 と設定 reg0の内容を右に2ビットシフト reg0 + reg1 + reg2 を計算  0.125倍   0.125倍値を求めればよいので、元の値をxとして   次のシーケンスを使います。 reg0 = x , reg1 = 0 , reg2 = 0 と設定 reg0の内容を右に3ビットシフト reg0 + reg1 + reg2 を計算  各処理のシーケンスを眺めると、次の処理で倍数処理を  実現していることがわかります。
  1. reg0、reg1、reg2の値を初期化
  2. 右シフト処理
  3. 加算
 乗算処理は、整数倍に9通り、小数倍に7通りの合計16通り  考えられます。動作シーケンスは同じで、初期値とシフト  するビット数が異なるだけです。  8ビットの整数に乗算する処理をVHDLで定義します。  仕様を検討します。   8ビットの最大値は、255なので10倍すると2550となり   結果を保存するには、12ビット必要です。   乗算種別は、16通りあるので、乗算種別の設定に   4ビット必要です。   4ビットの乗算種別指定は、次のようにします。 0000 2倍 0001 3倍 0010 4倍 0011 5倍 0100 6倍 0101 7倍 0110 8倍 0111 9倍 1000 10倍 1001 0.875倍 1010 0.750倍 1011 0.625倍 1100 0.500倍 1101 0.375倍 1110 0.250倍 1111 0.125倍   乗算開始のトリガーを用意し、計算中であることを示す   フラグを用意します。  ここまでの仕様検討で、Entityを作成できます。 entity mulx0 is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- select SEL : in std_logic_vector(3 downto 0) ; -- trigger TRG : in std_logic ; -- data ADAT : in std_logic_vector(7 downto 0) ; -- output BUSY : out std_logic ; RESEULT : out std_logic_vector(11 downto 0) --; ) ; end mulx0;  動作は、シーケンスになっています。  シーケンスをまとめて、コードを作成しやすくします。
  1. トリガー待ち
  2. reg0初期化
  3. 内部レジスタ初期化
  4. シフト処理
  5. 加算
 ステートマシンを構成します。 process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSTATE <= S0 ; iREG0 <= (others => '0') ; iREG1 <= (others => '0') ; iREG2 <= (others => '0') ; iRESULT <= (others => '0') ; elsif rising_edge( CLOCK ) then case iSTATE is -- wait trigger when S0 => if ( TRG = '1' ) then iSTATE <= S1 ; end if ; -- initialize registers when S1 => iSTATE <= S2 ; iSEL <= SEL ; iREG0 <= "0000" & ADAT ; iREG1 <= (others => '0') ; iREG2 <= (others => '0') ; iRESULT <= (others => '0') ; -- configure registers when S2 => iSTATE <= S3 ; case iSEL is -- x 3 , x 5 , x 6 when "0001" | "0011" | "0100" => iREG1 <= iREG0 ; -- x 7 , x 0.875 when "0101" | "1001" => iREG1 <= iREG0 ; iREG2 <= iREG0(7 downto 0) ; -- x 9 , x 10 when "0111" | "1000" => iREG1 <= iREG0 ; -- x 0.750 , x 0.625 , x 0.375 when "1010" | "1011" | "1101" => iREG1 <= iREG0 ; -- default when others => NULL ; end case ; -- shift when S3 => iSTATE <= S4 ; -- x 2 , x 3 if ( iSEL(3 downto 1) = "000" ) then iREG0 <= iREG0(10 downto 0) & '0' ; end if ; -- x 4 , x 5 if ( iSEL(3 downto 1) = "001" ) then iREG0 <= iREG0( 9 downto 0) & "00" ; end if ; -- x 6 , x 7 if ( iSEL(3 downto 1) = "010" ) then iREG0 <= iREG0( 9 downto 0) & "00" ; iREG1 <= iREG1(10 downto 0) & '0' ; end if ; -- x 8 , x 9 if ( iSEL(3 downto 1) = "011" ) then iREG0 <= iREG0( 8 downto 0) & "000" ; end if ; case iSEL is -- x 10 when "1000" => iREG0 <= iREG0( 8 downto 0) & "000" ; iREG1 <= iREG1(10 downto 0) & '0' ; -- x 0.875 when "1001" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "00" & iREG1(11 downto 2) ; iREG2 <= "000" & iREG2(7 downto 3) ; -- x 0.750 when "1010" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "00" & iREG1(11 downto 2) ; -- x 0.625 when "1011" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "000" & iREG1(11 downto 3) ; -- x 0.500 when "1100" => iREG0 <= '0' & iREG0(11 downto 1) ; -- x 0.375 when "1101" => iREG0 <= "00" & iREG0(11 downto 2) ; iREG1 <= "000" & iREG1(11 downto 3) ; -- x 0.250 when "1110" => iREG0 <= "00" & iREG0(11 downto 2) ; -- x 0.125 when "1111" => iREG0 <= "000" & iREG0(11 downto 3) ; -- default when others => NULL ; end case ; -- add when S4 => iSTATE <= S5 ; iRESULT <= iREG0 + iREG1 + ("0000" & iREG2) ; -- return first state when S5 => iSTATE <= S0 ; -- default when others => iSTATE <= S0 ; end case ; end if ; end process ;  全ソースコードは、以下です。 library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_ARITH.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; entity mulx0 is Port ( -- system nRESET : in std_logic ; CLOCK : in std_logic ; -- select SEL : in std_logic_vector(3 downto 0) ; -- trigger TRG : in std_logic ; -- data ADAT : in std_logic_vector(7 downto 0) ; -- output BUSY : out std_logic ; RESULT : out std_logic_vector(11 downto 0) --; ) ; end mulx0; architecture Behavioral of mulx0 is -- result signal iRESULT : std_logic_vector(11 downto 0) ; -- select signal iSEL : std_logic_vector(3 downto 0) ; -- internal register signal iREG0 : std_logic_vector(11 downto 0) ; signal iREG1 : std_logic_vector(11 downto 0) ; signal iREG2 : std_logic_vector( 7 downto 0) ; -- state type stype is (S0,S1,S2,S3,S4,S5); signal iSTATE : stype ; begin RESULT <= iRESULT ; BUSY <= '1' when ( iSTATE > S0 ) else '0' ; process ( nRESET , CLOCK ) begin if ( nRESET = '0' ) then iSTATE <= S0 ; iREG0 <= (others => '0') ; iREG1 <= (others => '0') ; iREG2 <= (others => '0') ; iRESULT <= (others => '0') ; elsif rising_edge( CLOCK ) then case iSTATE is -- wait trigger when S0 => if ( TRG = '1' ) then iSTATE <= S1 ; end if ; -- initialize registers when S1 => iSTATE <= S2 ; iSEL <= SEL ; iREG0 <= "0000" & ADAT ; iREG1 <= (others => '0') ; iREG2 <= (others => '0') ; iRESULT <= (others => '0') ; -- configure registers when S2 => iSTATE <= S3 ; case iSEL is -- x 3 , x 5 , x 6 when "0001" | "0011" | "0100" => iREG1 <= iREG0 ; -- x 7 , x 0.875 when "0101" | "1001" => iREG1 <= iREG0 ; iREG2 <= iREG0(7 downto 0) ; -- x 9 , x 10 when "0111" | "1000" => iREG1 <= iREG0 ; -- x 0.750 , x 0.625 , x 0.375 when "1010" | "1011" | "1101" => iREG1 <= iREG0 ; -- default when others => NULL ; end case ; -- shift when S3 => iSTATE <= S4 ; -- x 2 , x 3 if ( iSEL(3 downto 1) = "000" ) then iREG0 <= iREG0(10 downto 0) & '0' ; end if ; -- x 4 , x 5 if ( iSEL(3 downto 1) = "001" ) then iREG0 <= iREG0( 9 downto 0) & "00" ; end if ; -- x 6 , x 7 if ( iSEL(3 downto 1) = "010" ) then iREG0 <= iREG0( 9 downto 0) & "00" ; iREG1 <= iREG1(10 downto 0) & '0' ; end if ; -- x 8 , x 9 if ( iSEL(3 downto 1) = "011" ) then iREG0 <= iREG0( 8 downto 0) & "000" ; end if ; case iSEL is -- x 10 when "1000" => iREG0 <= iREG0( 8 downto 0) & "000" ; iREG1 <= iREG1(10 downto 0) & '0' ; -- x 0.875 when "1001" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "00" & iREG1(11 downto 2) ; iREG2 <= "000" & iREG2(7 downto 3) ; -- x 0.750 when "1010" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "00" & iREG1(11 downto 2) ; -- x 0.625 when "1011" => iREG0 <= '0' & iREG0(11 downto 1) ; iREG1 <= "000" & iREG1(11 downto 3) ; -- x 0.500 when "1100" => iREG0 <= '0' & iREG0(11 downto 1) ; -- x 0.375 when "1101" => iREG0 <= "00" & iREG0(11 downto 2) ; iREG1 <= "000" & iREG1(11 downto 3) ; -- x 0.250 when "1110" => iREG0 <= "00" & iREG0(11 downto 2) ; -- x 0.125 when "1111" => iREG0 <= "000" & iREG0(11 downto 3) ; -- default when others => NULL ; end case ; -- add when S4 => iSTATE <= S5 ; iRESULT <= iREG0 + iREG1 + ("0000" & iREG2) ; -- return first state when S5 => iSTATE <= S0 ; -- default when others => iSTATE <= S0 ; end case ; end if ; end process ; end Behavioral; (under construction)


目次 inserted by FC2 system