Error en mi implementación de SPI (VHDL)

Soy nuevo en la programación de VHDL/FPGA y experimenté un comportamiento extraño en mi implementación de SPI-Slave. Lo que hice:

  1. SPI-Master: estoy usando un Arduino (ATMega328p MCU) como SPI-Master. Para la depuración, envía un valor de contador de 8 bits al SPI-Slave de las FPGA una vez por segundo. Baja SS, envía 1 byte (8 bits, MSB primero, a una velocidad de reloj SPI de 2 MHz), vuelve a subir SS, incrementa el contador y espera un segundo.
  2. Eché un vistazo a las señales con mi DSO, que se ven bien (excepto por el exceso). Es capaz de decodificar el valor y no se queja de ningún problema de protocolo. Mi DSO tiene solo 2 entradas (CH1 = SCK, CH2 = MOSI en la imagen de abajo). También verifiqué si SS se baja/sube correctamente, que es el caso. Usé comandos lentos digitalWrite(SS_pin, LOW);y digitalWrite(SS_pin, LOW);, por lo que hay una gran brecha entre el primer flanco ascendente de SCK y el flanco descendente de SS_pin; y una larga brecha después del último flanco descendente SCK del byte antes de que el SS_pin se levante nuevamente.
  3. En mi código VHDL utilizo algunos FIFO como sincronizadores, ya que la lógica SPI pertenece a un dominio de reloj de 100 MHz (oscilador integrado de 50 MHz => multiplicador de reloj 2x a través de una IP PLL).

El problema:

Para fines de prueba, quería usar los 3 LED (BAJO = encendido) en la placa FPGA para mostrar los 3 LSB de los 8 bits recibidos. Dado que los datos se envían MSB primero, estos deberían ser los últimos 3 bits recibidos (al comenzar en el índice = 0: bits 5, 6 y 7).

Ejecuto mi siguiente código comprobando bit_index = 5/6/7 y asignando las salidas a los pines de los LED (negados, como BAJO = la luz está ENCENDIDA). Pero esto provocó que el último LED ( leds(0)) quedara estático (como no lo restablezco, su valor es aleatorio; entonces veo algo como: 00? => 01? => 10? => 11? => 00?) . Parece que el bit_index nunca llega a 7.

Cuando lo cambio (como se muestra a continuación) para verificar bit_index = 4/5/6, funciona como se esperaba (000 => 001 => 010 => 011 => 100 => 101 => 110 => 111 => 000 . ..).

Entonces, ¿cuál es el problema aquí? Sospecho que tiene que ver con el uso variablesde tipo entero para bit_index. Sé que signalspermanecen en su valor actual durante todo el ciclo del reloj cuando leen, incluso se les asignó un valor diferente y actualizan su valor en el próximo ciclo. Sin embargo, hasta donde yo sé, variablesde alguna manera se comportan como variables en lenguajes de programación que no son HDL y cambian su valor de inmediato. Como descubrí: por alguna razón mágica, son sintetizables sin quejas del compilador y no solo funcionan para simulaciones ... Así que lo usé. Ahora me pregunto si esto suele funcionar o si normalmente conduce a algunos errores graves, incluso la herramienta de sintetizador/compilador de VHDL no se queja de ello.

Por favor, eche un vistazo al código y hágame saber cuál es el problema y lo más importante: ¿POR QUÉ?

Captura de pantalla DSO:

Captura de pantalla DSO: SPI, valor de 8 bits

