Si tiene que dividir una operación en varios ciclos de reloj, tiene dos opciones: la canalización y la secuenciación.
Consideremos una operación mítica consistente en cuatro multiplicaciones, por ejemplo, en la que cada multiplicación (excepto la primera) requiere la salida de la multiplicación anterior como una de sus entradas. Sin embargo, las ideas básicas son mucho más generales.
En el pipelining, se dispone de hardware suficiente para realizar cada operación simultáneamente, interconectado por registros pipeline. Esto implica cuatro multiplicadores, separados por registros pipeline. Se tardan 4 ciclos de reloj en obtener el primer resultado (es decir, el pipeline tiene 4 etapas de profundidad y la latencia es de 4 ciclos), pero se obtiene un nuevo resultado cada ciclo de reloj (es decir, la tasa de rendimiento es de 1 ciclo). Un poco más de información sobre el diseño de las tuberías...
Inconveniente: se trata de una gran pieza de hardware. 4 multiplicadores son relativamente caros (por eso algunas familias de FPGA ofrecen muchos multiplicadores pequeños como bloques altamente optimizados).
La alternativa es secuenciar cada operación en el mismo multiplicador, con lo que se obtiene un diseño mucho más pequeño, pero se obtiene un resultado cada 4 ciclos.
En este caso puedes utilizar un único multiplicador, almacenando su resultado en un único registro, para un diseño mucho más pequeño.
Cada 4 ciclos (o siempre que algo indique que hay una nueva entrada preparada) conectas la nueva entrada a un puerto de entrada del multiplicador; en otros ciclos alimentas ese puerto desde el registro de salida (para utilizar el resultado de la multiplicación anterior). Y en cada ciclo alimentas los datos apropiados (coeficientes de filtro, valores de matriz, lo que sea) en el otro puerto del multiplicador. Cuatro ciclos después, presentas el resultado final como salida, y señalas a tu consumidor que un nuevo resultado está listo.
La forma obvia de secuenciar estas operaciones es una máquina de estados (FSM).
De hecho, los cálculos pueden integrarse en las acciones asociadas a cada estado, por ejemplo:
if rising_edge(clk) then
Done <= '0'; -- and any other default actions
case state is
when Idle =>
if Start = '1' then
Temp := Input * C1;
State := S1;
end if;
when s1 =>
Temp := Temp * C2;
State := S2;
when s2 =>
Temp := Temp * C3;
State := S3;
when s3 =>
Temp := Temp * C4;
State := S4;
when s4 =>
Output <= Temp;
Done <= '1';
State := Idle;
-- optional alternative for bombproof handshaking
-- if Start = '0' then
-- Done <= '1';
-- State <= Idle;
-- end if;
end case;
end if;
Si estás interactuando con otras unidades - interfaces SPI, UARTs etc, el FSM es de nuevo normalmente el mejor método.