Entiende el código VHDL

1

He estado aprendiendo la codificación VHDL durante las últimas dos semanas para poder entender el código VHDL de lo que estaré trabajando. En otras palabras, todavía soy un principiante y necesito ayuda para entender algunos códigos que he encontrado.

when WAIT_ST =>         
CAN_CS              <= '0';         
can_init_start      <= '0';         
can_read_start      <= '0';         
CPU_CAN_WE          <= '0';         
mux_sel             <= "00";        
CPU_CAN_DATA        <= X"00";       
CPU_CAN_ADDR        <= X"01";       
ID_START            <= '0';         
INST                <= Frame_Type & Sub_Frame_Type;         
WAIT_COUNT_N        <= WAIT_COUNT + 1;      

if WAIT_COUNT = WAIT_FOR then           
   state_n  <= IDLE_ST;         
else            
   state_n  <= WAIT_ST;         
end if;

Y hay miles de declaraciones de When después de esta, casi similares.

Se necesita ayuda para comprender lo que está sucediendo en este ciclo, ¡gracias!

    
pregunta Eman alawadhi

2 respuestas

3

Significa que alguien no ha pensado mucho en cómo escribir sus máquinas de estado.

Esto no es un bucle, es un estado en una máquina de estados. Es de suponer que hay muchos estados que hacen cosas similares, con pequeñas variaciones, y es demasiado fácil perder de vista los detalles.

En cada ciclo de reloj, una máquina de estado ejecuta las acciones predeterminadas (cosas que haces en cada ciclo) y el código para el estado específico en el que se encuentra ahora. Por lo tanto, debe realizar un seguimiento de su estado y, si es necesario, cambiar a otro estado.

Así que una máquina de estado de un solo proceso típica se parece a

process(reset,clk) is -- and nothing else in the sensitivity list!
begin
   if reset = '1' then
      state <= Idle;
   elsif rising_edge(clk) then
      -- default actions
      ID_START  <= '0';    
      if Counter /= 0 then 
         Counter <= Counter - 1; 
      end if;
      -- state machine proper
      case state is
      when WAIT_ST => 
         -- state actions
      ...
      when others => 
         state <= Idle;
      end case;
   end if;
end process;

Esperamos que puedas encontrar esta estructura básica y cada uno de sus componentes en la tuya. (También hay máquinas de estado de 2 procesos y 3 procesos que traen problemas adicionales y ninguna ventaja real, los ignoraré).

Varias herramientas para mejorar una máquina de estado:

(1) Usa acciones predeterminadas.

Aquí, asumo que su señal ID_START se establece en '0' en todos los estados excepto uno, probablemente etiquetado como START_ST . Si ese es el caso, puedes agregar una acción predeterminada, ID_START <= '0'; como lo he hecho anteriormente. Luego, puede anular esta acción en uno (o pocos) estado (s) que necesitan cualquier otro valor, como

   when START_ST =>
      ID_START  <= '1'; 

y puede eliminar todas las otras asignaciones de ID_START <= '0'; en otros estados. Un poco menos de desorden ...

Sin embargo, si ID_START se establece en un estado, se borra en otro y no se asigna (mantiene su valor) en todos los demás estados, entonces no puede hacer esto. Así que usa el juicio, como siempre, al refactorizar.

(2) Use constantes con nombre en lugar de números mágicos.   ¿Qué registro es la dirección X "01" y qué le hace X "00"?

CPU_CAN_DATA        <= NOP;       
CPU_CAN_ADDR        <= Control_Reg;  

(3) Use registros para agrupar señales asociadas.

(Puede ponerlos en un paquete y reutilizarlos donde lo necesite)

type CPU_CAN is record
 WE : std_logic;
 DATA : std_logic_vector(7 downto 0);
 ADDR : unsigned(7 downto 0)
end record;

signal CPU_CAN_BUS : CPU_CAN;

Las direcciones NB suelen ser números naturales, por lo tanto, sin firmar. Los datos pueden ser cualquier cosa, no necesariamente un número, por lo tanto, std_logic_vector.

Y ahora puede asignar señales relacionadas en una sola declaración:

