Bus VHDL SPI bastante simple que funciona en simulación pero no en FPGA (Lattice MACHOX3LF-6900C FPGA y software Lattice Diamond)

0

Soy nuevo en la programación de VHDL y FPGA, y aunque conozco un buen número de problemas que pueden existir entre la simulación y la síntesis, este problema en particular me dejó perplejo.

Mi diseño es bastante simple:

  • El bus SPI acepta un byte de datos del modelo de arriba, que luego se desplaza en los bordes crecientes del reloj.

  • Al restablecerse, todas las señales se configuran en valores iniciales. Activo esta entrada conectando un cable entre el pin de reinicio y la conexión a tierra en mi FPGA.

  • La interfaz SPI luego ingresa a una etapa de "espera" donde espera una señal del modelo anterior, que siempre es alta, por lo que sale de esta etapa de espera dentro de un ciclo de reloj.

  • Luego entra en una etapa de "escritura" en la que, en el primer flanco ascendente de la entrada del reloj, escribe el siguiente bit desde su entrada de datos a su salida de datos, y escribe la salida del reloj a baja. En el siguiente borde del reloj, escribe que la salida del reloj es alta, desplazando los datos a cualquier dispositivo que lo esté esperando conectado a los pines de salida del FPGA, e incrementa el contador que mantiene un registro del bit de entrada del byte de entrada que está configurando siguiente ciclo de entrada de reloj.

  • Este patrón se repite, hasta que el reloj de salida se establece en alto, cambiando el bit de entrada final, e incrementa el contador de selección de bit de entrada a 8. El siguiente flanco ascendente del reloj de entrada el dispositivo restablece todas las variables y cambia al modo "en espera", en lugar de intentar generar el bit de entrada # 8 del byte de entrada, que no existe, ya que la matriz de bytes de entrada es un bit_vector con índices etiquetados del 0 al 7.

  • Como esta versión mantiene constante el byte de entrada así como constante el bit de activación de entrada, el dispositivo repite y repite su salida de forma indefinida.

Todo esto funciona en la simulación como se espera, y casi funciona en realidad como se espera, excepto por el cambio final de los datos. Cuando subo el programa al FPGA, parece que se omite el paso de escribir la salida del reloj en el octavo bit, y en su lugar, se salta directamente a la fase de reinicio y se reinicia en la fase de "espera" el siguiente ciclo de entrada de reloj. Establece bien la salida de datos, pero luego, en lugar de configurar la salida del reloj alta por última vez antes de reiniciar, simplemente salta para reiniciar.

Creo que esto tiene que ver con el hecho de que incrementa el contador del selector de bits de entrada a 8 al mismo tiempo que escribe el reloj de salida alto. En lugar de esperar hasta el próximo ciclo de reloj de entrada para darse cuenta de que este valor es ahora 8, y que debería restablecerse, por alguna razón, ve el cambio a 8 de inmediato y decide restablecerse ya que el contador ahora está en 8.

Una nota final, en mi código hay un "bit de estado" en la arquitectura de la interfaz SPI que no está siendo utilizado por la arquitectura anterior en este diseño, sin embargo, en diseños futuros será necesario incluirlo. Yo simplificaría este código de depuración eliminándolo; sin embargo, cuando se elimina de todos los niveles de implementación, el error que enumeré anteriormente no se produce, y el FPGA se comporta tan perfectamente como la simulación. No tengo idea de por qué esto es.

Aquí está mi código:

El nivel más bajo en el diseño, la interfaz SPI:

entity SPI is 
    port (data_out, clk_out, status_out : out bit; data_in : in bit_vector(0 to 7); CLOCK, begin_in, RESET: in bit);
end entity SPI;

architecture SPIArch of SPI is 
    type SPIState is (waiting, writing);
    signal current_state : SPIState := waiting; 

    subtype byteCount is integer range 0 to 8;  
    signal current_bit : integer := 0;  

    signal data_set : bit := '0';  

