Implementación sobre una FPGA de un transmisor de radio en la banda comercial de FM 
En anteriores entradas se realizó la implementación, tanto de un transmisor FM como de un conversor analógico digital delta-sigma, sobre FPGA y como proyectos separados. A lo largo de esta entrada se aborda el desarrollo de un transmisor FM en la banda de la radio comercial para transmitir música, combinando ambos proyectos en uno solo.

Conversión analógica digital delta-sigma

Como se puede comprobar en el post anterior relacionado, la conversión analógica digital de tipo delta-sigma tiene la ventaja de requerir muy pocos componentes externos: un comparador analógico (para lo que puede usarse un amplificador operacional normal), un condensador y una resistencia; y permite implementar un ADC de precisión arbitraria (la precisión sólo está limitada por los recursos disponibles en la FPGA).



Recordemos que la conversión analógico digital de tipo delta-sigma lo que hace es, mediante un biestable de tipo D, hacer que la salida que carga el condensador C a través de la resistencia R "siga" a la entrada analógica:


- Si la entrada + del comparador está por encima de la entrada -, el comparador emite un 1, que es cargado en el siguiente ciclo de reloj por el biestable de entrada, haciendo que la salida que va a la resistencia R tome el valor de 1 y trate de "acercar" el valor de la entrada - del comparador al valor de la entrada + del comparador.

- Si la entrada + del comparador está por debajo de la entrada -, el comparador emite un 0, que es cargado en el siguiente ciclo de reloj por el biestable de entrada, haciendo que la salida que va a la resistencia R tome el valor de 0 y trate de "acercar" (esta vez hacia "abajo") el valor de la entrada - del comparador al valor de la entrada + del comparador.


Como se puede ver, este comportamiento hace que para valores próximos a 0 voltios en la entrada + del comparador, el biestable de entrada emite muchos más 0s que 1s, mientras que para valores próximos a 3.3 voltios en la entrada + del comparador, el biestable de entrada emite muchos más 1s que 0s. También se puede ver que para valores próximos a Vcc / 2 = 1.65 voltios, el biestable emitirá una cantidad aproximadamente igual de 0s que de 1s.

Si lo que hacemos ahora es asociar al valor 0 de la salida del biestable, el valor numérico -1, y, al valor 1 de la salida del biestable, el valor numérico +1, lo que tenemos es un circuito digital que emitirá:


- Más +1 que -1 para valores próximos a 3.3 voltios.

- Más -1 que +1 para valores próximos a 0 voltios.

- Una cantidad aproximadamente igual de -1 y +1 para valores próximos a 1.65 voltios.


Si vamos acumulando estos -1 y +1 sobre un acumulador que se pone a cero cada cierto tiempo, lo que tendremos es que en dicho acumulador tendremos un valor proporcional al voltaje de entrada del ADC:


- Valores próximos a 3.3 voltios, al generar más +1 que -1, dan como resultado un valor de conversión muy alto y positivo.

- Valores próximos a 0 voltios, al generar más -1 que +1, dan como resultado un valor de conversión muy bajo y negativo.

- Valores próximos a 1.65 voltios, al generar una cantidad aproximadamente similar de -1 y de +1, dan como resultado un valor de conversión próximo a 0.


Debido a esta necesidad de contar -1s y +1s necesitamos sobremuestreo: Si queremos obtener una resolución de 16 bits, eso significa que el valor máximo de conversión tendrá que ser -32767 mientras que el valor mínimo de conversión tendrá que ser -32768. Si asumimos una escala simétrica tendremos un valor entre -32767 y +32767, eso significa que debemos hacer 32767 sumas (de -1s y +1s) antes de leer el valor de conversión. Dichas sumas las tenemos que hacer en un registro, que llamaremos "acumulador de conversión".

Para el caso de 16 bits necesitaríamos un contador de 15 bits que, en el momento de que valga 0 haga que el "acumulador de conversión" copie su valor en el registro de salida del ADC y se ponga a cero, y, durante los 32767 pulsos restantes (hasta el siguiente desbordamiento), se sumen los -1s y +1s que van entrando.

Así vemos que, por ejemplo, para 12 MHz y 16 bits de resolución, como necesitaríamos un contador de 15 bits, al final la frecuencia de muestreo podrá ser, como máximo de:

$$ {12000000 \over {2^{15}}} = 366.21 \: Hz $$

Por ejemplo, si queremos hacer una conversión en calidad CD necesitaríamos un reloj de sistema (sobremuestreo) de:

$$ {44100 \times {2^{15}}} = 1445068800 \: Hz $$

En el caso que nos ocupa se ha decidido implementar el transmisor en una FPGA MAX10 de las que viene en una placa MAX1000 de Arrow, a 12 MHz, cuyo reloj puede ser subido mediante PLLs hasta unos 400 MHz. Debido a esta limitación se ha decidido subir el reloj a 300 MHz y, para trabajar con esa frecuencia de sobremuestreo, se han tenido que bajar un poco las especificaciones del ADC, usando un contador de 13 bits y un acumulador de conversión de 14 bits. De esta forma tenemos una frecuencia de muestreo de:

$$ {300000000 \over {2^{13}}} = 36621.09375 \: Hz $$

Por lo que el ancho de banda es de unos 18 KHz (buen ancho de banda para música) y la resolución de conversión es de 14 bits (valores de conversión entre -8191 y +8191). No es calidad CD pero tampoco está mal.

Consideraciones entorno a la entrada de sonido analógica

Hay que tener en cuenta que las señales de sonido tal cual salen de un amplificador, son señales simétricas (con semiciclos positivos y negativos) mientras que nuestro ADC mide voltajes entre 0 y 3.3 voltios (no mide voltajes negativos). Es necesario, por tanto, acondicional la señal de sonido de entrada para que quede "desplazada" hacia arriba y un valor de 0 voltios de entrada se traduzca en 1.65 voltios a la entrada + del comparador. Es por esto por lo que se coloca el divisor de tensión entre 3.3 y 0 voltios a la entrada de audio, que desplaza el "0" de la señal de sonido hasta los 1.65 voltios.



El nivel de señal que entrega la salida de auriculares de un ordenador es suficiente para la entrada del ADC y no requiere amplificación adicional, al menos para esta prueba.

Generador de señal de antena

La frecuencia de transmisión elegida es 87.5 MHz (el extremo inferior de la banda de radiodifusión de sonido). La señal de antena que se quiere generar debe estar centrada, por tanto, en dicha frecuencia y dicha frecuencia deberá variarse en un rango máximo de +-75 KHz (estándar de radiodifusión). Para la generación de frecuencias arbitrarias que sean inferiores a la frecuencia de reloj de un sistema digital lo lógico es utilizar un acumulador de fase.

