Kampis Elektroecke

Design des I2S-Sender

Im ersten Teil möchte ich zeigen, wie ein einfacher I2S-Transmitter entworfen und genutzt werden kann, um mit Hilfe eines CS4344 Stereo D/A Konverter einen konstanten Ton über einen Lautsprecher auszugeben. Der ausgegebene Ton soll im Block-Memory des FPGAs gespeichert und vom Sender, der die Daten an den D/A Konverter sendet, ausgelesen werden.

Das komplette Projekt teilt sich in drei Abschnitte auf, die ich nach und nach erörtern werde:

  • Ein Top-Design, in dem der Systemtakt erzeugt und das I2S-Modul instanziiert wird
  • Das I2S-Modul, welches den Speicher für den Ton und den I2S-Sender enthält
  • Der I2S-Sender

Der I2S-Sender:

Am untersten Ende des Designs steht der I2S-Sender, der die Aufgabe hat die einzelnen Datenwörter über die I2S-Schnittstelle zu verschicken.

Signal Beschreibung
Clock Taktfrequenz der Audio Samples
nReset Reset Eingang (Active Low)
Ready Ready Signal um eine neue Übertragung zu signalisieren
Tx
Zu übertragende Daten
SCLK Serial Clock der I2S-Schnittstelle
LRCLK Links/Rechts Takt (WS) der I2S-Schnittstelle
SD Serial Data der I2S-Schnittstelle

Aus dem Blockschaltbild des Senders ergibt sich die folgende Entität:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
entity I2S_Transmitter is
Generic ( WIDTH : INTEGER := 16
);
Port ( Clock : in STD_LOGIC;
nReset : in STD_LOGIC;
Ready : out STD_LOGIC;
Tx : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
LRCLK : out STD_LOGIC;
SCLK : out STD_LOGIC;
SD : out STD_LOGIC
);
end I2S_Transmitter;
entity I2S_Transmitter is Generic ( WIDTH : INTEGER := 16 ); Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; Ready : out STD_LOGIC; Tx : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0); LRCLK : out STD_LOGIC; SCLK : out STD_LOGIC; SD : out STD_LOGIC ); end I2S_Transmitter;
entity I2S_Transmitter is
    Generic (   WIDTH   : INTEGER := 16
                );
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;
            Ready   : out STD_LOGIC;
            Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
            LRCLK   : out STD_LOGIC;
            SCLK    : out STD_LOGIC;
            SD      : out STD_LOGIC
            );
end I2S_Transmitter;

Über den Parameter

WIDTH
WIDTH wird die Breite eines Datenwortes pro Kanal definiert.