CPU_CAN_BUS <= ( WE=> '0', ADDR => Ctrl_Reg, DATA => NOP);   

Probablemente puedas hacer lo mismo con CAN_CS, can_init_start, can_read_start.

(4) Use procedimientos y funciones para simplificar operaciones repetitivas.

(5) Use un contador de retardo programable. Si WAIT_ST es el único estado que necesita un contador de retardo, el enfoque actual es correcto. Sin embargo, si necesita varios retrasos, pueden compartir un contador, que cuenta como una de las acciones predeterminadas anteriores. Como cuenta atrás, solo necesita compararlo con 0 (ahorrando un poco de hardware) y está programado cuando salta a un estado como WAIT_ST

   if something then     
      Counter  <= WAIT_FOR;
      state_n  <= WAIT_ST;         
   end if;

(6) detente antes de hacer las cosas más complicadas! es decir, si mux_sel no se ajusta lógicamente a ninguno de los anteriores, déjelo en paz ...

El resultado final de estos cambios es algo así como

when WAIT_ST =>         
    -- CAN_CS, can_init_start, can_read_start, ID_START are defaults
    mux_sel     <= "00";        
    CPU_CAN_BUS <= ( WE=> '0', ADDR => Ctrl_Reg, DATA => NOP);       
    INST        <= Frame_Type & Sub_Frame_Type;         

    if Counter = 0 then then           
       state_n  <= IDLE_ST;         
    end if;   -- else we stay in WAIT_ST

¿Mejor?

    
respondido por el Brian Drummond
1

Supongo que el código publicado es parte de una implementación de máquina de estado de 2 procesos porque WAIT_COUNTER_N y STATE_N parecen ser los siguientes valores de los registros WAIT_COUNT y STATE respectivamente. Por lo tanto, debe haber un proceso cronometrado que describa los registros similares a este:

process(clock)
begin
  if rising_edge(clock) then
    WAIT_COUNT <= WAIT_COUNT_N;
    STATE <= STATE_N;
  end if;
end process; 

El proceso anterior puede contener más lógica de reinicio.

Luego, el segundo proceso es la lógica combinatoria que define los valores de salida y los valores de registro siguientes en función del estado actual y las entradas de la máquina de estados. El proceso comienza así:

process (STATE, WAIT_COUNT)
begin
-- default signal assignments if any
-- .. 
case STATE is

Después de esto, un bloque WHEN sigue describiendo el comportamiento para cada estado, por ejemplo. como en tu código:

when WAIT_ST =>         
  CAN_CS              <= '0';         
  can_init_start      <= '0';         
  can_read_start      <= '0';         
  CPU_CAN_WE          <= '0';         
  mux_sel             <= "00";        
  CPU_CAN_DATA        <= X"00";       
  CPU_CAN_ADDR        <= X"01";       
  ID_START            <= '0';         
  INST                <= Frame_Type &  Sub_Frame_Type;         
  WAIT_COUNT_N        <= WAIT_COUNT + 1;      

  if WAIT_COUNT = WAIT_FOR then      
     state_n  <= IDLE_ST;         
  else            
     state_n  <= WAIT_ST;         
  end if;

Por ejemplo, el bloque anterior contiene asignaciones a salidas como esta:

  CAN_CS              <= '0';

El valor en el lado derecho se asigna inmediatamente después de que STATE haya cambiado a WAIT_ST en el flanco ascendente del reloj.

Además, el contador de espera se incrementa asignando inmediatamente el valor respectivo a WAIT_COUNT_N aquí:

  WAIT_COUNT_N        <= WAIT_COUNT + 1;

El nuevo valor se almacena en el registro WAIT_COUNT en el flanco ascendente del reloj por el proceso cronometrado.

Por lo tanto, el if final verifica el valor del contador actual (antiguo) y selecciona el estado de seguimiento deseado. De nuevo, el siguiente estado se asigna inmediatamente a la señal state_n primero. El valor se almacena en el registro STATE en el flanco ascendente del reloj mediante el proceso cronometrado.

    
respondido por el Martin Zabel

Lea otras preguntas en las etiquetas