La forma exacta en que lo implementas depende del rendimiento que necesites, lo que determina la profundidad con la que lo canalizas.
Voy a suponer que (do something)
y (do something else)
tardan aproximadamente la comparación en x>=x_ref
, y que el sistema está lo suficientemente lento para que haya suficiente tiempo adicional para propagar señales a través de un multiplexor antes del siguiente reloj. (También asumo que todo x, x_ref,
y las entradas a do_something_*
están sincronizadas con la señal del reloj, y que tiene un conocimiento básico de semántica de asignación de señal VHDL )
Luego la implementación inicial
process(clk) is
begin
if rising_edge(clk) then
if x>=x_ref then
Result <= (do something);
else
Result <= (do something else)
end if;
end if;
end process;
es equivalente a lo siguiente, todos evaluados en paralelo (es decir, fuera de un proceso)
Test <= x >= xref; -- Test is a boolean signal
A <= (do something);
B <= (do something else);
A_or_B <= A when Test else B;
Result <= A_or_B when rising_edge(clk);
En otras palabras, ambos brazos del MI, así como la comparación, se evalúan en paralelo. Luego, sus salidas A,B
se desplazan a través del selector (multiplexor) a A_or_B
, que funciona en paralelo a lo anterior, pero cuya salida no es válida hasta que todos los valores de A, B y Test sean estables.
También funciona en paralelo un registro temporizado, que espera hasta el siguiente flanco del reloj y engancha su entrada en Result
. Esto es solo una taquigrafía para el proceso normal:
process(clk) is
begin
if rising_edge(clk) then
Result <= A_or_B;
end if;
end process;
Si ha seguido esta explicación, acaba de ver que se ejecuta una instrucción IF completa en un solo ciclo de reloj.
Los bloques básicos reales pueden parecerse un poco al lenguaje ensamblador compilado a partir del código anterior, pero recuerde que cada uno es un pequeño bloque de hardware, que opera en paralelo, en lugar de instrucciones de ensamblador ejecutadas secuencialmente.
La ventaja de la forma IF
en un proceso cronometrado es que hace que la operación de alto nivel sea relativamente clara y fácil de seguir. ¡Si puedes seguir la lógica a través de todas esas tareas paralelas más fácilmente, entonces tu mente funciona de manera diferente a la mía!
Ambas formas deben sintetizarse en el mismo hardware, asumiendo que la herramienta de síntesis reconoce la sintaxis when rising_edge(clk)
. Si no es así, simplemente use el proceso cronometrado equivalente.
Y siendo todo lo demás igual, se prefiere la forma de nivel superior, que es más fácil de leer, comprender y mantener.
Ahora supongamos que no es lo suficientemente rápido, pero (do something)
, (do something else)
y x>=x_ref
toman menos de un ciclo de reloj. Podemos canalizar esto más profundamente al realizar estas operaciones en un solo ciclo (en paralelo) y luego realizar la selección (el if
real) en un segundo ciclo.
Test <= x >= xref when rising_edge(clk); -- insert a register
A <= (do something) when rising_edge(clk); -- ditto
B <= (do something else) when rising_edge(clk); -- ditto
A_or_B <= A when Test else B;
Result <= A_or_B when rising_edge(clk); -- second stage pipeline register
Si se está preguntando por qué A_or_B
NO TIENE su propio registro de canalización ... buena pregunta. La respuesta es que podría: escribiríamos las 2 últimas líneas como una sola línea:
Result <= (A when Test else B) when rising_edge(clk);
O igualmente podría haber separado la operación del registro en los casos anteriores:
A_int <= (do something);
A <= A_int when rising_edge(clk);
Esto deja una pregunta pendiente: ¿podemos mantener la claridad de la operación de alto nivel mientras lo canalizamos más profundamente? Bueno, aquí está mi enfoque ...
Process(clk) is
begin
if rising_edge(clk) then
-- Pipeline stage 1
Test <= x >= x_ref;
A <= (do something);
B <= (do something else);
-- Pipeline stage 2
if Test then
Result <= A;
else
Result <= B;
end if;
end if;
end process;