Der Sender besteht aus einem dreistufigen Zustandsautomaten, der wie folgt beschrieben ist:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
architecture I2S_Transmitter_Arch of I2S_Transmitter is
type State_t is (STATE_LOAD, STATE_TRANSMIT);
signal CurrentState : State_t := STATE_LOAD;
signal Tx_Int : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0) := (others => '0');
signal Ready_Int : STD_LOGIC := '0';
signal LRCLK_Int : STD_LOGIC := '1';
signal SD_Int : STD_LOGIC := '0';
signal Enable : STD_LOGIC := '0';
begin
process
variable BitCounter : INTEGER := 0;
begin
wait until falling_edge(Clock);
case CurrentState is
when STATE_LOAD =>
BitCounter := 0;
Tx_Int <= Tx;
LRCLK_Int <= '0';
CurrentState <= STATE_TRANSMIT;
when STATE_TRANSMIT =>
BitCounter := BitCounter + 1;
if(BitCounter > (WIDTH - 1)) then
LRCLK_Int <= '1';
end if;
if(BitCounter < ((2 * WIDTH) - 1)) then
Ready_Int <= '0';
CurrentState <= STATE_TRANSMIT;
else
Ready_Int <= '1';
CurrentState <= STATE_LOAD;
end if;
Tx_Int <= Tx_Int(((2 * WIDTH) - 2) downto 0) & "0";
SD_Int <= Tx_Int((2 * WIDTH) - 1);
end case;
if(nReset = '0') then
BitCounter := 0;
Ready_Int <= '0';
LRCLK_Int <= '1';
Enable <= '1';
SD_Int <= '0';
Tx_Int <= (others => '0');
CurrentState <= STATE_TRANSMIT;
end if;
end process;
Ready <= Ready_Int;
SCLK <= Clock and Enable;
LRCLK <= LRCLK_Int;
SD <= SD_Int;
end I2S_Transmitter_Arch;
architecture I2S_Transmitter_Arch of I2S_Transmitter is type State_t is (STATE_LOAD, STATE_TRANSMIT); signal CurrentState : State_t := STATE_LOAD; signal Tx_Int : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0) := (others => '0'); signal Ready_Int : STD_LOGIC := '0'; signal LRCLK_Int : STD_LOGIC := '1'; signal SD_Int : STD_LOGIC := '0'; signal Enable : STD_LOGIC := '0'; begin process variable BitCounter : INTEGER := 0; begin wait until falling_edge(Clock); case CurrentState is when STATE_LOAD => BitCounter := 0; Tx_Int <= Tx; LRCLK_Int <= '0'; CurrentState <= STATE_TRANSMIT; when STATE_TRANSMIT => BitCounter := BitCounter + 1; if(BitCounter > (WIDTH - 1)) then LRCLK_Int <= '1'; end if; if(BitCounter < ((2 * WIDTH) - 1)) then Ready_Int <= '0'; CurrentState <= STATE_TRANSMIT; else Ready_Int <= '1'; CurrentState <= STATE_LOAD; end if; Tx_Int <= Tx_Int(((2 * WIDTH) - 2) downto 0) & "0"; SD_Int <= Tx_Int((2 * WIDTH) - 1); end case; if(nReset = '0') then BitCounter := 0; Ready_Int <= '0'; LRCLK_Int <= '1'; Enable <= '1'; SD_Int <= '0'; Tx_Int <= (others => '0'); CurrentState <= STATE_TRANSMIT; end if; end process; Ready <= Ready_Int; SCLK <= Clock and Enable; LRCLK <= LRCLK_Int; SD <= SD_Int; end I2S_Transmitter_Arch;
architecture I2S_Transmitter_Arch of I2S_Transmitter is

    type State_t is (STATE_LOAD, STATE_TRANSMIT);

    signal CurrentState     : State_t                                       := STATE_LOAD;

    signal Tx_Int           : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)  := (others => '0');

    signal Ready_Int        : STD_LOGIC                                     := '0';
    signal LRCLK_Int        : STD_LOGIC                                     := '1';
    signal SD_Int           : STD_LOGIC                                     := '0';
    signal Enable           : STD_LOGIC                                     := '0';

begin

    process
        variable BitCounter : INTEGER := 0;
    begin
        wait until falling_edge(Clock);

        case CurrentState is
            when STATE_LOAD =>
                BitCounter := 0;

                Tx_Int <= Tx;
                LRCLK_Int <= '0';

                CurrentState <= STATE_TRANSMIT;

            when STATE_TRANSMIT =>
                BitCounter := BitCounter + 1;

                if(BitCounter > (WIDTH - 1)) then
                    LRCLK_Int <= '1';
                end if;

                if(BitCounter < ((2 * WIDTH) - 1)) then
                    Ready_Int <= '0';

                    CurrentState <= STATE_TRANSMIT;
                else
                    Ready_Int <= '1';

                    CurrentState <= STATE_LOAD;
                end if;

                Tx_Int <= Tx_Int(((2 * WIDTH) - 2) downto 0) & "0";
                SD_Int <= Tx_Int((2 * WIDTH) - 1);
        end case;
    
        if(nReset = '0') then
            BitCounter := 0;

            Ready_Int <= '0';
            LRCLK_Int <= '1';
            Enable <= '1';
            SD_Int <= '0';
            Tx_Int <= (others => '0');

            CurrentState <= STATE_TRANSMIT;
        end if;
    end process;

    Ready <= Ready_Int;
    SCLK <= Clock and Enable;
    LRCLK <= LRCLK_Int;
    SD <= SD_Int;

end I2S_Transmitter_Arch;

Während eines Reset werden die Ausgangssignale gelöscht und der Takt für SCLK deaktiviert.

Nach einem Reset wechselt der Automat in den Zustand

