Ein Direct Digital Frequency Synthesizer liest aus einer Tabelle Werte aus, und gibt die an einen DA-Wandler. Damit können dann die wildesten Kurvenformen erzeugt werden. Wenn ich aber "nur" einen ordinären Sinus will, reicht es aus, den ersten Quadranten im RAM zu speichern und daraus die anderen drei zu berechnen.
Genau das macht dieser VHDL-Code: in einem BRAM im FPGA wird das erste Viertel einer Sinusschwingung abgelegt, und dann abhängig vom Quadranten vorwärts oder rückwärts ausgelesen und bei Bedarf invertiert. Die Sinustabelle wurde nach den Regeln des Artikels zur Digitalen Sinusfunktion (www.mikrocontroller.net) realisiert.
library IEEE;
use IEEE.std_logic_1164.all;
use IEEE.numeric_std.all;
--------------------------------------------------
Entity DDFS is
--------------------------------------------------
Port ( CLK : in std_logic;
Freq_Data : in std_logic_vector (7 downto 0);
Dout : out std_logic_vector (7 downto 0)
);
end DDFS;
--------------------------------------------------
Architecture RTL of DDFS is
--------------------------------------------------
signal Result : signed (7 downto 0);
signal Accum : unsigned (20 downto 0) := (others=>'0');
signal Address : integer range 0 to 63;
signal RomAddr : integer range 0 to 63;
signal Quadrant : std_logic;
signal Sign : std_logic;
type Rom64x8 is array (0 to 63) of signed (7 downto 0);
-- siehe http://www.mikrocontroller.net/articles/Digitale_Sinusfunktion
constant Sinus_Rom : Rom64x8 := (
x"02", x"05", x"08", x"0b", x"0e", x"11", x"14", x"17",
x"1a", x"1d", x"20", x"23", x"26", x"29", x"2c", x"2f",
x"32", x"36", x"39", x"3c", x"3e", x"40", x"43", x"46",
x"48", x"4b", x"4d", x"50", x"52", x"54", x"57", x"59",
x"5b", x"5d", x"5f", x"62", x"64", x"65", x"67", x"69",
x"6b", x"6d", x"6e", x"70", x"71", x"73", x"74", x"75",
x"76", x"77", x"79", x"79", x"7a", x"7b", x"7c", x"7d",
x"7d", x"7e", x"7e", x"7f", x"7f", x"7f", x"7f", x"7f");
begin
-- Phasenakkumulator
process begin
wait until rising_edge(CLK);
Accum <= Accum + unsigned(Freq_Data);
end process;
-- BROM
process begin
wait until rising_edge(CLK);
RomAddr <= Address; -- getaktete Adresse --> BRAM
end process;
Result <= signed(Sinus_Rom(RomAddr));
Quadrant <= Accum(Accum'left-1);
Address <= to_integer(Accum(Accum'high-2 downto Accum'high-7)) when (Quadrant='0') else
63-to_integer(Accum(Accum'high-2 downto Accum'high-7));
-- 1 Takt Latency wegen BROM
process begin
wait until rising_edge(CLK);
Sign <= Accum(Accum'left);
end process;
Dout <= std_logic_vector( Result) when (Sign='1') else
std_logic_vector(0-Result);
end RTL;
Wird die Fließkommalibrary IEEE.math_real verwendet, dann kann man sich die Sinustabelle auch einfach vom Synthesizer ausrechnen lassen. Hier wird nur ein Viertel des Sinus ausgerechnet, die berechneten Werte entsprechen denen aus der Tabelle oben:
use ieee.math_real.all;
:
type Rom64x8 is array (0 to 63) of signed (7 downto 0);
-- Sinus von 0° bis 90° (0 bis PI/2)
signal Sinus_Rom : Rom64x8;
:
table: for i in 0 to 63 generate
Sinus_Rom(i) <= to_signed(integer( sin(2.0*MATH_PI*(real(i)+0.5)/256.0) *127.5),8);
end generate;