Un acumulador de fase no es más que un registro que se incrementa en un valor constante (no tiene por qué incrementarse de 1 en 1) con desbordamiento. Por ejemplo, si a partir de un reloj de 300 MHz queremos generar un reloj de 75 MHz lo que podemos hacer es incrementar un registro de 2 bits de 1 en 1 a 300 MHz:

...
Pulso: 00 --> 01
Pulso: 01 --> 10
Pulso: 10 --> 11
Pulso: 11 --> 00
Pulso: 00 --> 01
Pulso: 01 --> 10
Pulso: 10 --> 11
Pulso: 11 --> 00
...

En este ejemplo se puede ver que si la frecuencia de pulso es de 300 MHz, la frecuencia del bit 0 será de ${300 \over 2} = 150 \: MHz$ mientras que la frecuencia del bit 1 será de ${150 \over 2} = 75 \: MHz$. Un acumulador de fase con incrementos potencia de 2 es, formalmente, un divisor de frecuencia. Si se aplica este mismo principio para registros con mayor cantidad de bits y usando incrementos arbitrarios, conseguimos frecuencias diferentes.

Asumamos que la señal de salida de nuestro "oscilador" será siempre el bit más significativo de un registro de 16 bits y nuestro reloj va a 300 MHz. Eso significará que, incrementando el registro de 1 en 1, el bit más significativo cambiará a razón de:

$$ {300000000 \over {2^N}} = 4577 \: Hz $$

Si en lugar de incrementar el registro de 1 en 1, lo incrementamos de 2 en 2, la cantidad de pulsos que tarde en desbordarse el registro será menor, por tanto, la frecuencia del bit más significativo será mayor, es decir, la frecuencia del bit más significativo es proporcional al valor de incremento del registro. El máximo valor de incremento será $2^{N-1}$ para N bits (es decir un 1 seguido de N-1 ceros) que hará que el registro se comporte de la siguiente manera:

...
Pulso: 0000000000000000 --> 1000000000000000
Pulso: 1000000000000000 --> 0000000000000000
Pulso: 0000000000000000 --> 1000000000000000
Pulso: 1000000000000000 --> 0000000000000000
...

En este caso extremo tenemos que la frecuencia del bit más significativo es de 150 MHz (para una frecuencia de pulso de 300 MHz). De forma genérica tenemos que el valor de incremento para una frecuencia dada puede calcularse de la siguiente forma:

$$ I = {{f_{deseada}} \over 300000000} \times 2^{N} $$

En nuestro caso, si queremos emitir a 87.5 MHz hay que usar un valor de N=54 bits para que el valor de incremento no sea fraccionario:

$$ I_{central} = {87500000 \over 300000000} \times 2^{54} = 5254199565265579 $$

Ese valor es el que habría que usar como valor de incremento en cada pulso de reloj para que en el bit más significativo del registro de 54 bits (bit 53) tengamos una señal a 87.5 MHz. Nótese que para valores de incremento que no sean potencias de 2, obtendremos señales no cuadradas o de fase algo irregular (en el anterior post dedicado a la transmisión FM se profundiza en este tema), pero para el caso que nos ocupa, la "calidad" de la señal resultante no es significativa, lo importante es que la frecuencia fundamental sea la correcta. Para hacer modulación en frecuencia hemos de modificar esta frecuencia en el rango de -75 KHz y +75 KHz, lo que nos genera los siguiente incrementos mínimos y máximos:

$$ I_{min} = {(87500000 - 75000) \over 300000000} \times 2^{54} = 5249695965638208 $$

$$ I_{max} = {(87500000 + 75000) \over 300000000} \times 2^{54} = 5258703164892949 $$

Si calculamos la diferencia entre los incrementos y la dividimos entre dos nos dará el valor 4503599627370.5 que es la amplitud máxima que deberá tener la señal del ADC para que modifique el valor del incremento y que este, a su vez, genere una variación máxima de +- 75 KHz en la frecuencia portadora.

Como la salida del ADC da valores entre -8191 y +8191 a este valor hay que multiplicarle el valor ${4503599627370.5 \over 8191} = 549822930$ para que valores próximos a -8191 en la salida del ADC generen una portadora de 87.5 MHz - 75 KHz = 87.425 MHz y valores próximos a +8191 en la salida del ADC generen una portadora de 87.5 MHz + 75 KHz = 87.575 MHz. El valor del incremento del acumulador de fase que genera la señal de la antena será el siguiente:

$$ I = I_{central} + (ADC \times 549822930) = 5254199565265579 + (ADC \times 549822930) $$

A continuación puede verse el código fuente completo (cabe en un único fichero VHDL).

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


entity MAX10FMTransmitter is
    port (
        Clk              : in std_logic;
        AnalogComparator : in std_logic;
        PWM              : out std_logic;
        Antenna          : out std_logic;
        Led              : out std_logic_vector(7 downto 0)
    );
end entity;

architecture A of MAX10FMTransmitter is
    component PLL
        port (
            inclk0 : in std_logic := '0';
            c0        : out std_logic 
        );
    end component;
    signal Clk300 : std_logic;
    signal ADCFollowerD : std_logic;
    signal ADCFollowerQ : std_logic;
    signal ADCScalerD : std_logic_vector(12 downto 0);
    signal ADCScalerQ : std_logic_vector(12 downto 0);
    signal ADCAccumulatorD : std_logic_vector(13 downto 0);
    signal ADCAccumulatorQ : std_logic_vector(13 downto 0);   -- signed value: -8191 to +8191
    signal ADCOutputD : std_logic_vector(13 downto 0);
    signal ADCOutputQ : std_logic_vector(13 downto 0);
    signal PhaseAccumulatorD : std_logic_vector(53 downto 0);
    signal PhaseAccumulatorQ : std_logic_vector(53 downto 0);
    signal PhaseAccumulatorIncD : std_logic_vector(53 downto 0);
    signal PhaseAccumulatorIncQ : std_logic_vector(53 downto 0);
    -- increment for phase accumulator to transmit at 87.5 MHz: (87500000 / 300000000) * (2^54) = 5254199565265579
    --constant TxCentralFrequencyInc : integer := 5254199565265579;   -- must fit in 54 bits
    constant TxCentralFrequencyIncL : integer := 2863311531;   -- 32 bits
    constant TxCentralFrequencyIncH : integer := 1223338;      -- 22 bits
    constant TXCentralFrequencyInc : signed(53 downto 0) := to_signed(TxCentralFrequencyIncH, 22) & to_signed(TxCentralFrequencyIncL, 32);
    -- gain for ADC output 
    constant ADCGain : integer := 549822930;   -- ADCGain needs 30 bits
