Actualmente estoy diseñando un FIFO asíncrono para fines de aprendizaje. He hecho el módulo pero tengo algunas dudas al respecto.
En primer lugar, he visto algunos artículos que describen cómo abordarlos y diseñarlos de manera aproximada (no he revisado ningún artículo con información detallada, porque quiero aprenderlo yo mismo). Y en todos los indicadores de estado que he visto que indican si FIFO está lleno o vacío se está registrando:
always @(posedge clk_write) begin
full <= ...
end
always @(posedge clk_read) begin
empty <= ...
end
Pero en mi módulo he definido 'lleno' y 'vacío' como cables y asignarlos así:
assign full = (wr_pnt == rd_pnt_sync_w) && (wr_pnt_wraped != rd_pnt_wraped_sync_w);
assign empty = (rd_pnt == wr_pnt_sync_r) && (rd_pnt_wraped == wr_pnt_wraped_sync_r);
'rd_pnt_sync_w' y 'wr_pnt_sync_r' son punteros sincronizados a otros dominios de reloj. Esos '_wraped' son solo algunos bits para indicar si un puntero de escritura / lectura se ha desplazado alrededor de FIFO.
Entonces, tengo una pregunta, ¿cuál es la diferencia si registro señales 'llenas' / 'vacías'? En mi módulo, todas las señales 'wr_pnt', 'rd_pnt_sync_w', 'wr_pnt_wraped', ... están registradas en los relojes respectivos. ¿Hay una gran diferencia si lo hago así o debo registrar esas señales?
Módulo completo:
module dual_port_fifo_dc #(
parameter DATA_WIDTH = 8, // Data bus width in bits.
parameter ADDR_WIDTH = 8 // Address width in bits. 2 ^ 'ADDR_WIDTH' locations 'DATA_WIDTH' wide in FIFO.
)(
input clk_r, // Clock for read port.
input clk_w, // Clock for write port.
input reset, // Active high reset.
input we, // Write enable.
input re, // Read enable.
input [DATA_WIDTH - 1:0] data_w, // Data to write to FIFO.
output [DATA_WIDTH - 1:0] data_r, // Data read from FIFO.
output full, // FIFO is full and can not be written to.
output empty, // FIFO is empty and can not be read from.
output almost_full, // When FIFO is half or more full.
output almost_empty // When FIFO is half or more empty.
);
// Gray encoding is used for pointers because at maximum only one bit changes simultaneously where as
// with binary encoding going from 3 (3'b011) to 4 (3'b100) all bits change. This one bit change is
// wanted for synchronizations to other clock domains as no special care is needed (just a general
// 2-stage synchronizer with D flip-flops). While with binary encoding there could be problems with
// the same approach, if value changes from 3->4 close to positive clock edge, some bit values may
// not get captured correctly.
// Write address/pointer counter.
wire [ADDR_WIDTH - 1:0] wr_pnt; // Write pointer value (Gray).
wire [ADDR_WIDTH - 1:0] wr_addr; // Write address value (Binary).
wire wr_pnt_wraped; // Aditional bit indicating if write address has wraped around.
fifo_addr_counter #(
.WIDTH (ADDR_WIDTH)
) write_counter (
.clk (clk_w),
.ce (we & ~full),
.reset (reset),
.gray_cnt (wr_pnt),
.binary_cnt (wr_addr),
.carry (wr_pnt_wraped)
);
// Read address/pointer counter.
wire [ADDR_WIDTH - 1:0] rd_pnt; // Read pointer value (Gray).
wire [ADDR_WIDTH - 1:0] rd_addr; // Read address value (Binary).
wire rd_pnt_wraped; // Aditional bit indicating if write address has wraped around.
fifo_addr_counter #(
.WIDTH (ADDR_WIDTH)
) read_counter (
.clk (clk_r),
.ce (re & ~empty),
.reset (reset),
.gray_cnt (rd_pnt),
.binary_cnt (rd_addr),
.carry (rd_pnt_wraped)
);
// Synchronize read pointer to write clock ('clk_w').
wire [ADDR_WIDTH - 1:0] rd_pnt_sync_w;
wire rd_pnt_wraped_sync_w;
nbit_synchronizer #(
.STAGE (2),
.WIDTH (DATA_WIDTH)
) rd_pnt_synch (
.clk (clk_w),
.reset (reset),
.d (rd_pnt),
.q (rd_pnt_sync_w)
);
synchronizer #(
.STAGE (2)
) rd_pnt_wraped_synch (
.clk (clk_w),
.reset (reset),
.d (rd_pnt_wraped),
.q (rd_pnt_wraped_sync_w)
);
// Synchronize write pointer to read clock ('clk_r').
wire [ADDR_WIDTH - 1:0] wr_pnt_sync_r;
wire wr_pnt_wraped_sync_r;
nbit_synchronizer #(
.STAGE (2),
.WIDTH (DATA_WIDTH)
) wr_pnt_synch (
.clk (clk_r),
.reset (reset),
.d (wr_pnt),
.q (wr_pnt_sync_r)
);
synchronizer #(
.STAGE (2)
) wr_pnt_wraped_synch (
.clk (clk_r),
.reset (reset),
.d (wr_pnt_wraped),
.q (wr_pnt_wraped_sync_r)
);
// FIFO full flag generation. 'full' signal gets asserted immediately as
// write pointer changes. But because of read pointer synchronization it will
// get de-asserted two 'clk_w' ticks after FIFO isnt actually full anymore.
// Full when wraped bits dont match. Meaning write pointer has wraped around FIFO.
assign full = (wr_pnt == rd_pnt_sync_w) && (wr_pnt_wraped != rd_pnt_wraped_sync_w);
// FIFO empty flag generation. 'empty' signal gets asserted immediately as
// read pointer changes. But because of write pointer synchronization it will
// get de-asserted two 'clk_r' ticks after FIFO isnt actually empty anymore.
assign empty = (rd_pnt == wr_pnt_sync_r) && (rd_pnt_wraped == wr_pnt_wraped_sync_r);
// Dual port RAM with seperate asynchronous clocks for reading and writting.
dp_ram_block #(
.DATA_WIDTH (DATA_WIDTH),
.ADDR_WIDTH (ADDR_WIDTH)
) dpram (
.clk_w (clk_w),
.clk_r (clk_r),
.we (we),
.addr_w (wr_addr),
.addr_r (rd_addr),
.data_w (data_w),
.data_r (data_r)
);
endmodule