Código VHDL:

   library ieee;
   use ieee.std_logic_1164.all;
   use ieee.std_logic_arith.all;

   library work;
   use work.MyPackage.all;

   entity SPI is
          port(
                 clk  : in  std_logic;
                 SCK  : in  std_logic;
                 SS   : in  std_logic;
                 MOSI : in  std_logic;
                 MISO : out std_logic;
                 -- LEDS for debugging
                 leds : out std_logic_vector(2 downto 0) := "111";
                 -- DATA to transfer
                 data : in  SAMPLE_ARRAY_T
          );
   end entity;

   architecture RTL of SPI is

          -- synchronizer FIFOs
          signal SS_FIFO   : std_logic_vector(2 downto 0);
          signal SCK_FIFO  : std_logic_vector(2 downto 0);
          signal MOSI_FIFO : std_logic_vector(1 downto 0);

          -- output shift register
          -- holding remaining bits of current 16-bit word to send
          signal output_ShiftReg : std_logic_vector(15 downto 0);

          -- current bit / word to be sent at rising_edge of SCK
          shared variable bit_index  : integer range 0 to 15 := 0;
          shared variable word_index : integer range 0 to 7  := 0;

   begin
          process(clk) is
          begin
                 if rising_edge(clk) then
                        -- shift in new data into
                        -- synchronization FIFOs
                        SS_FIFO   <= SS_FIFO(1 downto 0) & SS;
                        SCK_FIFO  <= SCK_FIFO(1 downto 0) & SCK;
                        MOSI_FIFO <= MOSI_FIFO(0) & MOSI;

                        -- on falling_edge(SS): reset offsets
                        -- load first data word to output_ShiftReg
                        if SS_FIFO(2 downto 1) = "10" then
                               bit_index       := 0;
                               word_index      := 0;
                               output_ShiftReg <= data(word_index);
                        end if;

                        -- if SS = LOW and rising_edge of SCK
                        -- transfer data ...
                        if SS_FIFO(1) = '0' and SCK_FIFO(2 downto 1) = "01" then

                               -- display 3 LSB bits of incoming 8 bit data
                               -- via debug LEDs (data is sent MSB first)
                               case bit_index is
                                      when 4 =>           -- WHY?? this should be 5...
                                             leds(2) <= not MOSI_FIFO(1);
                                      when 5 =>           -- WHY?? this should be 6...
                                             leds(1) <= not MOSI_FIFO(1);
                                      when 6 =>           -- WHY?? this should be 7...
                                             leds(0) <= not MOSI_FIFO(1);
                                      when others =>
                               end case;

                               -- return input data
                               MISO <= output_ShiftReg(15);

                               -- increment bit/sample index
                               bit_index := bit_index + 1;
                               if bit_index = 0 then
                                      -- A bit_index overflow happened:
                                      -- load next data word into sample
                                      word_index      := word_index + 1;
                                      output_ShiftReg <= data(word_index);
                               else
                                      -- shift 1 bit out of output_ShiftReg
                                      output_ShiftReg <= output_ShiftReg(14 downto 0) & '0';
                               end if;
                        end if;
                 end if;
          end process;
   end architecture;

Maestro SPI (MCU, Arduino / C++):

   #include <SPI.h>

   int SS_pin = 10;

   SPISettings settingsA(2000000, MSBFIRST, SPI_MODE0);

   void setup() {
     Serial.begin(115200);
     // initialize SPI:
     pinMode(SS_pin, OUTPUT);
     digitalWrite(SS_pin, HIGH);
     SPI.begin();
   }

   uint8_t counter = 0;

   void loop() {
     uint8_t retval;
     digitalWrite(SS_pin, LOW);
     SPI.beginTransaction(settingsA);
     retval = SPI.transfer(counter);
     SPI.endTransaction();
     digitalWrite(SS_pin, HIGH);
     Serial.print("SEND: ");
     Serial.print(counter);
     Serial.print(", GOT: ");
     Serial.println(retval);
     ++counter;
     delay(1000);
   }

Información adicional:

  • FPGA: Altera Cyclone II (EP2C5T144E, placa Mini-FPGA)
  • Quartus II WebEdition, 64 bits, 13.0.1sp1 (compilación 232)

EDITAR #1:

