En el siguiente tema veremos algunos de los circuitos secuenciales más importantes. Empezaremos por los Flip-Flop tipo D y continuaremos con Contadores, Registros y otros circuitos secuencia les de mayor complejidad.
El flip-flop tipo D (DFF) es el circuito secuencial más importante en VLSI y sistemas digitales. Es la unidad básica de memoria; un dispositivo capaz de almacenar 1 bit de información sincronizado con una señal reloj.
Los DFFs son los circuitos secuenciales más sencillos y la base sobre la que se construyen otros circuitos secuenciales más complejos tales como Registros, Contadores, Máquinas de estados (FSM), Pipelines, Memorias, etc.
module d_ff(
input logic d,
input logic clk,
output logic q
);
always_ff @(posedge clk)
begin
q <= d;
end
endmodule
Para ver de manera práctica el funcionamiento del flip-flop en la BASYS 3, podemos conectar al pin clk una señal de reloj lenta, en lugar de la señal de 100 MHz.
El código y el esquema del reloj customizable, se muestran a continuación:
En el módulo d_ff_top conectamos la señal de reloj al flip-flop. Con el SW1 simulamos la entrada d, con los ledes 0 y 1 visualizamos la señal de reloj y la salida q.
module d_ff_top(in, out, clk, clk_led);
input logic in, clk;
output logic out, clk_led;
wire w_custom_clk;
assign clk_led = w_custom_clk;
custom_clk_top CUSTOM_CLK (
.clk(clk),
.custom_clk(w_custom_clk));
d_ff D_FF (
.d(in),
.clk(w_custom_clk),
.q(out));
endmodule
Otros ejemplos de flip-flop con reset y enable se muestran a continuación:
module d_ff (
input logic clk, // Reloj
input logic rst_n, // Reset
input logic d, // Entrada
output logic q // Salida
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end else begin
q <= d;
end
end
endmodule
module d_ff (
input logic clk, // Reloj
input logic rst_n, // Reset
input logic en, // Enable
input logic d, // Entrada
output logic q // Salida
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
q <= 1'b0;
end else if (en) begin
q <= d;
end
end
endmodule
Es un conjunto de flip-flops tipo D que guarda un valor binario (por ejemplo, 8, 16, 32 o 64 bits) y que no tiene una función fija específica; se puede usar para almacenar cualquier tipo de dato, dirección u operación.
Un ejemplo de registro de propósito general (GPR, General Purpose Register) lo tenemos en el registro interno de la CPU o de un sistema digital que se utiliza para almacenar datos temporales y operar con ellos durante la ejecución de un programa, por ejemplo el Register File de un procesador MIPS.
Este tipo de registros evitan accesos contantes a la memoria RAM que además, es más lenta que el acceso a registros.
module register_file (
input logic clk,
input logic we, // write enable
input logic [3:0] addr_w, // dirección escritura
input logic [3:0] addr_r1, addr_r2,// direcciones lectura
input logic [31:0] data_in,
output logic [31:0] data_out1, data_out2
);
logic [31:0] regs [15:0]; // 16 registros de 32 bits
// Escritura
always_ff @(posedge clk) begin
if (we)
regs[addr_w] <= data_in;
end
// Lectura (combinacional)
assign data_out1 = regs[addr_r1];
assign data_out2 = regs[addr_r2];
endmodule
Un registro PISO (Parallel-In Serial-Out) es un tipo de registro que carga todos los datos a la vez (parallel-in) y los casa de uno en uno (serial-out).
Es ampliamente utilizado en comunicación serie (UART, SPI) e interfaces externas (envío de datos a periféricos).
Funcionamiento:
Carga (load = 1): En el siguiente flanco de reloj, el valor completo de d_in se guarda en shift_reg.
Desplazamiento (load = 0): En cada flanco de reloj, los bits se mueven una posición a la derecha. El bit que estaba en shift_reg[0] sale por s_out.
Concatenación {1'b0, shift_reg[WIDTH-1:1]}: Esta línea introduce un cero por la izquierda (MSB) mientras desplaza el resto de los bits hacia la derecha.
module piso_reg #(
parameter WIDTH = 8
)(
input logic clk, // Reloj
input logic rst_n, // Reset asíncrono activo en bajo
input logic load, // 1: Carga paralela, 0: Desplazamiento
input logic en, // Habilitador de operación
input logic [WIDTH-1:0] d_in, // Entrada paralela
output logic s_out // Salida serial
);
logic [WIDTH-1:0] shift_reg;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
shift_reg <= '0;
end else if (en) begin
if (load) begin
// Carga paralela
shift_reg <= d_in;
end else begin
// Desplazamiento a la derecha
shift_reg <= {1'b0, shift_reg[WIDTH-1:1]};
end
end
end
// La salida serie es siempre el bit menos significativo
assign s_out = shift_reg[0];
endmodule
// piso reg top module
module top_module (
input logic reset, enable, load, clk,
input logic [7:0] data_in,
output logic serial_out, clk_led);
wire w_custom_clk;
assign clk_led = w_custom_clk;
custom_clk_top CUSTOM_CLK (
.clk(clk),
.custom_clk(w_custom_clk));
piso_reg PISO_REG (
.clk(w_custom_clk),
.rst_n(reset),
.load(load),
.en(enable),
.d_in(data_in),
.s_out(serial_out));
endmodule
Los CSR, (Control and Status Register) son el "puente de mando" de un componente de hardware, es un registro interno de un sistema digital (CPU, microcontrolador, FPGA o ASIC) que se utiliza para leer el estado interno del sistema y configurar el comportamiento del software.
A diferencia de los registros generales (GPR) que mueven datos en el bus, los CSR sirven para configurar cómo se comporta un periférico o el procesador y para leer qué está pasando dentro de él. Un CSR tiene dos partes; Control → lo escribe el software y Status → lo lee el software, es decir, el software escribe las señales de control y lee el estado del sistema, mientras que el hardware conectado al SCR escribe su estado actual en él para informar al sistema. Es un registro accesible por la CPU que conecta el mundo software con el hardware.
En el ejemplo de la imagen, el CSR tiene un bus de datos de 8 bits donde csr[5:0] corresponden a los bits de Control y los bits csr[7:6] son los bits de Status.
Los bits de control puede ser por ejemplo:
[INT_EN, MODE, ENABLE, RESET, LOOPBACK, TX_EN]
[Interrupciones, Selección de Modo, Enable, Reset, Modo Test, Transmisión]
HDD escribe en el CSR su estado (u otro periférico conectado al CSR).
La CPU puede leer el estado del periférico en csr[7:6] y escribir los bits de control en csr[5:0] para que éste realice las operaciones que le correspondan.
module csr_example (
input logic clk,
input logic rst,
input logic write_en,
input logic [7:0] write_data,
input logic hw_ready,
input logic hw_error,
output logic [7:0] csr_out
);
logic [7:0] csr;
always_ff @(posedge clk or posedge rst) begin
if (rst)
csr <= 8'b0;
else begin
// Escritura (bits de control)
if (write_en)
csr[5:0] <= write_data[5:0];
// Bits de estado (hardware los actualiza)
csr[6] <= hw_ready;
csr[7] <= hw_error;
end
end
assign csr_out = csr;
endmodule
esquema conceptual
Un pipeline register es simplemente un registro que se coloca entre etapas de procesamiento para dividir una operación compleja en varias operaciones simples.
Por ejemplo, se divide una operación matemática en etapas (operaciones simples) y entre medias se coloca un registro...
Entrada → [Etapa 1] → (Registro) → [Etapa 2] → (Registro) → [Etapa 3] → Salida
...los registros intermedios son los pipeline registers.
Vemamos un ejemplo sencillo, supongamos que queremos calcular y = (a + b) × c con pipeline de 2 etapas:
Etapa 1: suma → a + b
Etapa 2: multiplicación → (a + b) * c
Entre ambas colocamos un pipeline register.
module pipeline_example (
input logic clk,
input logic rst,
input logic [7:0] a, b, c,
output logic [15:0] y
);
// Pipeline register (entre etapas)
logic [8:0] sum_stage; // resultado de la etapa 1
// Etapa 1: suma
always_ff @(posedge clk or posedge rst) begin
if (rst)
sum_stage <= 0;
else
sum_stage <= a + b;
end
// Etapa 2: multiplicación
always_ff @(posedge clk or posedge rst) begin
if (rst)
y <= 0;
else
y <= sum_stage * c;
end
endmodule
Para probar el circuito de manera práctica en la BASYS 3, empleamos los switches sw[7:0] para introducir el dato a y los switches sw[15:8] para el dato b. El dato c lo introducimos mediante el puerto PMOD JA y el resultado y de la operación se muestra en los ledes[15:0].
Etapa 1 (Captura/Procesamiento inicial): La primera etapa recibe los datos de entrada, realiza una parte del cálculo y guarda el resultado intermedio en un registro intermedio.
Etapa 2 (Procesamiento final/Salida): En el siguiente ciclo, la segunda etapa toma el valor del registro intermedio, completa la operación y entrega el resultado final.
Un par de conceptos importantes cuando se trabaja con pipeline son:
· Latencia, el número de ciclos que de reloj en completarse la operación desde que entra hasta que sale. En el ejemplo que acabamos de ver Latencia = 2.
· Rendimiento (throughput), una vez que el pipeline está lleno, se obtiene un resultado por cada ciclo de reloj. Rendimiento =1.
Un LFSR (Linear Feedback Shift Register) es un registro de desplazamiento cuyos bits se actualizan aplicando una función lineal (normalmente XOR) sobre ciertos bits llamados taps o puntos de realimentación.
Los usos más comunes son: generación de números pseudoaleatorios, simulaciones, algoritmos de cifrado simples y generación de patrones de prueba.
Una de las aplicaciones más interesantes del LFSR es como BIST (Built-In Self-Test), es decir, un circuito capaz de probarse a sí mismo.
La capacidad del LFSR de generar patrones pseudoaleatorios es ideal como generador de estímulos para verificar el correcto funcionamiento de un circuito. En este modo de funcionamiento lo que se hace es utilizar las salidas del LFSR para estimular las entradas del Circuito Bajo Prueba (CUT). La salida del CUT se compara con un patrón y si el resultado del test es OK, el circuito se da por bueno.
Veamos un ejemplo práctico. La siguiente imagen muestra el código de un LFSR de 8 bits y un circuito combinacional que será el CUT, el circuito a verificar.
En el módulo top conectamos las salidas del LFSR a las entradas del CUT y las salidas del CUT las conectamos a los ledes de la BASYS 3. También podemos conectar la salida del LFSR a los ledes de la BASYS para ver como cambian. El proyecto en Vivado se puede descargar al final de la página.
También he añadido un reloj lento de 250 ms para ver mejor el funcionamiento de los ledes, de otra manera los veriamos siempre encendidos.
Aún así, nos siguen faltando ledes en la BASYS 3 para mostrar el resultado de las operaciones, así que he empleado los segmentos del los displays para visualizar la suma de los nibbles, el led de paridad y la señal de reloj lenta de 250 ms.
##7 Segment Display
set_property -dict { PACKAGE_PIN W7 IOSTANDARD LVCMOS33 } [get_ports {led_suma_nibbles[0]}]
set_property -dict { PACKAGE_PIN W6 IOSTANDARD LVCMOS33 } [get_ports {led_suma_nibbles[1]}]
set_property -dict { PACKAGE_PIN U8 IOSTANDARD LVCMOS33 } [get_ports {led_suma_nibbles[2]}]
set_property -dict { PACKAGE_PIN V8 IOSTANDARD LVCMOS33 } [get_ports {led_suma_nibbles[3]}]
set_property -dict { PACKAGE_PIN U5 IOSTANDARD LVCMOS33 } [get_ports {led_paridad}]
set_property -dict { PACKAGE_PIN V5 IOSTANDARD LVCMOS33 } [get_ports {clk_led}]
#set_property -dict { PACKAGE_PIN U7 IOSTANDARD LVCMOS33 } [get_ports {seg[6]}]
Puedes descargar las fuentes y el proyecto Vivado de los ejemplo empleados en esta página en el enlace de aquí abajo.
Espero que os haya gustado ¡Hasta pronto!