STATE_LOAD
STATE_LOAD. In diesem Zustand überträgt der Automat den Inhalt des Buffers
Tx_Int
Tx_Int über die I2S-Schnittstelle. Sobald die Übertragung des letzten Datenbits gestartet wird, wird
Ready
Ready gesetzt um das Ende einer Übertragung und die Bereitschaft neue Daten annehmen zu können zu signalisieren. Anschließend wechselt der Automat in den Zustand
STATE_LOAD
STATE_LOAD, wo der Sendebuffer mit einem neuen Datenwort gefüllt und eine neue Übertragung gestartet wird.

Das I2S-Modul:

Der fertige Sender wird vom übergeordneten I2S-Modul genutzt um Daten aus einem ROM an den D/A-Wandler zu übertragen.

Signal Beschreibung
MCLK Haupttakt für das Audio Interface
nReset Reset Eingang (Active Low)
SCLK Serial Clock der I2S-Schnittstelle
LRCLK LRCLK (WS) der I2S-Schnittstelle
SD Serial Data der I2S-Schnittstelle

Aus dieser Beschreibung ergibt sich die nachfolgende Entität:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
entity I2S is
Generic ( RATIO : INTEGER := 8;
WIDTH : INTEGER := 16
);
Port ( MCLK : in STD_LOGIC;
nReset : in STD_LOGIC;
LRCLK : out STD_LOGIC;
SCLK : out STD_LOGIC;
SD : out STD_LOGIC
);
end I2S;
entity I2S is Generic ( RATIO : INTEGER := 8; WIDTH : INTEGER := 16 ); Port ( MCLK : in STD_LOGIC; nReset : in STD_LOGIC; LRCLK : out STD_LOGIC; SCLK : out STD_LOGIC; SD : out STD_LOGIC ); end I2S;
entity I2S is
    Generic (   RATIO   : INTEGER := 8;
                WIDTH   : INTEGER := 16
                );
    Port (  MCLK     : in STD_LOGIC;
            nReset   : in STD_LOGIC;
            LRCLK    : out STD_LOGIC;
            SCLK     : out STD_LOGIC;
            SD       : out STD_LOGIC
            );
end I2S;

Die Parameter

RATIO
RATIO und
WIDTH
WIDTH definieren das Verhältnis von SCLK zu MCLK und die Breite eines Datenwortes pro Kanal.

Das Modul verwendet, neben dem I2S-Sender, ein ROM, welches über den Block-Memory Generator erstellt und mit Daten gefüllt werden kann. Beides kann über den IP-Integrator von Vivado gemacht werden.

Zum Abschluss wird das ROM über Other Options über eine coe-Datei mit einem Sinussignal initialisiert.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
memory_initialization_radix=16;
memory_initialization_vector=
0000,
0809,
100A,
17FB,
1FD4,
278D,
2F1E,
367F,
3DA9,
4495,
4B3B,
5196,
579E,
5D4E,
629F,
678D,
6C12,
7029,
73D0,
7701,
79BB,
7BF9,
7DBA,
7EFC,
7FBE,
7FFF,
7FBE,
7EFC,
7DBA,
7BF9,
79BB,
7701,
73D0,
7029,
6C12,
678D,
629F,
5D4E,
579E,
5196,
4B3B,
4495,
3DA9,
367F,
2F1E,
278D,
1FD4,
17FB,
100A,
0809,
0000,
F7F7,
EFF6,
E805,
E02C,
D873,
D0E2,
C981,
C257,
BB6B,
B4C5,
AE6A,
A862,
A2B2,
9D61,
9873,
93EE,
8FD7,
8C30,
88FF,
8645,
8407,
8246,
8104,
8042,
8001,
8042,
8104,
8246,
8407,
8645,
88FF,
8C30,
8FD7,
93EE,
9873,
9D61,
A2B2,
A862,
AE6A,
B4C5,
BB6B,
C257,
C981,
D0E2,
D873,
E02C,
E805,
EFF6,
F7F7,
memory_initialization_radix=16; memory_initialization_vector= 0000, 0809, 100A, 17FB, 1FD4, 278D, 2F1E, 367F, 3DA9, 4495, 4B3B, 5196, 579E, 5D4E, 629F, 678D, 6C12, 7029, 73D0, 7701, 79BB, 7BF9, 7DBA, 7EFC, 7FBE, 7FFF, 7FBE, 7EFC, 7DBA, 7BF9, 79BB, 7701, 73D0, 7029, 6C12, 678D, 629F, 5D4E, 579E, 5196, 4B3B, 4495, 3DA9, 367F, 2F1E, 278D, 1FD4, 17FB, 100A, 0809, 0000, F7F7, EFF6, E805, E02C, D873, D0E2, C981, C257, BB6B, B4C5, AE6A, A862, A2B2, 9D61, 9873, 93EE, 8FD7, 8C30, 88FF, 8645, 8407, 8246, 8104, 8042, 8001, 8042, 8104, 8246, 8407, 8645, 88FF, 8C30, 8FD7, 93EE, 9873, 9D61, A2B2, A862, AE6A, B4C5, BB6B, C257, C981, D0E2, D873, E02C, E805, EFF6, F7F7,
memory_initialization_radix=16;
memory_initialization_vector=
0000,
0809,
100A,
17FB,
1FD4,
278D,
2F1E,
367F,
3DA9,
4495,
4B3B,
5196,
579E,
5D4E,
629F,
678D,
6C12,
7029,
73D0,
7701,
79BB,
7BF9,
7DBA,
7EFC,
7FBE,
7FFF,
7FBE,
7EFC,
7DBA,
7BF9,
79BB,
7701,
73D0,
7029,
6C12,
678D,
629F,
5D4E,
579E,
5196,
4B3B,
4495,
3DA9,
367F,
2F1E,
278D,
1FD4,
17FB,
100A,
0809,
0000,
F7F7,
EFF6,
E805,
E02C,
D873,
D0E2,
C981,
C257,
BB6B,
B4C5,
AE6A,
A862,
A2B2,
9D61,
9873,
93EE,
8FD7,
8C30,
88FF,
8645,
8407,
8246,
8104,
8042,
8001,
8042,
8104,
8246,
8407,
8645,
88FF,
8C30,
8FD7,
93EE,
9873,
9D61,
A2B2,
A862,
AE6A,
B4C5,
BB6B,
C257,
C981,
D0E2,
D873,
E02C,
E805,
EFF6,
F7F7,