Como se sugirió, escribí un banco de pruebas para él y lo simulé a través de ModelSIM. Aquí está el código y los resultados de la simulación. Tenga en cuenta que utilicé los valores 5, 6 y 7 para comparar bit_index (no los valores alternativos anteriores, que necesito para mi FPGA):

            case bit_index is
                when 5 =>           -- using 5, as I should here 
                    leds(2) <= not MOSI_FIFO(1);
                when 6 =>           -- using 6, as I should here 
                    leds(1) <= not MOSI_FIFO(1);
                when 7 =>           -- using 7, as I should here
                    leds(0) <= not MOSI_FIFO(1);
                when others =>
            end case;

SPITest.vhd (código del banco de pruebas):

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

   library work;
   use work.MyPackage.all;

   entity SPITest is
   end entity;

   architecture SIM of SPITest is

          component SPI is
                 port(
                        clk  : in  std_logic;
                        SCK  : in  std_logic;
                        SS   : in  std_logic;
                        MOSI : in  std_logic;
                        MISO : out std_logic;
                        -- LEDS for debugging
                        leds : out std_logic_vector(2 downto 0) := "111";
                        -- DATA to transfer
                        data : in  SAMPLE_ARRAY_T
                 );
          end component;

          signal spi_CLK  : std_logic := '0';
          signal spi_SS   : std_logic := '1';
          signal spi_MOSI : std_logic := '1';
          signal spi_MISO : std_logic := '0';

          signal mydata : SAMPLE_ARRAY_T := (others => (others => '0'));

          signal leds : std_logic_vector(2 downto 0) := "111";

          signal clk : std_logic := '0';

          signal counter_slv : std_logic_vector(7 downto 0) := (others => '0');

          constant period : time := 10 ns;

          function to_string(a : std_logic_vector) return string is
                 variable b    : string(1 to a'length) := (others => NUL);
                 variable stri : integer               := 1;
          begin
                 for i in a'range loop
                        b(stri) := std_logic'image(a((i)))(2);
                        stri    := stri + 1;
                 end loop;
                 return b;
          end function;

   begin
          dut : spi port map(clk => clk, SCK => spi_CLK, SS => spi_SS, MISO => spi_MISO, MOSI => spi_MOSI, leds => leds, data => mydata);

          clk <= not clk after period / 2;

          process is
          begin
                 -- initial state
                 spi_CLK  <= '0';
                 spi_SS   <= '1';
                 spi_MOSI <= '1';
                 report "INIT STATE.";

                 wait for 100 ns;

                 -- send 8 times '1'
                 for counter in 0 to 255 loop

                        report "PULLING spi_SS to LOW...";
                        spi_SS <= '0';

                        -- 2000 ns should be enough
                        wait for 2000 ns;

                        counter_slv <= std_logic_vector(to_unsigned(counter, counter_slv'length));
                        for bit in 7 downto 0 loop
                               spi_MOSI <= counter_slv(bit);
                               wait for 250 ns;
                               spi_CLK  <= '1';
                               wait for 250 ns;
                               spi_CLK  <= '0';
                        end loop;

                        wait for 2000 ns;
                        report "PULLING spi_SS back to HIGH...";
                        spi_SS <= '1';

                        wait for 2000 ns;
                 end loop;

                 report "LEDs are: " & to_string(leds);
                 wait;

          end process;

   end architecture;

Por si quieres probarlo tú mismo. También necesitará MyPackage.vhl (que define SAMPLE_ARRAY_T):

   library ieee;
   use ieee.std_logic_1164.all;

   package MyPackage is

          type SAMPLE_ARRAY_T is array (0 to 7) of std_logic_vector(15 downto 0);

          component SPI is
                 port(
                        clk  : in  std_logic;
                        SCK  : in  std_logic;
                        SS   : in  std_logic;
                        MOSI : in  std_logic;
                        MISO : out std_logic;
                        -- LEDS for debugging
                        leds : out std_logic_vector(2 downto 0) := "111";
                        -- DATA to transfer
                        data : in  SAMPLE_ARRAY_T
                 );
          end component;

   end package;

Resultado:

NOTA: La simulación produce resultados correctos. Los valores de los "leds" están invertidos (según lo previsto, ya que los leds están conectados a VCC y una señal baja tirada enciende el LED).

Resultados de la simulación

Editar #2:

Como cambié el código VHDL para la simulación revalidé el comportamiento del hardware FPGA. No cambió: la simulación funciona. La versión sintetizada falla. Entonces parece ser un problema de cómo se sintetiza el VHDL.

Eliminé el código relacionado con la salida (MISO) y solo conservé el código que controla la salida del LED (ya que es mucho más compacto) y dejé que Quartus II generara un esquema. Esto es lo que parece:

ingrese la descripción de la imagen aquí

Parece que el sintetizador/optimizador produjo algunas locuras. Especialmente el conteo de bit_index me parece muy confuso. La salida SS_FIFO controla ambos multiplexores etiquetados como "MUX21" que restablece bit_index. La salida SCK_FIFO se compara con "01" (en "Equal0") y se combina con "no SS_FIFO". Esto controla la habilitación de escritura para el registro LED y garantiza que el registro no se escriba, cuando SS sea ALTO o no se detecte un flanco ascendente en SCK. La salida MOSI_FIFO se niega y se alimenta a las entradas de datos de los multiplexores "MUX0", "MUX1" y "MUX2". Combinados con la retroalimentación de datos de los leds[2..0]~reg0, generan la nueva entrada para el registro leds[2..0]~reg0. Debido a sus entradas seleccionadas, uno de los bits de leds [] puede ser reemplazado por la salida MOSI_FIFO mientras que los otros se seleccionan para permanecer como están. "Add0" se usa para incrementar "bit_index" en uno. Si se deja, la parte "MUX21" (bit_index~[7..4]) puede seleccionar entre el valor actual de la cadena bit_index (después del segundo mutex) y este valor incrementado en uno. Este último se elige en un flanco ascendente detectado de SCK. La parte derecha "MUX21" (bit_index~[3..0]) es para restablecer el bit_index a 0, si SS se eleva. No puedo verificar el cableado de entrada de "MUX0", "MUX1" y "MUX2", pero el resto me parece correcto. El registro bit_index se restablece a 0 siempre que SS sea ALTO. Cuando SS se tira BAJO, todavía permanece 0, porque el multiplexor izquierdo elige mantener el valor actual hasta que se detecta un flanco ascendente en "SCK". Si esto sucede, el registro bit_index aún emite 0 y debería hacer que las líneas de datos correctas de los multiplexores "MUX0", "MUX1" y "MUX2" se alimenten en el registro de LED. Finalmente, después de un ciclo de reloj del reloj FPGA "clk", el valor bit_index se incrementa en uno. Por lo tanto, bit_index se usa antes del incremento para seleccionar las entradas del multiplexor y se incrementa en cada flanco ascendente detectado de SCK. Todo esto parece totalmente correcto. después de un ciclo de reloj del reloj FPGA "clk", el valor bit_index se incrementa en uno. Por lo tanto, bit_index se usa antes del incremento para seleccionar las entradas del multiplexor y se incrementa en cada flanco ascendente detectado de SCK. Todo esto parece totalmente correcto. después de un ciclo de reloj del reloj FPGA "clk", el valor bit_index se incrementa en uno. Por lo tanto, bit_index se usa antes del incremento para seleccionar las entradas del multiplexor y se incrementa en cada flanco ascendente detectado de SCK. Todo esto parece totalmente correcto.

Aquí hay un pequeño video de lo que sucede. Como puede ver, el primer LED (que debería mostrar el lsb) permanece encendido. Mientras que el LED central muestra el LSB y el otro LED muestra el estado del segundo bit. Las asignaciones de pines no son el problema (ya que cambiar el código VHDL como se mencionó anteriormente hace que todos los LED funcionen como se esperaba).

¿Algunas ideas?

Debe escribir un banco de pruebas que replique las señales correctas de su procesador y ver cómo reacciona el VHDL. Utilice un simulador que le permita observar señales internas.
@ElliotAlderson: he hecho una simulación ahora. Sin embargo, simulé solo una brecha de 2 us entre las transacciones SPI (en lugar de los 1 en la configuración real). Los resultados de la simulación se comportan como me gustaría que lo hiciera el hardware (al comparar bit_index con 5, 6 y 7 y copiar MOSI a las salidas LED). Como realicé ligeras modificaciones en la fuente VHDL (eliminé algunos otros componentes), tendré que verificar si el hardware aún se comporta de manera diferente al usar esta versión reducida. Lo haré tan pronto como tenga el hardware a mano y le haré saber el resultado.
Se agregó el código de simulación/resultados a la pregunta anterior.
Comprobado el comportamiento del hardware. Desafortunadamente, todavía es diferente de la simulación RTL. Ver Editar # 2 en mi publicación. Se agregó el Esquema generado por el "Visor RTL" (Herramientas > Visores Netlist). También se vincula una vista, que muestra el comportamiento incorrecto de la FPGA.

Respuestas (1)

Para empezar, el hecho de que la simulación funcione y el código sintetizado no, no significa necesariamente que el sintetizador haya hecho algo mal. Porque generalmente él/ella hace exactamente lo que se supone que debe hacer, sintetizar SU código. Hay muchos aspectos en el mundo real que se descuidan en la mayoría de las simulaciones.

  • señales entrantes asíncronas
  • señales externas que en realidad no se ven como las esperas en tu simulación
  • faltan FF de entrada

Pero en tu caso probablemente tengas razón. Tiendo a ir sin variables siempre que sea posible, y la mayoría de las veces lo es, porque son peligrosas. Para ser honesto, ni siquiera sabía que shared variableexistía algo así en VHDL. Según esta fuente http://vhdlguru.blogspot.com/2010/03/variables-and-shared-variables.html , se pueden usar para simulación pero no se pueden sintetizar. Entonces, comenzaría reemplazando las variables con señales (haría las adaptaciones de código requeridas) y probaría nuevamente.

Y sugeriría agregar una señal de reinicio para comenzar siempre en un estado definido.

Parece razonable. Reemplazaré las variables con señales y volveré a probar. Si todavía no funciona, esto podría deberse a algunos problemas de metaestabilidad (señales entrantes asíncronas como dijiste). Si esto sucede, intentaré sintetizar el código de simulación como un maestro SPI separado, que tenga pines diferentes pero el mismo dominio de reloj. Conectándolo externo al FPGA (para que el optimizador no lo sepa). Por lo tanto, el SPI-master debería estar sincronizado si los cables son lo suficientemente cortos y de la misma longitud (y tal vez disminuya la velocidad del reloj a algunos kHz).
Por cierto: Shared variablesson válidas en el ámbito de la arquitectura (y se pueden compartir entre varios procesos) como señales internas, mientras que las variables habituales se limitan al ámbito del proceso/función/procedimiento. Sin embargo, sospecho que usarlos en el código para el hardware son peligrosos como dices.
Para aclarar: según el enlace en la respuesta, a variablees sintetizable, mientras que shared variablesse dice que no es sintetizable. Así que parece peligroso usarlo shared variablessolo.
Por supuesto, las variables son sintetizables, pero aún necesitan un cuidado especial, solo digo que use señales siempre que sea posible.
@SDwarfs Las variables compartidas que usan un tipo ordinario (cualquier tipo que no sea un tipo protegido) fueron una función temporal introducida en 1076 (VHDL)-93 y quedaron obsoletas y eliminadas en 1076-2002. Es sorprendente verlos todavía apoyados por herramientas.