begin
    -- PLL to obtain 300 MHz from external 12 MHz
    P : PLL port map (
        inclk0 => Clk,
        c0 => Clk300
    );

    -- ADC
    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            ADCFollowerQ <= ADCFollowerD;
        end if;
    end process;
    
    ADCFollowerD <= AnalogComparator;
    PWM <= ADCFollowerQ;
    
    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            ADCScalerQ <= ADCScalerD;
        end if;
    end process;
    
    ADCScalerD <= std_logic_vector(unsigned(ADCScalerQ) + 1);

    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            ADCAccumulatorQ <= ADCAccumulatorD;
        end if;
    end process;

    ADCAccumulatorD <= std_logic_vector(to_signed(0, 14)) when (unsigned(ADCScalerQ) = 0) else
                       std_logic_vector(signed(ADCAccumulatorQ) + to_signed(1, 14)) when (ADCFollowerQ = '1') else
                       std_logic_vector(signed(ADCAccumulatorQ) - to_signed(1, 14)) when (ADCFollowerQ = '0') else
                       ADCAccumulatorQ;
    
    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            ADCOutputQ <= ADCOutputD;
        end if;
    end process;
    
    ADCOutputD <= ADCAccumulatorQ when (unsigned(ADCScalerQ) = 0) else
                  ADCOutputQ;
    Led <= ADCOutputQ(13 downto 6);
    
    -- phase accumulator
    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            PhaseAccumulatorQ <= PhaseAccumulatorD;
        end if;
    end process;
        
    PhaseAccumulatorD <= std_logic_vector(unsigned(PhaseAccumulatorQ) + unsigned(PhaseAccumulatorIncQ));
    Antenna <= PhaseAccumulatorQ(53);
    
    -- phase accumulator increment control (output frequency control)
    process (Clk300)
    begin
        if (Clk300'event and (Clk300 = '1')) then
            PhaseAccumulatorIncQ <= PhaseAccumulatorIncD;
        end if;
    end process;
    
    -- 14 bits * 40 bits = 54 bits
    PhaseAccumulatorIncD <= std_logic_vector((signed(ADCAccumulatorQ) * to_signed(ADCGain, 40)) + TxCentralFrequencyInc) when (unsigned(ADCScalerQ) = 0) else
                            PhaseAccumulatorIncQ;
end architecture;


El bit 53 del registro acumulador de fase se saca por un pin de la FPGA y en dicho pin se puede colocar un simple trozo de cable. No es necesario hacer ningún circuito que acondicione la señal de salida.



Si a corta distancia del circuito ponemos un receptor de radio FM comercial sintonizado a 87.5 MHz podremos escuchar la señal que está leyendo el ADC de la FPGA y que está siendo transmitida en FM.



Código fuente disponible en la sección soft.

[ añadir comentario ] ( 94 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 15 )
Efectos de sonido con un CPLD 
¿Se pueden generar efectos de sonido rudimentarios sin un chip de sonido y utilizando muy pocas macroceldas de un CPLD o una FPGA?

Motivación

En la anterior entrada del blog se realizó un pequeño proyecto para generar dos efectos de sonido utilizando un sencillo chip PSG, el SN76489. Los sonidos generados fueron muy buenos pero a nivel hardware, como el objetivo del montaje es una futura integración en un pequeño robot con mando a distancia, me encontré con que, el hecho de tener que cablear todo un bus de 8 bits junto con los pines OE y READY, consumía muchos pines del STM32 (el robot consume ya GPIO y PWM para las ruedas, UART para la comunicación e I2C para una pequeña pantalla OLED).

Una primera aproximación válida sería un conversor serie a paralelo de tipo I2C o algo así pero instalar DOS chips sólo para generar dos ruidos rudimentarios me pareció excesivo, por lo que opté por una solución basada en un CPLD.

64 macroceldas

Ese es el reto: hacer un generador de sonido que consuma, como mucho, 64 macroceldas en un CPLD de la serie MAX3000A de Intel, en concreto el EPM3064.



64 macroceldas son 64 biestables con la lógica combinatoria asociada. No es un reto sencillo y es probable que los resultados no tengan tan buena calidad como con un PSG, pero el beneficio que se consigue en reducción de pines, miniaturización y reducción de consumo (Un CPLD consume mucho para los estándares actuales, pero el SN76489 consume más) hace que valga la pena intentarlo.

El circuito

Se plantea un circuito sencillo con dos entradas configuradas en lógica negativa y circuitería antirrebote básica y una salida con condensador de desacoplo y divisor de tensión para evitar sobretensiones en el amplificador de audio de la salida. La placa equipada con el CPLD EPM3064 incluye un oscilador a 50 MHz conectado a uno de los pines de reloj del CPLD y un led con su cátodo conectado a otro de los pines del CPLD (se enciende cuando se emite un 0 por ese pin).



Diagrama de bloques

A continuación puede verse el diagrama del bloques que se ha implementado en el CPLD.



El bloque $x^{18} + x^{11} + 1$ se corresponde con el LFSR maximal de grado 18 que permite generar ruido blanco (aproximado) en el registro de arriba.

L1 es un bloque combinacional que emite un 1 si la entrada vale 0 y la entrada sin cambiar en caso contrario.
L1
EntradaSalida
01
xx

L2 es un bloque combinacional que emite 8193 si la entrada vale 0 y la entrada sin cambiar en caso contrario.
L2
EntradaSalida
08193
xx

B es el bloque combinacional encargado de controlar los multiplexores en función de las señales de entrada y del cruce por cero del registro de 23 bits:
B
EntradasSalidas (MUX)
/Laser/NoiseNS123
11dc111
0dcdc00dc
100010
101210
(dc = don't care)

Descripción funcional

Cuando se activa la entrada /Laser (se pone a nivel bajo), se carga en el registro de 23 bits el valor "01110000000000000000000", este valor se carga para que los 4 bits más significativos tengan el valor "0111". Si nos fijamos los 4 bits más significativos del registro de 23 bits se utlizan para incrementar el valor del registro de 18 bits. Dicho registro de 18 bits actúa como acumulador de fase para una señal de onda cuadrada correspondiente al bit más significativo (bit 17) de este registro.

Si tenemos un registro de 18 bits como acumulador de fase y una frecuencia de reloj de 50 MHz (la de la placa que estamos usando) tendremos una frecuencia del bit más significativo de:

$$f_{out} = {f_{clk} \over 2^{18}}$$

De forma general, en caso de que apliquemos incrementos arbitrarios a este registro de desplazamiento, obtendremos una frecuencia en el bit más significativo de:

$$f_{out} = Inc \times {f_{clk} \over 2^{18}} = Inc \times {50000000 \over 2^{18}}$$

Para simular el sonido de un disparo láser lo que generamos es una caida rápida en frecuencia por lo que empezamos con un $Inc = 7$ cuando /Laser = 0 (de ahí los 4 bits más significativos del valor 01110000000000000000000), este valor de incremento genera una frecuencia en el bit más significativo del registro de 18 bits de:

$$Inc = 7 \Rightarrow f_{out} = 7 \times {50000000 \over 2^{18}} = 1335.1 Hz$$

Cuando /Laser vuelve al valor 1, vamos bajando el valor de Inc (los 4 bits más significativos del registro de 23 bits) hasta que vale 0:

$$Inc = 6 \Rightarrow f_{out} = 1144.4 Hz$$
$$Inc = 5 \Rightarrow f_{out} = 953.67 Hz$$
$$Inc = 4 \Rightarrow f_{out} = 762.94 Hz$$
$$Inc = 3 \Rightarrow f_{out} = 572.2 Hz$$
$$Inc = 2 \Rightarrow f_{out} = 381.47 Hz$$
$$Inc = 1 \Rightarrow f_{out} = 190.73 Hz$$
$$Inc = 0 \Rightarrow f_{out} = 0 Hz$$

Como el registro de 23 bits también actúa como un acumulador de fase (pues se decrementa en bloque, no solo los bits más significativos), la caida es lo suficientemente lenta como para ser audible (que es lo que queremos). Nótese que una vez cae a 0, el registro de 23 bits se queda ahí estancado gracias al circuito combinacional L1 que actúa como limitador, lo que, en la práctica, provoca que el registro de 18 bits "pare" de oscilar (pues $Inc = 0$ siempre). Nótese también que, aunque pare de oscilar, es posible que a la salida que va hacia el amplificador (el bit 17) se quede un "1" de forma pemanente, es por ello por lo que se hace necesario colocar siempre un condensador de desacoplo a la salida.

Si lo que se pone a 0 es la entrada /Noise y la entrada /Laser permanece a 1, lo que se hace es seleccionar como realimentación del registro de 18 bits la salida del polinomio LFSR, que provocará una secuencia de números pseudoaleatorios (ruido) en el bit 17 (salida del amplificador). La carga del valor del LFSR no se produce en cada ciclo del reloj de 50 MHz, pues provocaría ruido no audible, sino que se aprovecha el registro de desplazamiento de 23 bits y, a través del circuito combinacional L2, se hace que "desborde" en 8192, por lo que el registro de 18 bits cambiará con una frecuencia de:

$$f_{muestreo} = {50000000 \over 8192} = 6103.5 Hz$$

por lo que el ruido resultante ocupará aproximadamente hasta la banda de los 3 KHz. No es un ruido blanco pero a efectos audibles es muy parecido al ruido generado por un PSG.

Código fuente

Todo el código fuente en VHDL puede meterse dentro de una sola entidad:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity Epm3064GunSound is
    port (
        Clk               : in std_logic;
        TrigLaserIn       : in std_logic;
        TrigNoiseIn       : in std_logic;
        SpeakerOut        : out std_logic;
        LedOut            : out std_logic
    );
end entity;

architecture Architecture1 of Epm3064GunSound is
    signal LFSRD : std_logic_vector(17 downto 0);
    signal LFSRQ : std_logic_vector(17 downto 0);
    signal LFSRRawOut : std_logic_vector(17 downto 0);
    signal LFSROut : std_logic_vector(17 downto 0);
    signal LFSRMux : std_logic_vector(1 downto 0);
    signal TimerD : std_logic_vector(22 downto 0);
    signal TimerQ : std_logic_vector(22 downto 0);
    signal TimerMux : std_logic;
    signal Limited1TimerQ : std_logic_vector(22 downto 0);
    signal Limited2TimerQ : std_logic_vector(22 downto 0);
    signal LimiterMux : std_logic;
    signal LimiterMuxOut : std_logic_vector(22 downto 0);
    signal NoiseSample : std_logic;
begin
    -- LFSR
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            LFSRQ <= LFSRD;
        end if;
    end process;
    
    --LFSRRawOut <= (LFSRQ(0) xor LFSRQ(3)) & LFSRQ(19 downto 1);    20 bits
    LFSRRawOut <= (LFSRQ(0) xor LFSRQ(7)) & LFSRQ(17 downto 1);    -- 18 bits
    LFSROut <= LFSRRawOut when (unsigned(LFSRRawOut) /= 0) else
               std_logic_vector(to_unsigned(1, 18));
    LFSRD <= LFSROut when (LFSRMux = "10") else
             std_logic_vector(unsigned(LFSRQ) + unsigned(TimerQ(22 downto 19))) when (LFSRMux = "01") else
                LFSRQ;
    SpeakerOut <= LFSRQ(17);
    
    -- timer
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            TimerQ <= TimerD;
        end if;
    end process;

    Limited1TimerQ <= std_logic_vector(to_unsigned(1, 23)) when (signed(TimerQ) = 0) else
                      TimerQ;
    Limited2TimerQ <= std_logic_vector(to_unsigned(8192 + 1, 23)) when (signed(TimerQ) = 0) else
                      TimerQ;
    LimiterMuxOut <= Limited1TimerQ when (LimiterMux = '1') else
                     Limited2TimerQ;
    TimerD <= std_logic_vector(signed(LimiterMuxOut) - 1) when (TimerMux = '1') else
              "01110000000000000000000";
    NoiseSample <= '1' when (signed(TimerQ) = 0) else
                   '0';
    
    -- operation logic
    LFSRMux <= "01" when (((TrigLaserIn = '1') and (TrigNoiseIn = '1')) or (TrigLaserIn = '0')) else
               "10" when ((TrigLaserIn = '1') and (TrigNoiseIn = '0') and (NoiseSample = '1')) else
                  "00";
    TimerMux <= '0' when (TrigLaserIn = '0') else
                '1';
    LimiterMux <= '1' when ((TrigLaserIn = '1') and (TrigNoiseIn = '1')) else
                  '0';
    LedOut <= TrigLaserIn and TrigNoiseIn;
end architecture;


Resultados

Con este circuito se consiguen unos resultados similares a los obtenidos utilizando el chip SN76489, con menos circuitería, ocupando menos pines y menos tiempo de procesamiento en el microcontrolador: nótense que ahora sólo necesitamos dos pines GPIO del microcontrolador (uno para /Laser y otro para /Noise).



Todo el código fuente puede descargarse de la sección soft.

[ añadir comentario ] ( 151 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 2.9 / 147 )
Implementación de un conversor analógico-digital delta-sigma sobre FPGA 
El uso de conversores analógico-digitales de tipo delta-sigma permite conseguir una conversión con una muy buena calidad usando sólo tres componentes externos y una circuitería analógica mínima. La resolución del conversor es arbitraria y depende de los recursos digitales que se tengan (biestables) y de la velocidad de reloj (sobremuestreo).

Esquema

Como se puede apreciar la parte analógica del conversor se reduce a un comparador, una resistencia y un condensador. En la "zona digital" tenemos todo el meollo: un biestable, un acumulador y un filtro paso bajo. Veamos el funcionamiento con una traza sencilla.

Funcionamiento

Asumamos que empezamos con la salida que va al filtro RC a 0 y el condensador totalmente descargado y que en la entrada analógica tenemos un voltaje entre 0 y Vcc (3.3 voltios) que llamaremos $V_{in}$. En estas condiciones iniciales, como $V_{in} > 0$, el comparador emite 3.3 voltios (un 1 lógico) y por tanto, en el siguiente ciclo de reloj, el biestable carga ese 1 en su salida Q.

Tras esta primera carga la salida que va conectada al filtro RC (la Q del biestable) se pone a 1, lo que significa que el condensador empieza a cargarse a través de R. Para cuando llegue el siguiente ciclo de reloj lo más probable es que la carga del condensador aún sea muy baja y el comparador siga emitiendo a su salida un 1 (su entrada + tiene un voltaje aún mayor que su entrada -). Tendremos, por tanto que el biestable seguirá emitiendo 1s hasta que el voltaje en la entrada - del comparador sea superior a $V_{in}$, en el momento que ocurra eso la salida del comparador será de 0 voltios (un 0 lógico). El biestable en el siguiente ciclo de reloj cargará ese 0 en su Q y el condensador empezará ahora a descargarse a través de la R.

Como se puede apreciar, el sistema en lazo cerrado lo que hace es, mediante 0s y 1s tratar de hacer que el voltaje en la entrada - del comparador (el voltaje en el condensador) "siga" al voltaje de entrada $V_{in}$ y esto, en la práctica, significa que a la salida del biestable lo que tenemos es una especie de señal PWM cuyo ciclo de trabajo será proporcional al voltaje $V_{in}$:

1.- Cuanto mayor es el valor de $V_{in}$, mayor cantidad de 1s emite el biestable para tratar de "alcanzar" $V_{in}$.

2.- Cuanto menor es el valor de $V_{in}$, mayor es la cantidad de 0s que emite el biestable para tratar de "alcanzar" $V_{in}$.

3.- Se puede ver, además, que para un valor próximo a ${Vcc \over 2}$ el biestable emitirá aproximadamente la misma cantidad de 0s que de 1s por unidad de tiempo.

Ahora supongamos que pasamos la salida del biestable por un circuito combinacional que ante un 0 en la entrada emite un -1 en un bus de N bits y ante un 1 en la entrada emite un +1 en esa misma salida de N bits. Si durante $2^{N-1}$ ciclos de reloj vamos sumando en un acumulador con signo los valores +1 y -1 que van saliendo de este circuito combinacional tendremos lo siguiente:

1.- Para valores altos de $V_{in}$, que generan muchos más 1s que 0s en la salida del biestable, tendremos que el acumulador cada $2^{N-1}$ ciclos de reloj tendrá un valor próximo a $2^{N-1}$.

2.- Para valores bajos de $V_{in}$, que generan muchos más 0s que 1s en la salida del biestable, tendremos que el acumulador, cada $2^{N-1}$ ciclos de reloj, tendrá un valor próximo a $-2^{N-1}$.

3.- Para valores próximos a ${Vcc \over 2}$, que generan una cantidad muy similar de 0s y de 1s por unidad de tiempo, tendremos que el acumulador, cada $2^{N-1}$ ciclos de reloj, tendrá un valor próximo a 0.

Si cada $2^{N-1}$ ciclos de reloj cargamos el valor de este acumulador sobre un registro (que podemos llamar de salida), justo antes de poner de nuevo el acumulador a 0 (y volver a acumular los +1 y -1 que llegan del biestable), tenemos que este registro de salida será el resultado de la conversión analógico-digital del voltaje de entrada $V_{in}$ en N bits, que era nuestro objetivo. En la implementación recomendada se introduce una tercera etapa que realiza un sobremuestreo adicional para suavizar la señal (actúa como filtro paso bajo) pero que no se ha implementado en este caso por simplicidad.

Otra característica importante en este tipo de conversores es que la resolución en bits del conversor viene determinada por N (la anchura en bits del acumulador y del filtro paso bajo), que es un parámetro relativamente sencillo de cambiar, y que no requiere cambios en la electrónica externa.

Implementación

A continuación puede verse un esquema detallado de lo que sería la implementación en una FPGA o en un CPLD del conversor descrito:

Cada vez que el contador pasa por 0 hace que en acumulador se cargue en el registro de salida y al mismo tiempo se reinicie con el valor 0. Los valores de la resistencia R y del condensador C deben ser elegidos acorde a la frecuencia de reloj del sistema. Por ejemplo, Lattice Semiconductor recomienda que la constante de tiempo del filtro $t = R \times C$ cumpla que $200 < t \times f_{clk} < 1000$. En este caso concreto se ha hecho una implementación sobre un CPLD de Altera (Intel) que va a 50 MHz y se han elegido los valores de C = 1.5 nF y R = 10 K, que cumplen dicha desigualdad.

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

entity DeltaSigmaADC is
    port (
        Reset        : in std_logic;
        Clk          : in std_logic;
        DataOut      : out std_logic_vector(7 downto 0);
        ComparatorIn : in std_logic;
        RCChargeOut  : out std_logic
    );
end entity;

architecture Architecture1 of DeltaSigmaADC is
    signal DBus : std_logic;
    signal QBus : std_logic;
    signal FollowerOut : std_logic_vector(15 downto 0);
    signal CounterDBus : std_logic_vector(14 downto 0);
    signal CounterQBus : std_logic_vector(14 downto 0);
    signal IntegratorDBus : std_logic_vector(15 downto 0);
    signal IntegratorQBus : std_logic_vector(15 downto 0);
    signal LatchDBus : std_logic_vector(15 downto 0);
    signal LatchQBus : std_logic_vector(15 downto 0);
begin
    -- biestable de seguimiento de voltaje
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            QBus <= DBus;
        end if;
    end process;

    DBus <= ComparatorIn;
    RCChargeOut <= QBus;
    FollowerOut <= std_logic_vector(to_signed(-1, 16)) when (QBus = '0') else
                   std_logic_vector(to_signed(1, 16));

    -- contador
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            CounterQBus <= CounterDBus;
        end if;
    end process;

    CounterDBus <= std_logic_vector(to_signed(0, 15)) when (Reset = '1') else
                   std_logic_vector(signed(CounterQBus) + 1);

    -- integrador
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            IntegratorQBus <= IntegratorDBus;
        end if;
    end process;

    IntegratorDBus <= std_logic_vector(to_signed(0, 16)) when (signed(CounterQBus) = 0) else
                      std_logic_vector(signed(FollowerOut) + signed(IntegratorQBus));

    -- latch
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            LatchQBus <= LatchDBus;
        end if;
    end process;
    
    LatchDBus <= IntegratorQBus when (signed(CounterQBus) = 0) else
                 LatchQBus;

    -- salida
    DataOut <= LatchQBus(15 downto 8);
end architecture;



El código fuente está disponible en la sección soft.

[ añadir comentario ] ( 883 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 2578 )
Luz de fogata para el belén con CPLD 
Un año más, decorando el belén con nuevos inventos. En esta ocasión he vuelto a los orígenes y he implementado una luz, pero esta vez una luz especial que simule un fuego encendido utilizando un CPLD.

Introducción

A la hora de simular el crepitar de una llamas se ha optado por hacer que un led varíe de luminosidad de forma aleatoria varias veces por segundo.



Con una luz que varíe su intensidad lumínica varias veces por segundo (entre 5 y 6 veces por segundo, por ejemplo) más una buena escenografía (color de la luz, decorados, etc.) se consigue un razonable efecto de fuego encendido.

Diseño técnico

Como vamos a hacer el diseño utilizando un circuito totalmente digital, la intensidad lumínica se modulará utilizando una señal cuadrada modulada en anchura (PWM). Cuanto mayor sea el semiciclo a 1 y menor el semiciclo a 0 más brillará el led y a la inversa: cuanto mayor sea el semiciclo a 0 y menor el semiciclo a 1 menos brillará.



Si hacemos que la frecuencia sea lo suficientemente alta no se apreciará ningún tipo de parpadeo y la luz se percibirá como que brilla de forma continuada pero con diferente intensidad. El CPLD utilizado tiene conectado un reloj a 50 MHz, por tanto usando un contador de 10 bits estándar conseguiremos un desbordamiento a una frecuencia de

$${50000000 \over {2^{10}}} = 48828.125 Hz$$

Si el valor de este contador lo comparamos con un valor determinado, el resultado de esta comparación será la salida PWM que necesitamos para el led de nuestra fogata:



El valor de intensidad lo generaremos mediante un LFSR maximal de 10 bits. Dicho LFSR ha sido utilizado en otros montajes anteriores:



Genera una secuencia maximal de 1023 valores pseudoaleatorios (el valor 0 no aparece en la secuencia) que se puede usar como valor de intensidad lumínica:



En este circuito el bloque combinacional "OP" es el que implementa el polinomio maximal de 10 bits. A continuación sólo falta implementar la temporización. Como queremos el que valor de intensidad lumínica cambie unas 5 ó 6 veces por segundo, bastará con poner un contador estándar de:

$$\lceil log_2\left({50000000 \over 6}\right) \rceil = 23 bits$$

Con un contador de 23 bits a 50 MHz tendremos una frecuencia de desbordamiento de casi 6 veces por segundo:

$${50000000 \over {2^{23}}} = 5.96 Hz$$

A continuación puede verse cómo quedaría el diagrama completo:



El bloque combinacional "CM" (Control del Multiplexor) se encarga de controlar la selección del multiplexor del LFSR en función del timer contador de 23 bits y del valor del propio LFSR. La tabla de verdad de este bloque sería la siguiente:

EntradasSalidas
Timer == 0LFSR == 0MUX
X1"1"
10Salida de OP
00Salida del LFSR


Cuando el valor del LFSR es 0, lo que hace es seleccionarla entrada "1" del multiplexor para que el LFSR se cargue con un valor distinto de cero (el 1) y poder así arrancar la generación de números aleatorios. Cuando el timer (contador de 23 bits) se desborda (pasa por cero) el LFSR se carga con el siguiente valor de la secuencia de números pseudoaleatorios y el resto del tiempo (valor del contador de 23 bits diferente de 0) el registro LFSR permanece inalterado.

Como se puede apreciar, se ha prescindido de circuitería de reset. Teniendo en cuenta que el objetivo es minimizar la circuitería y que los fabricantes siempre te garantizan que en el arranque, todos los biestables están a 0, se puede "abusar" de esta característica y ahorrar así parte de la circuitería de reset.

Posibles mejoras

Como se puede apreciar, del contador que hace de timer (el de 23 bits) sólo nos interesa cuando pasa por un valor concreto (el 0), no es como el contador que se utiliza para comparar con el LFSR y generar la señal PWM. Teniendo esto presente, dicho contador de 23 bits podría implementarse utilizando también un LFSR maximal de 23 bits: en lugar de un sumador, se puede implementar un bloque combinacionar consistente tan solo en una única puerta xor (ver polinomio a aplicar aquí), lo que supone un ahorro considerable en circuitería.

Hay que tener presente que los LFSRs no pasan nunca por cero, por lo que habría que elegir cualquier otro valor (cualquiera) como valor de "desbordamiento" (el valor 1, por ejemplo).

Implementación

A continuación puede verse la implementación de este diseño en VHDL.

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

entity ChristmasFire is
    port (
        Clk : in std_logic;
        Led : out std_logic
    );
end entity;

architecture RTL of ChristmasFire is
    signal CounterDBus : std_logic_vector(9 downto 0);
    signal CounterQBus : std_logic_vector(9 downto 0);
    signal LFSRDBus : std_logic_vector(9 downto 0);
    signal LFSRQBus : std_logic_vector(9 downto 0);
    signal TimerDBus : std_logic_vector(22 downto 0);
    signal TimerQBus : std_logic_vector(22 downto 0);
    signal TimerOverflow : std_logic;
begin
    -- pwm counter
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            CounterQBus <= CounterDBus;
        end if;
    end process;
    
    CounterDBus <= std_logic_vector(signed(CounterQBus) + to_signed(1, 10));
    
    -- lfsr
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            LFSRQBus <= LFSRDBus;
        end if;
    end process;
    
    LFSRDBus <= std_logic_vector(to_signed(1, 10)) when (signed(LFSRQBus) = to_signed(0, 10)) else
                ((LFSRQBus(3) xor LFSRQBus(0)) & LFSRQBus(9 downto 1)) when (TimerOverflow = '1') else
                LFSRQBus;
    
    -- timer
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            TimerQBus <= TimerDBus;
        end if;
    end process;
    
    TimerDBus <= std_logic_vector(signed(TimerQBus) + to_signed(1, 23));
    TimerOverflow <= '1' when (signed(TimerQBus) = to_signed(0, 23)) else
                     '0';
    
    -- output
    Led <= '1' when (signed(CounterQBus) > signed(LFSRQBus)) else
           '0';
end architecture;


Se trata de un único fichero que puede descargarse desde la sección soft.



¡Feliz Navidad a todos!

[ añadir comentario ] ( 511 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 8413 )
Sintetizador monofónico basado en FPGA: Parser MIDI mejorado y filtro paso-bajo de segundo orden 
Partiendo del montaje realizado en el post anterior, se han realizado varias modificaciones y mejoras. El parser MIDI de esta segunda iteración genera ahora 3 señales de control, de 7 bits cada una, que se utilizan para controlar la frecuencia de corte, la resonancia y la ganancia de la entrada de un filtro paso bajo de segundo orden:

Este sería el diagrama de bloques de esta segunda iteración:




Parser MIDI mejorado


En la versión iniciar el parser MIDI no se tuvieron en cuenta algunas características "raras" que se dan el algunos teclados controladores y al mismo tiempo se asumía que un "note off" posterior a un "note on" siempre era de la misma tecla, lo cual es demasiado suponer, sobre todo cuando quien toca es un humano. Cuando un humano toca una secuencia de notas en un teclado (por ejemplo: La, Mi, Do) uno puede pensar que los mensaje que manda el teclado controlador son los siguientes:
noteOn(La), noteOff(La), noteOn(Mi), noteOff(Mi), noteOn(Do), noteOff(Do)

Sin embargo lo cierto es que a veces un humano pulsa la siguiente tecla al mismo tiempo o antes de soltar la anterior:
noteOn(La), noteOn(Mi), noteOff(La), noteOff(Mi), noteOn(Do), noteOff(Do)

Con la anterior versión del parser, que asumía que un noteOff se correspondía siempre con el noteOn inmediatamente anterior, lo que ocurría era que cuando al sinte le llegaba el noteOff(La) callaba la nota Mi disparada justo antes porque asumía que ese noteOff se correspondía con dicha nota Mi. En la nueva versión del parse este noteOff(Mi) es ignorado por la máquina de estados por lo que la respuesta del sintetizador es más natural.

Para mejorar el comportamiento y la funcionalidad del parser MIDI se ha optado por un diseño basado en máquinas de estado en serie y en paralelo en lugar de una única máquina de estados grande. El parser MIDI se ha divido en dos etapas (Stage1 y Stage2), la primera etapa genera señales "KeyOn" y "KeyOff" limpias por cables separados y además implementa en paralelo una máquina de estados aparte para procesar los mensajes de "Control Change". En la segunda etapa se implementa la lógica anteriormente descrita de ignorar los "Note Off" que no se corresponden con el mensaje "Note On" inmediatamente anterior.







De esta forma, aunque aparentemente se ha complicado el diseño, se han separado los problemas y es más sencillo introducir modificaciones y depurar errores en las máquinas de estado. Cada una por separado es más sencilla y fácil de trazar que una hipotética máquina de estados única para todo.

Además de la mejora en el procesado de los mensajes "Note On" y "Note Off", este parser ya reconoce mensajes de tipo "Control Change", en concreto para tres valores prefijados de controlador: 71, 74 y 16, que se asignarán en el sintetizador a la frecuencia de corte del filtro, la resonancia del filtro y la ganancia de entrada del filtro.


Filtro paso bajo de segundo orden


Se ha optado por la implementación estándar de un filtro de estado variable (state variable filter). Se trata de un filtro de segundo orden (dos polos) que genera simultáneamente 3 salidas:

- paso bajo (con pendiente de filtrado de 12 dB/octava)
- paso alto (con pendiente de filtrado de 12 dB/octava)
- paso banda (con pendiente de filtrado de 6 dB/octava)

No son grandes pendientes de filtrado pero siempre se pueden mejorar poniendo varios filtros en cascada. La implementación que se ha utilizado es la descrita en el libro "Musical Applications of Microprocessors" de Hal Chamberlin (dicha implementación ya fue usada sobre un microcontrolador en este post). El filtro de estado variable viene determinado por el siguiente sistema de ecuaciones en diferencias finitas:

$$pasoAlto[n] = entrada - ({r \times pasoBanda[n-1]}) - pasoBajo[n]$$
$$pasoBanda[n] = ({f \times pasoAlto[n]}) + pasoBanda[n - 1]$$
$$pasoBajo[n] = ({f \times pasoBanda[n - 1]}) + pasoBajo[n - 1]$$

Siendo:

$$f = 2\sin\left({\pi F_c \over F_s}\right)$$
$$r = {1 \over Q}$$

Siendo $F_c$ la frecuencia de corte del filtro, $F_s$ la frecuencia de muestreo y $Q$ la Q del filtro (la resonancia).

Si se reordenan las ecuaciones en diferencias:

$$pasoBajo[n] = ({f \times pasoBanda[n - 1]}) + pasoBajo[n - 1]$$
$$pasoAlto[n] = entrada - ({r \times pasoBanda[n - 1]}) - pasoBajo[n]$$
$$pasoBanda[n] = ({f \times pasoAlto[n]}) + pasoBanda[n - 1]$$

Podemos olvidarnos de los índices:
pasoBajo += f * pasoBanda
pasoAlto = entrada - (r * pasoBanda) - pasoBajo
pasoBanda += f * pasoAlto

Como se puede apreciar es preciso mantener en memoria (registro) al menos las variables pasoBajo y pasoBanda entre que se procesa una muestra y la siguiente (se trata de un filtro digital de segundo orden).

Para implementar dicho filtro sobre FPGA lo que necesitaremos serán básicamente los siguientes elementos:

- Al menos tres registros en los que almacenaremos los valores "pasoBajo", "pasoBanda" y "pasoAlto" (aunque realmente podríamos no gastar un registro para "pasoAlto", lo vamos a incluir para poder disponer de esa salida en el módulo).
- Una unidad de suma con multiplicación: Un módulo combinacional que realiza la operación: A = (B * C) + D (en muchos casos D = A, por lo que se puede ver como A += B * C)
- Una máquina de estados para controlar qué operandos y operaciones se hacen en cada momento.

Con estos elemento y teniendo en cuenta las ecuaciones anteriores, podemos hacer una propuesta de secuenciación de operaciones como sigue:

1. LP := (cutoff * BP) + LP
2. HP := (0 * x ) + IN
3. HP := (-reso * BP) + HP
4. HP := (-1 * LP) + HP
5. BP := (cutoff * HP) + BP

Cada paso requiere un único ciclo de reloj por lo que bastará con implementar una máquina de estados que, por cada muestra que llegue, pase por los 5 estados de forma secuencial para que los registros LP, BP y HP (LowPass, BandPass y HighPass) tengan los valores de salida del filtro que necesitamos. Nótese que será preciso utilizar aritmética de punto fijo y en nuestro caso se ha optado por un formato Q16.16 (16 bits de parte entera y 16 bits de parte fraccionaria).

A continuación puede verse como quedaría la implementación del filtro en VHDL:

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

entity StateVariableFilter is
    port (
        Reset       : in std_logic;
        Clk         : in std_logic;
        EnableIn    : in std_logic;
        SampleIn    : in std_logic_vector(15 downto 0);
        CutOffIn    : in std_logic_vector(31 downto 0);    -- 0..1  fixed point Q16.16
        ResonanceIn : in std_logic_vector(31 downto 0);    -- 0..1  fixed point Q16.16
        SampleOut   : out std_logic_vector(15 downto 0);
        EnableOut   : out std_logic
    );
end entity;

architecture RTL of StateVariableFilter is
    signal LPDBus : std_logic_vector(31 downto 0);
    signal LPQBus : std_logic_vector(31 downto 0);
    signal HPDBus : std_logic_vector(31 downto 0);
    signal HPQBus : std_logic_vector(31 downto 0);
    signal BPDBus : std_logic_vector(31 downto 0);
    signal BPQBus : std_logic_vector(31 downto 0);
    signal MultOperandA : std_logic_vector(31 downto 0);
    signal MultOperandB : std_logic_vector(31 downto 0);
    signal MultResult64 : std_logic_vector(63 downto 0);
    signal MultResult : std_logic_vector(31 downto 0);
    signal AddOperandB : std_logic_vector(31 downto 0);
    signal AddResult : std_logic_vector(31 downto 0);
    signal NegResonance : std_logic_vector(31 downto 0);
    signal FSMDBus : std_logic_vector(2 downto 0);
    signal FSMQBus : std_logic_vector(2 downto 0);
begin
    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            LPQBus <= LPDBus;
        end if;
    end process;

    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            HPQBus <= HPDBus;
        end if;
    end process;

    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            BPQBus <= BPDBus;
        end if;
    end process;

    process (Clk)
    begin
        if (Clk'event and (Clk = '1')) then
            FSMQBus <= FSMDBus;
        end if;
    end process;

    NegResonance <= std_logic_vector(to_signed(-to_integer(signed(ResonanceIn)), 32));
    MultOperandA <= CutOffIn when ((FSMQBus = "001") or (FSMQBus = "101")) else
                    NegResonance when (FSMQBus = "011") else
                    std_logic_vector(to_signed(-65536, 32)) when (FSMQBus = "100") else  -- -65536 es -1 en notación Q16.16
                    std_logic_vector(to_signed(0, 32));
    MultOperandB <= LPQBus when (FSMQBus = "100") else
                    BPQBus when ((FSMQBus = "001") or (FSMQBus = "011")) else
                    HPQBus;
    AddOperandB <= LPQBus when (FSMQBus = "001") else
                   BPQBus when (FSMQBus = "101") else
                   HPQBus when ((FSMQBus = "011") or (FSMQBus = "100")) else
                   std_logic_vector(to_signed(to_integer(signed(SampleIn)), 32));
    --MultResult64 <= std_logic_vector(to_signed(to_integer(signed(MultOperandA)) * to_integer(signed(MultOperandB)), 64));
    MultResult64 <= std_logic_vector(signed(MultOperandA) * signed(MultOperandB));
    MultResult <= MultResult64(47 downto 16);
    --AddResult <= std_logic_vector(to_signed(to_integer(signed(MultResult)) + to_integer(signed(AddOperandB)), 32));
    AddResult <= std_logic_vector(signed(MultResult) + signed(AddOperandB));
    LPDBus <= std_logic_vector(to_signed(0, 32)) when (Reset = '1') else
              AddResult when (FSMQBus = "001") else
              LPQBus;
    HPDBus <= std_logic_vector(to_signed(0, 32)) when (Reset = '1') else
              AddResult when ((FSMQBus = "011") or (FSMQBus = "100") or (FSMQBus = "010")) else
              HPQBus;
    BPDBus <= std_logic_vector(to_signed(0, 32)) when (Reset = '1') else
              AddResult when (FSMQBus = "101") else
              BPQBus;

    -- fsm
    --    LP += cutoff * BP
    --    HP = in - (resonance * BP) - LP
    --    BP += cutoff * HP
    FSMDBus <= "000" when ((Reset = '1') or (FSMQBus = "110")) else       --       MultOperandA   MultOperandB   AddOperandB
               "001" when ((FSMQBus = "000") and (EnableIn = '1')) else   -- LP := cutoff       * BP           + LP
               "010" when (FSMQBus = "001") else                          -- HP := 0            * x            + IN
               "011" when (FSMQBus = "010") else                          -- HP := -reso        * BP           + HP
               "100" when (FSMQBus = "011") else                          -- HP := -1           * LP           + HP
               "101" when (FSMQBus = "100") else                          -- BP := cutoff       * HP           + BP
               "110" when (FSMQBus = "101") else
               "000";
    EnableOut <= '1' when (FSMQBus = "110") else
                 '0';
    SampleOut <= std_logic_vector(to_signed(-32768, 16)) when (to_integer(signed(LPQBus)) < -32768) else
                 std_logic_vector(to_signed(32767, 16)) when (to_integer(signed(LPQBus)) > 32767) else
                 LPQBus(15 downto 0);
end architecture;


La máquina de estados espera hasta que la entrada "EnableIn" se ponga a "1", dicho evento es la señal que indica al filtro que debe realizar una iteración (i.e. calcular la siguiente muestra a partir de la entrada "SampleIn").



Todo el código está disponible en la sección soft.

[ 1 comentario ] ( 477 visualizaciones )   |  [ 0 trackbacks ]   |  enlace permanente
  |    |    |    |   ( 3 / 1693 )

| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Siguiente> >>