begin  
    shiftOut : process (CLOCK, RESET)   
    begin 
        if (RESET = '0') then
            current_bit <= 0;  
            data_set <= '0'; 
            clk_out <= '0';  
            data_out  <= '0'; 
            status_out <= '1';
            current_state <= waiting;   
        elsif (CLOCK = '1' and CLOCK'event) then  
            case current_state is
                when waiting =>
                    if (begin_in = '1') then
                        status_out <= '0';
                        current_state <= writing;
                    end if;
                when writing =>
                    if (current_bit /= 8) then
                        if (data_set = '1') then 
                            clk_out <= '1';
                            data_set <= '0';
                            current_bit <= current_bit + 1;
                        end if;
                        if (data_set = '0') then
                            data_out <= data_in(current_bit);
                            clk_out <= '0';
                            data_set <= '1'; 
                        end if; 
                    else 
                        data_out <= '0';
                        clk_out <= '0'; 
                        current_bit <= 0;  
                        status_out <= '1';
                        data_set <= '0';
                        current_state <= waiting; 
                    end if;
            end case; 
        end if;
    end process shiftOut;
end architecture SPIArch;

A continuación, el nivel por encima de la interfaz SPI, el "controlador" para alimentar a la interfaz SPI el valor de prueba internamente (tenga en cuenta que se usó el divisor de reloj grande para poder ver lo que estaba pasando en las salidas de mi FPGA mediante LED) :

entity SPIDrive is
    port (data_out, clk_out, status_out : out bit; CLOCK, RESET: in bit);
end entity SPIDrive;

architecture SPIDriveArch of SPIDrive is 

    signal SPI_clk : bit;
    signal SPI_counter : integer;
    signal SPI_data : bit_vector(0 to 7);
    signal SPI_begin : bit;

begin
    testDevice : entity work.SPI(SPIArch)
        port map (data_out, clk_out, status_out, SPI_data, SPI_clk, SPI_begin, RESET);

    outputTest : process (CLOCK, RESET) 
    begin
        if (RESET = '0') then
            SPI_clk <= '0';
            SPI_counter <= 0;
            SPI_data <= B"10110011";
            SPI_begin <= '1';
        elsif (CLOCK = '1' and CLOCK'event) then
            SPI_counter <= SPI_counter + 1;
            if (SPI_counter = 5000000) then
                SPI_counter <= 0;
                SPI_clk <= not SPI_clk;
            end if;
        end if;
    end process outputTest;
end architecture SPIDriveArch;

El nivel final, utilizado solo como banco de pruebas para la simulación, y simplemente genera la fuente del reloj y el reinicio, que normalmente proviene de un oscilador integrado y un pin de entrada:

entity testbench1 is
end entity testbench1;

architecture testbench1Arch of testbench1 is 
    signal data_out, clk_out, status_out, CLOCK, RESET: bit;
begin
    troll : entity work.SPIDrive(SPIDriveArch)
        port map (data_out, clk_out, status_out, CLOCK, RESET);

    process 
    begin
        RESET <= '0';
        CLOCK <= '0';
        wait for 1 ns;
        RESET <= '1';
        while (true) loop
            wait for 1 ns;
            CLOCK <= '1';
            wait for 1 ns;
            CLOCK <= '0';
        end loop;
    end process;
end architecture testbench1Arch;

Aquí está la salida de mi simulación (Nótese que cambié el divisor del reloj en el código del "controlador SPI" a 10 en lugar de 5 millones para facilitar la visualización en la simulación, y que disparo un reinicio de 1 ns en la simulación ( como se ve en mi código de banco de pruebas) (aunque es difícil de ver en la imagen):

Paraevitarcualquierconfusiónencuantoacuálfuemierror,aquíhayunaversióneditada(pobremente)delaimagendesimulación,quemuestrabásicamenteloqueveoenlavidarealprovenientedemiFPGA:

Unaadiciónfinalprobablementeinnecesaria,recibounagrancantidaddeadvertenciasaparentementeignorablesdurantelasíntesis(porejemplo,unagrancantidaddepestillosenmientradadedatos,locualtienesentidoyaquenuncacambioelvalordeentradadedatos)sinembargo,tambiénobtengotresqueindican"sin restricciones de diseño" en mis archivos. Supongo que estos son para algún tipo de simulación y no son la causa de mis problemas, sin embargo, parecen ser notables, así que los menciono.

Como se indica en el título, estoy usando Lattice Diamond para la programación y simulación (aunque la simulación abre Active-HDL) y el Lattice MACHXOLF-6900C FPGA

    
pregunta Kevin Brant

1 respuesta

2

Respondiendo a mi propia pregunta aquí, ya que resulta que NO se supone que uses divisores de reloj en VHDL. Supuse falsamente que esto estaba bien siempre y cuando tratara a cada reloj como un reloj, sin embargo, resulta que hay una única ruta específica de hardware que toma el reloj, y un segundo reloj procedente de un divisor simplemente no tiene la misma pequeña -Detrasar las propiedades de la línea de reloj personalizada.

Obtuve todo esto de esta publicación del foro ( edaboard.com/thread283723.html ), en la que recomiendan reemplazar los separadores de reloj Con habilitadores de reloj. Entonces, en lugar de tener toda su lógica activada por un borde del reloj, tiene un borde del reloj que dispara una mirada a un condicional, que busca ver si "habilitar el reloj" es alto y si es alto, entonces ejecuta el código. Mientras mantengas el reloj alto para solo un ciclo de reloj, entonces actúa de la misma manera. Básicamente, lo conviertes en alto cada 10 millones de ciclos y en 10000001, si quieres un reloj dividido por 10 millones, por ejemplo.

Quieres evitar ir

if(clk = '1' and clock'event and clock_enable) 

ya que estoy bastante seguro de que se supone que debes evitar las operaciones lógicas con tu reloj (soy nuevo en VHDL, así que nunca lo experimenté, solo lo leí en otras publicaciones del foro). En su lugar, vas

if (clk = '1' and clock'event) then
    if (clock_enable = '1') then
    ...
    
respondido por el Kevin Brant

Lea otras preguntas en las etiquetas