Das I2S-Modul verwendet einen Zustandsautomaten um Daten aus dem ROM auszulesen und an den I2S-Sender zu übergeben.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
architecture I2S_Arch of I2S is
type State_t is (STATE_WAIT_READY, STATE_INC_ADDRESS, STATE_WAIT_START);
signal CurrentState : State_t := STATE_WAIT_READY;
signal Tx : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0) := (others => '0');
signal ROM_Data : STD_LOGIC_VECTOR((WIDTH - 1) downto 0) := (others => '0');
signal ROM_Address : STD_LOGIC_VECTOR(6 downto 0) := (others => '0');
signal Ready : STD_LOGIC;
signal SCLK_Int : STD_LOGIC := '0';
component I2S_Transmitter is
Generic ( WIDTH : INTEGER := 16
);
Port ( Clock : in STD_LOGIC;
nReset : in STD_LOGIC;
Ready : out STD_LOGIC;
Tx : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
LRCLK : out STD_LOGIC;
SCLK : out STD_LOGIC;
SD : out STD_LOGIC
);
end component;
component SineROM is
Port ( Address : in STD_LOGIC_VECTOR(6 downto 0);
Clock : in STD_LOGIC;
DataOut : out STD_LOGIC_VECTOR(15 downto 0)
);
end component SineROM;
begin
Transmitter : I2S_Transmitter generic map( WIDTH => WIDTH
)
port map( Clock => SCLK_Int,
nReset => nReset,
Ready => Ready,
Tx => Tx,
LRCLK => LRCLK,
SCLK => SCLK,
SD => SD
);
ROM : SineROM port map (Clock => MCLK,
Address => ROM_Address,
DataOut => ROM_Data
);
process
variable Counter : INTEGER := 0;
begin
wait until rising_edge(MCLK);
if(Counter < ((RATIO / 2) - 1)) then
Counter := Counter + 1;
else
Counter := 0;
SCLK_Int <= not SCLK_Int;
end if;
if(nReset = '0') then
Counter := 0;
SCLK_Int <= '0';
end if;
end process;
process
variable WordCounter : INTEGER := 0;
begin
wait until rising_edge(MCLK);
case CurrentState is
when STATE_WAIT_READY =>
if(Ready = '1') then
CurrentState <= STATE_WAIT_START;
else
CurrentState <= STATE_WAIT_READY;
end if;
when STATE_WAIT_START =>
ROM_Address <= STD_LOGIC_VECTOR(to_unsigned(WordCounter, ROM_Address'length));
Tx <= x"0000" & ROM_Data;
if(Ready = '0') then
CurrentState <= STATE_INC_ADDRESS;
else
CurrentState <= STATE_WAIT_START;
end if;
when STATE_INC_ADDRESS =>
if(WordCounter < 99) then
WordCounter := WordCounter + 1;
else
WordCounter := 0;
end if;
CurrentState <= STATE_WAIT_READY;
end case;
if(nReset = '0') then
WordCounter := 0;
CurrentState <= STATE_WAIT_START;
end if;
end process;
end I2S_Arch;
architecture I2S_Arch of I2S is type State_t is (STATE_WAIT_READY, STATE_INC_ADDRESS, STATE_WAIT_START); signal CurrentState : State_t := STATE_WAIT_READY; signal Tx : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0) := (others => '0'); signal ROM_Data : STD_LOGIC_VECTOR((WIDTH - 1) downto 0) := (others => '0'); signal ROM_Address : STD_LOGIC_VECTOR(6 downto 0) := (others => '0'); signal Ready : STD_LOGIC; signal SCLK_Int : STD_LOGIC := '0'; component I2S_Transmitter is Generic ( WIDTH : INTEGER := 16 ); Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; Ready : out STD_LOGIC; Tx : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0); LRCLK : out STD_LOGIC; SCLK : out STD_LOGIC; SD : out STD_LOGIC ); end component; component SineROM is Port ( Address : in STD_LOGIC_VECTOR(6 downto 0); Clock : in STD_LOGIC; DataOut : out STD_LOGIC_VECTOR(15 downto 0) ); end component SineROM; begin Transmitter : I2S_Transmitter generic map( WIDTH => WIDTH ) port map( Clock => SCLK_Int, nReset => nReset, Ready => Ready, Tx => Tx, LRCLK => LRCLK, SCLK => SCLK, SD => SD ); ROM : SineROM port map (Clock => MCLK, Address => ROM_Address, DataOut => ROM_Data ); process variable Counter : INTEGER := 0; begin wait until rising_edge(MCLK); if(Counter < ((RATIO / 2) - 1)) then Counter := Counter + 1; else Counter := 0; SCLK_Int <= not SCLK_Int; end if; if(nReset = '0') then Counter := 0; SCLK_Int <= '0'; end if; end process; process variable WordCounter : INTEGER := 0; begin wait until rising_edge(MCLK); case CurrentState is when STATE_WAIT_READY => if(Ready = '1') then CurrentState <= STATE_WAIT_START; else CurrentState <= STATE_WAIT_READY; end if; when STATE_WAIT_START => ROM_Address <= STD_LOGIC_VECTOR(to_unsigned(WordCounter, ROM_Address'length)); Tx <= x"0000" & ROM_Data; if(Ready = '0') then CurrentState <= STATE_INC_ADDRESS; else CurrentState <= STATE_WAIT_START; end if; when STATE_INC_ADDRESS => if(WordCounter < 99) then WordCounter := WordCounter + 1; else WordCounter := 0; end if; CurrentState <= STATE_WAIT_READY; end case; if(nReset = '0') then WordCounter := 0; CurrentState <= STATE_WAIT_START; end if; end process; end I2S_Arch;
architecture I2S_Arch of I2S is

    type State_t is (STATE_WAIT_READY, STATE_INC_ADDRESS, STATE_WAIT_START);

    signal CurrentState : State_t                                           := STATE_WAIT_READY;

    signal Tx           : STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0)      := (others => '0');

    signal ROM_Data     : STD_LOGIC_VECTOR((WIDTH - 1) downto 0)            := (others => '0');
    signal ROM_Address  : STD_LOGIC_VECTOR(6 downto 0)                      := (others => '0');

    signal Ready        : STD_LOGIC;
    signal SCLK_Int     : STD_LOGIC                                         := '0';

    component I2S_Transmitter is
        Generic (   WIDTH   : INTEGER := 16
                    );
        Port (  Clock   : in STD_LOGIC;
                nReset  : in STD_LOGIC;
                Ready   : out STD_LOGIC;
                Tx      : in STD_LOGIC_VECTOR(((2 * WIDTH) - 1) downto 0);
                LRCLK   : out STD_LOGIC;
                SCLK    : out STD_LOGIC;
                SD      : out STD_LOGIC
                );
    end component;

    component SineROM is
        Port (  Address : in STD_LOGIC_VECTOR(6 downto 0);
                Clock   : in STD_LOGIC;
                DataOut : out STD_LOGIC_VECTOR(15 downto 0)
                );
    end component SineROM;

begin

    Transmitter : I2S_Transmitter generic map(  WIDTH => WIDTH
                                                )
                                  port map(     Clock => SCLK_Int,
                                                nReset => nReset,
                                                Ready => Ready,
                                                Tx => Tx,
                                                LRCLK => LRCLK,
                                                SCLK => SCLK,
                                                SD => SD
                                                );

    ROM : SineROM port map (Clock => MCLK,
                            Address => ROM_Address,
                            DataOut => ROM_Data
                            );

    process
        variable Counter    : INTEGER := 0;
    begin
        wait until rising_edge(MCLK);
        if(Counter < ((RATIO / 2) - 1)) then
            Counter := Counter + 1;
        else
            Counter := 0;

            SCLK_Int <= not SCLK_Int;
        end if;

        if(nReset = '0') then
            Counter := 0;

            SCLK_Int <= '0';
        end if;
    end process;

    process
        variable WordCounter    : INTEGER := 0;
    begin
        wait until rising_edge(MCLK);
        case CurrentState is
            when STATE_WAIT_READY =>
                if(Ready = '1') then
                    CurrentState <= STATE_WAIT_START;
                else
                    CurrentState <= STATE_WAIT_READY;
                end if;

            when STATE_WAIT_START =>
                ROM_Address <= STD_LOGIC_VECTOR(to_unsigned(WordCounter, ROM_Address'length));
                Tx <= x"0000" & ROM_Data;

                if(Ready = '0') then
                    CurrentState <= STATE_INC_ADDRESS;
                else
                    CurrentState <= STATE_WAIT_START;
                end if;

            when STATE_INC_ADDRESS =>
                if(WordCounter < 99) then
                    WordCounter := WordCounter + 1;
                else
                    WordCounter := 0;
                end if;

                CurrentState <= STATE_WAIT_READY;

        end case;

        if(nReset = '0') then
            WordCounter := 0;

            CurrentState <= STATE_WAIT_START;
        end if;
    end process;
end I2S_Arch;

Der erste Prozess wird genutzt um aus MCLK das für den Sender benötigte Taktsignal SCLK zu erzeugen.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
process
variable Counter : INTEGER := 0;
begin
wait until rising_edge(MCLK);
if(Counter < ((RATIO / 2) - 1)) then
Counter := Counter + 1;
else
Counter := 0;
Clock_Audio <= not Clock_Audio;
end if;
if(nReset = '0') then
Counter := 0;
Clock_Audio <= '0';
end if;
end process;
process variable Counter : INTEGER := 0; begin wait until rising_edge(MCLK); if(Counter < ((RATIO / 2) - 1)) then Counter := Counter + 1; else Counter := 0; Clock_Audio <= not Clock_Audio; end if; if(nReset = '0') then Counter := 0; Clock_Audio <= '0'; end if; end process;
process
    variable Counter    : INTEGER := 0;
begin
    wait until rising_edge(MCLK);
    if(Counter < ((RATIO / 2) - 1)) then
        Counter := Counter + 1;
    else
        Counter := 0;
        Clock_Audio <= not Clock_Audio;
    end if;

    if(nReset = '0') then
        Counter := 0;
        Clock_Audio <= '0';
    end if;
end process;

Der zweite Prozess kümmert sich um die Abarbeitung des Zustandsautomaten. Nach dem Verlassen des Reset-Zustandes wartet der Automat so lange in dem Zustand

STATE_WAIT_READY
STATE_WAIT_READY bis der Sender durch das
Ready
Ready-Signal Bereitschaft signalisiert.

Sobald der Sender bereit ist, wechselt der Automat in den Zustand

STATE_WAIT_START
STATE_WAIT_START. In diesem Zustand wird das aktuelle Datenwort aus dem ROM ausgelesen und an den Sender übergeben.


Hinweis:

Das hier gezeigte ROM beinhaltet nur die Informationen aus einem Kanal. Für den zweiten Kanal müssen die Daten entsprechend erweitert werden.


Sobald der Sender das

Ready
Ready-Signal löscht und mit der Übertragung der Daten beginnt, wechselt der Zustandsautomat in den Zustand
STATE_INC_ADDRESS
STATE_INC_ADDRESS. In diesem Zustand wird die ROM-Adresse um eins erhöht und dann wieder zurück in den Zustand
STATE_WAIT_READY
STATE_WAIT_READY gewechselt.

Das Top-Design:

Die letzte Komponente ist das Top-Design, dass das I2S-Modul und einen Clocking Wizard beinhaltet. Dieses Beispiel verwendet die folgenden Parameter für die Ansteuerung des CS4344:

Parameter Wert
MCLK 12,288 MHz
SCLK 1,536 MHz
LRCLK 48 kHz
RATIO 8
WIDTH 16

Der Clocking Wizard wird genutzt um den 12,288 MHz Takt aus der Oszillatorfrequenz der programmierbaren Logik zu erzeugen. Dazu wird der Clocking Wizard über den IP-Integrator eingefügt und zusammen mit dem fertigen I2S-Modul im VHDL-Code instantiiert.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
entity Top is
Generic ( RATIO : INTEGER := 8;
WIDTH : INTEGER := 16
);
Port ( Clock : in STD_LOGIC;
nReset : in STD_LOGIC;
MCLK : out STD_LOGIC;
LRCLK : out STD_LOGIC;
SCLK : out STD_LOGIC;
SD : out STD_LOGIC;
LED : out STD_LOGIC_VECTOR(3 downto 0)
);
end Top;
architecture Top_Arch of Top is
signal nSystemReset : STD_LOGIC := '0';
signal MCLK_DCM : STD_LOGIC := '0';
signal Locked : STD_LOGIC := '0';
component I2S is
Generic ( RATIO : INTEGER := 8;
WIDTH : INTEGER := 16
);
Port ( MCLK : in STD_LOGIC;
nReset : in STD_LOGIC;
LRCLK : out STD_LOGIC;
SCLK : out STD_LOGIC;
SD : out STD_LOGIC
);
end component;
component AudioClock is
Port ( ClockIn : in STD_LOGIC;
Locked : out STD_LOGIC;
MCLK : out STD_LOGIC;
nReset : in STD_LOGIC
);
end component;
begin
InputClock : AudioClock port map ( ClockIn => Clock,
nReset => nReset,
MCLK => MCLK_DCM,
Locked => Locked
);
I2S_Module : I2S generic map ( RATIO => RATIO,
WIDTH => WIDTH
)
port map ( MCLK => MCLK_DCM,
nReset => nSystemReset,
LRCLK => LRCLK,
SCLK => SCLK,
SD => SD
);
nSystemReset <= nReset and Locked;
LED(0) <= nReset;
LED(1) <= Locked;
LED(2) <= nSystemReset;
MCLK <= MCLK_DCM;
end Top_Arch;
entity Top is Generic ( RATIO : INTEGER := 8; WIDTH : INTEGER := 16 ); Port ( Clock : in STD_LOGIC; nReset : in STD_LOGIC; MCLK : out STD_LOGIC; LRCLK : out STD_LOGIC; SCLK : out STD_LOGIC; SD : out STD_LOGIC; LED : out STD_LOGIC_VECTOR(3 downto 0) ); end Top; architecture Top_Arch of Top is signal nSystemReset : STD_LOGIC := '0'; signal MCLK_DCM : STD_LOGIC := '0'; signal Locked : STD_LOGIC := '0'; component I2S is Generic ( RATIO : INTEGER := 8; WIDTH : INTEGER := 16 ); Port ( MCLK : in STD_LOGIC; nReset : in STD_LOGIC; LRCLK : out STD_LOGIC; SCLK : out STD_LOGIC; SD : out STD_LOGIC ); end component; component AudioClock is Port ( ClockIn : in STD_LOGIC; Locked : out STD_LOGIC; MCLK : out STD_LOGIC; nReset : in STD_LOGIC ); end component; begin InputClock : AudioClock port map ( ClockIn => Clock, nReset => nReset, MCLK => MCLK_DCM, Locked => Locked ); I2S_Module : I2S generic map ( RATIO => RATIO, WIDTH => WIDTH ) port map ( MCLK => MCLK_DCM, nReset => nSystemReset, LRCLK => LRCLK, SCLK => SCLK, SD => SD ); nSystemReset <= nReset and Locked; LED(0) <= nReset; LED(1) <= Locked; LED(2) <= nSystemReset; MCLK <= MCLK_DCM; end Top_Arch;
entity Top is
    Generic (   RATIO   : INTEGER := 8;
                WIDTH   : INTEGER := 16
                );
    Port (  Clock   : in STD_LOGIC;
            nReset  : in STD_LOGIC;
            MCLK    : out STD_LOGIC;
            LRCLK   : out STD_LOGIC;
            SCLK    : out STD_LOGIC;
            SD      : out STD_LOGIC;
            LED     : out STD_LOGIC_VECTOR(3 downto 0)
            );
end Top;

architecture Top_Arch of Top is

    signal nSystemReset : STD_LOGIC := '0';
    signal MCLK_DCM     : STD_LOGIC := '0';
    signal Locked       : STD_LOGIC := '0';

    component I2S is    
        Generic (   RATIO   : INTEGER := 8;
                    WIDTH   : INTEGER := 16
                    );
        Port (  MCLK    : in STD_LOGIC;
                nReset   : in STD_LOGIC;
                LRCLK    : out STD_LOGIC;
                SCLK     : out STD_LOGIC;
                SD       : out STD_LOGIC
                );
    end component;

    component AudioClock is
        Port (  ClockIn     : in STD_LOGIC;
                Locked      : out STD_LOGIC;
                MCLK        : out STD_LOGIC;
                nReset      : in STD_LOGIC
                );
    end component;

begin

    InputClock : AudioClock port map (  ClockIn => Clock,
                                        nReset => nReset,
                                        MCLK => MCLK_DCM,
                                        Locked => Locked
                                        );

    I2S_Module : I2S generic map (  RATIO => RATIO,
                                    WIDTH => WIDTH
                                    )
                          port map ( MCLK => MCLK_DCM,
                                     nReset => nSystemReset,
                                     LRCLK => LRCLK,
                                     SCLK => SCLK,
                                     SD => SD
                                     );

    nSystemReset <= nReset and Locked;
    LED(0) <= nReset;
    LED(1) <= Locked;
    LED(2) <= nSystemReset;
    MCLK <= MCLK_DCM;

end Top_Arch;

Das Design kann nun implementiert, auf das FPGA übertragen und getestet werden. Im Idealfall gibt der D/A-Wandler ein 480 Hz Sinussignal aus, da das Signalmuster aus dem ROM eine Länge von 100 Abtastwerten aufweist und die Abtastfrequenz 48 kHz beträgt. Mit einem Oszilloskop kann die Kommunikation und das Signal kontrolliert werden:

Und wenn man schon dabei ist, kann auch gleich das Audiosignal gecheckt werden. Die FFT-Funktion eines Oszilloskops eignet sich sehr gut dafür.

Wie man erkennt, wird ein (fast) perfekter Sinus mit einer Frequenz von 480 Hz ausgegeben. Die Oberwellen erscheinen bei den Vielfachen der Abtastfrequenz und stammen aus der D/A-Wandlung des Konverters.

Im nächsten Teil wird der I2S-Sender zusätzlich noch mit einem AXI-Stream Interface ausgestattet, um ihn mit dem Processing System des ZYNQ verbinden zu können.

Zurück

2 Kommentare

    1. Hallo Rolf,

      nein, da die Samples 16 Bit lang sind. Ich muss somit 32 Bit innerhalb einer Periode von LRCLK (48 kHz) senden, woraus sich ein SCLK von 1,536 MHz ergibt.

      Gruß
      Daniel

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert