HTML

Műszer

Hobby és amatőr elektronika, műszerépítés a XXI. században

Címkék

Friss topikok

  • Nite: @fromi: Ha egy bejegyzéssel kapcsolatos kérdésed van, akkor jobb odaírni kommentbe, mert esetleg m... (2010.11.26. 15:17) FAQ

Licenc

Creative Commons Licenc

VHDL gyorstalpaló 2: DDS

Nite 2010.08.02. 12:17

 Még mindig a DDS-ről írok, mivel szabadidőmben mostanában ez foglalkoztat. Ha valaki végigböngészi a Xilinx IP-ket, gyorsan talál egy DDS-t közöttük, mégis inkább úgy voltam vele, hogy megírom a sajátomat. Ennek oka leginkább a Xilinx DDS hiányosságaiban keresendő: például szerettem volna tetszőleges hullámformát generálni, illetve a dither-t ki-be kapcsolni, ahogy azt a generált jelalak megkívánja (ez még a kódban nem szerepel). Eredetileg kicsit több melóra számítottam vele, de mint kiderült, annyira egyszerű, hogy az egész kódot beírhatom ide. Minden részét nem fogom mégsem bemásolni, a generált fájlokat amúgy is jobb, ha mindenki legenerálja magának.

 A DDS projekt a következő elemekből épül fel:

 - Clock: A Xilinx Clock IP-je építette fel az órajel előállításához szükséges paramétereket. Nekem a panelomon 25MHz-es órajel van, a DA átalakítóm 50MHz-et bír, így egy kétszeres szorzót tettem bele mindössze.
 - Ram: Ez is egy IP, mégpedig egy 2048 byte-os memória, amiben a jelalakot tároljuk.
 - Random: A dither előállításához szükséges pszeudo-random generátor. Sajnos a Xilinx IP-i közül kivették, ezért meg kellett írni.
 - DDSMain: A DDS implementációja.
 - testbench: A teszteléshez szükséges jeleket előállító kód.

 A forráskódokat innen lehet letölteni.

 Nézzük akkor meg a kódokat, először is a véletlen szám generátort:
 
entity random is
port (
      clk : in std_logic;
      random_num : out std_logic_vector (31 downto 0)   --output vector           
    );
end random;
 Nincs túlbonyolítva, az órajel minden ciklusára egy 32 bájtos véletlen számot állít elő.

architecture Behavioral of random is
begin

    process(clk)
        variable rand_temp : std_logic_vector(31 downto 0) := "10111010111110001011101011111000";
        variable temp : std_logic := '0';
    begin
   
        if(rising_edge(clk)) then
            temp := rand_temp(31) xor rand_temp(21);
            temp := temp xor rand_temp(1);
            temp := temp xor rand_temp(0);
            rand_temp(31 downto 1) := rand_temp(30 downto 0);
            rand_temp(0) := temp;
        end if;
        random_num <= rand_temp;
   
    end process;
   
end Behavioral;
 Mint az látszik, ez egy egyszerű LFSR, a régi Xilinx IP dokumentációja itt található. Ebben van egy táblázat, amiből kideríthető, hogy 32 bites hossznál melyik biteket célszerű XOR-olni, hogy a vélelen szám generálás a legkésőbb kezdjen ismétlődni.

 A DDS kódja:

entity ddsmain is
    Port ( ClkIn : in  STD_LOGIC;
           Rst : in  STD_LOGIC;
           ClkOut : out  STD_LOGIC;
           DataOut : out  STD_LOGIC_VECTOR (7 downto 0);
           DataIn : in  STD_LOGIC_VECTOR (7 downto 0);
           Addr : in  STD_LOGIC_VECTOR (3 downto 0);
           ManClk : in  STD_LOGIC;
              WriteClk : in  STD_LOGIC;
           ClkSel : in  STD_LOGIC);
end ddsmain; 
 Fussunk végig a bemeneteken és kimeneteken:

- ClkIn: A bemenő órajel, mint említettem 25 MHz
- ManClk: Bemenő órajel, ha nem a 25MHz-es jelet akarjuk használni
- ClkSel: Ezzel választhatunk a ClkIn vagy a ManClk közül
- Rst: Reset
- ClkOut: Kimenő órajel a DA átalakítóhoz
- DataOut: Kimenő adat a DA átalakítóhoz
- DataIn: Bemenő adat a DDS programozásához
- Addr: A programozandó regiszter címe
- WriteClk: A programozáshoz szükséges órajel

 A DDS paramétereinek beállítása ahhoz hasonlóan történik, mintha regiszterekbe írnánk az értékeket. Az Addr bemeneten kiválasztjuk a regisztert, a DataIn-be beírjuk az adatot, és a WriteClk ciklusával az adat beíródik.

process (WriteClk, Addr, DataIn)
    begin
   
        if rising_edge(WriteClk) then
            case Addr is
                when "0000" =>    freqbuf(7 downto 0) <= DataIn;
                when "0001" =>    freqbuf(15 downto 8) <= DataIn;
                when "0010" =>    freqbuf(23 downto 16) <= DataIn;
                when "0011" =>    freqbuf(29 downto 24) <= DataIn(5 downto 0); freqreg <= freqbuf;
                when "0100" =>    phasebuf(7 downto 0) <= DataIn;
                when "0101" =>    phasebuf(9 downto 8) <= DataIn(1 downto 0); phasereg <= phasebuf;
                when "0110" =>    ramaddrbuf(7 downto 0) <= DataIn;
                when "0111" =>    ramaddrbuf(10 downto 8) <= DataIn(2 downto 0); ramaddrreg <= ramaddrbuf;
                when "1000" =>    ramdatain <= DataIn;
                when "1001" =>    ramrst <= DataIn(0);
                when "1010" =>    ramwrite(0) <= DataIn(0);
                when others => NULL;
            end case;
        end if;
    end process;
  A következő címek vannak definiálva:

 - 0000 - 0011: A generálandó frekvencia, 4 bájton ábrázolva. Mivel a DDS fázisakkumulátora 32 bájtos, a frekvencia legfelső két bitjét nem használjuk (nem kapnánk értelmes jelalakot).
 - 0100 - 0101: 10 bites fázis regiszter
 - 0110 - 0111: 11 bites cím regiszter a RAM írásához
 - 1000: A RAM-ba írandó adat
 - 1001: A RAM törlése
 - 1010: csak a legalsó bitje használt, ha 1, akkor a RAM-ot írjuk, ha 0, akkor olvassuk.

 Kicsit előreszaladtunk, ha a kód elejét nézzük, az első lépés a külső modulok példányosítása:

        Inst_Clock: Clock PORT MAP(
        CLKIN_IN => ClkIn,
        CLKIN_IBUFG_OUT => bufo,
        CLK0_OUT => clk0,
        CLK2X_OUT => clk2
    );

    Inst_Ram: ram port map (
            clka => intclk,
            rsta => ramrst,
            wea => ramwrite,
            addra => ramaddr,
            dina => ramdatain,
            douta => DataOut
    );

    Inst_Rnd: random PORT MAP (
          clk => clk2,
          random_num => random_num
        );

 Ezzel gyakorlatilag bekötjük belső jelekre a moduljaink lábait.
 A következő részben az órajel előállításához szükséges műveletek jönnek:

     IBUFG_inst_ManClk : IBUFG
    generic map ( IBUF_DELAY_VALUE => "0", -- Specify the amount of added input delay for buffer, "0"-"16"
                        IOSTANDARD => "DEFAULT")
    port map (
        O => manclkbuf, -- Clock buffer output
        I => ManClk -- Clock buffer input (connect directly to top-level port)
    );         
   
    BUFGMUX_inst : BUFGMUX
    port map (
        O => intclk, -- Clock MUX output
        I0 => clk2, -- Clock0 input
        I1 => manclkbuf, -- Clock1 input
        S => ClkSel -- Clock select input
    );   
   
    ClkOut <= intclk;
 Létrehozunk egy Clock Buffert a ManClk számára is, mert az IP Wizard csak a ClkIn-nek csinálta meg. A Spartan3 kézikőnyvében van leírva, hogy mit miért és hogy kell csinálni.

 Egy BUFGMUX példány dönti el, hogy a ManClk vagy a ClkIn(x2) bemenetet használjuk órajelnek.

 Ezután már csak az a rész van hátra, ahol a DDS jelgenerálás végbemegy:

      process (intclk, Rst, ramwrite, freqreg, phasereg, random_num, phasedither, phasecrop, ramaddrreg)
    begin

        if rising_edge(intclk) then
            if (Rst = '1') then           
                phaseacc <= "00000000000000000000000000000000";
                phaseout <= "00000000000000000000000000000000";
            else
                if (ramwrite(0) = '0') then
                    phaseacc <= phaseacc + freqreg;
                    phaseout <= phaseacc + (phasereg * "1000000000000000000000");
                    phasedither <= phaseout + random_num(20 downto 0);
                    phasecrop <= phasedither(31 downto 21);
                    ramaddr <= phasecrop;
                else
                    ramaddr <= ramaddrreg;
                end if;
            end if;
        end if;
   
    end process;
  Az első rész a resetet végzi el, utána jön tulajdonképpen a jelgenerálás. Ez nem tér el a DDS-nél már leírt algoritmustól. A fázis akkumulátorhoz hozzáadjuk a frekvencia regisztert minden órajelciklusban, ehhez jön a fázis regiszter, a véletlen szám a ditherhez, végül az egészet akkorára vágjuk, hogy a RAM-ot meg lehessen vele címezni.

 A RAM kimenete van a DataOut-ra kötve, így a megcímzett memória tartalma kerül a kimenetre.

 Gyakorlatilag a DDS ennyiből áll. Érdemes még egy picit a teszteléshez használt kódon is átfutni, ugyanis ebben megnézhetjük, hogyan kell feltölteni a RAM-ot egy szinusz hullámmal:

    signal s: real;
    signal s2: real;
    signal si: real;
    signal intaddr: std_logic_vector(10 downto 0);
   
    ...
   
         -- Fill up RAM with sine
        DataIn <= "11111111";
        Addr <= "1010";
        wait for WriteClk_period;

        si <= 0.0;
        for i in 0 to 2048 loop
            intaddr <= conv_std_logic_vector(i,11);
            DataIn <= intaddr(7 downto 0);
            Addr <= "0110";
            wait for WriteClk_period;
            DataIn(2 downto 0) <= intaddr(10 downto 8);
            DataIn(7 downto 3) <= "00000";
            Addr <= "0111";
            wait for WriteClk_period;
           
            s <= sin((Math_pi / 1024.0) * si);
            s2 <= s * 127.5;
            si <= si + 1.0;
           
            DataIn <= conv_std_logic_vector(INTEGER(s2)+127, 8);
            Addr <= "1000";
           
            wait for WriteClk_period;
        end loop;

        DataIn <= "00000000";
        Addr <= "1010";
        wait for WriteClk_period;
        -- Fill End
  A szinusz hullám feltöltésének menete a következő:

 - Írásra állítjuk a RAM-ot
 - Egy ciklussal végigmegyünk a címeken, a "conv_std_logic_vector" segítségével alakíthatjuk a ciklusváltozónkat címmé
 - A címet beírjuk a DDS regiszterébe
 - A szinuszt a VHDL beépített függvényeivel kiszámoljuk. Ez a rész csak a teszt közben tud futni, az FPGA-ba nem programozható :)
 - Az "INTEGER" függvény alakítja egész számmá a "real"-t amiben a szinusz tárolva van, ezt szokás szerint a "conv_std_logic_vector" alakítja nyolc bites számmá, ami a RAM-ba írandó adat.
 - Az adatot beírjuk a RAM-ba
 - Ha az összes címre beírtuk az adatot, az írás módot kikapcsoljuk.

 Gyakorlatilag ennyi az egész. Ha a tesztet lefuttatjuk, nyomon követhetjük, ahogy a szinusz hullámot a DDS létrehozza. Ha a frekvenciát elég alacsonyra vesszük, azt is megnézhetjük, hogyan működik a dither.
 Jó szórakozást!        
 
 
 

Címkék: vhdl

Szólj hozzá!

A bejegyzés trackback címe:

https://muszer.blog.hu/api/trackback/id/tr132193004

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása