Learning route:
- Synchronous FIFO design (with source code RTL/TB)
- Asynchronous FIFO design (with source code RTL/TB)
- uart communication protocol (with source code RTL/TB)
- SPI protocol interface design (with source code RTL/TB)
- AMBA 3 APB interface design (with source code RTL/TB)
- AMBA AHB interface design (with source code RTL/TB)
- AMBA AXI interface design (with source code RTL/TB)
- UART 2 APB bridge design (with source code RTL/TB)
- APB 2 UART bridge design (with source code RTL/TB)
1. Basic concepts
- What is FIFO and what is it used for?
- What does "synchronous" mean for synchronous FIFO?
- What are the interfaces:
FIFO is afirst-in-first-out data buffer. FIFO and RAM can be said to be twin brothers, first in, first out. As the name suggests, it cannot read and write according to the address like RAM, but can only write and read data sequentially. a>This is the biggest difference between it and RAM, and therefore determines their different usage scenarios. Logically speaking, FIFO is composed of RAM and control logic. In terms of interface timing, there are no read address and write address signals, but more empty signals and full signals. Other configurations, such as size configuration, operating frequency and other characteristics are consistent with RAM. The data address is automatically added by 1 to the internal read-write pointer.
Synchronous FIFO, the same clock is used for reading and writing. Its role is generally to serve as a buffer for interactive data, that is It is said that its main function is a buffer. The buffer is used to achieve rate matching. Asynchronous FIFO, different clocks are used for reading and writing. It has two main functions, one isto realize data transfer in different clock domains, and the other function isto realize different data widths Data interface.
Since it is a data buffer, the buffer size, storage depth, read and write address and memory full status all need to be determined.
Generally, FIFO uses a circular pointer (the count automatically returns to zero when it overflows). Generally, the write pointer can be called the head, and the read pointer can be called the tail. During initialization, the read and write pointers point to the same data address.
As shown in the above figure, when the FIFO is initialized, the WP and RP pointers point to the same data unit. WP points to the next data unit to be written, and RP points to the data unit to be read. The two are a catching-up process. You can set a counter, write only, come one data, write one, write address, +1, counter +1, until full; read only, come one, read one data, read address +1, counter -1; at the same time Reading and writing, the counter value remains unchanged, and the reading and writing addresses are both +1.
3. Spec
(1) Function description
Synchronous FIFO implements write/read control, and its interface solves the problem of data rate mismatch at both ends of the interface.
(2) Feature list
- Supports configurable storage width and depth
- The clock operating frequency is 1MHz
(3) Block diagram
The module is mainly divided into four parts: read/write interface, read/write pointer, comparison logic of read and write pointer, and array storage array.
- Read/write interface: Provides the module with read and write data and read and write enable signals;
- Read-write pointer: main flag read-write pointer current array address
- Comparison logic:
- Use element counter (elem_cnt) to record the number of data in FIFO RAM:
▷ When equal to 0, the empty signal is given; when equal to BUF_LENGTH, the full signal is given - elem_cnt:
▷ Increase by 1 when writing but not full
▷ Decrease 1 when reading but not empty
▷ When read and write operations occur at the same time, elem_cnt remains unchanged
(4) Interface description
(5) Timing
Divided into three parts, writing operations, reading operations, and read and write operations.
四、RTL design
- DUT module
module sync_fifo
#(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 8 ,
parameter PTR_WIDTH = 3
//parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)
(
input wire clk_i ,
input wire rst_n_i ,
//write interface
input wire wr_en_i ,
input wire [DATA_WIDTH-1:0] wr_data_i,
//read interface
input wire rd_en_i ,
output reg [DATA_WIDTH-1:0] rd_data_o,
//Flags_o
output reg full_o ,
output reg empty_o
);
reg [DATA_WIDTH-1:0] regs_array [DATA_DEPTH-1:0];
reg [PTR_WIDTH-1 :0] wr_ptr ;
reg [PTR_WIDTH-1 :0] rd_ptr ;
reg [PTR_WIDTH :0] elem_cnt ;
reg [PTR_WIDTH :0] elem_cnt_nxt ;
//Flags
wire full_comb ;
wire empty_comb ;
/*---------------------------------------------------\
--------------- write poiter addr ----------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
wr_ptr <= 3'b0;
end
else if (wr_en_i && !full_o) begin
wr_ptr <= wr_ptr + 3'b1;
end
end
/*---------------------------------------------------\
-------------- read poiter addr ------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_ptr <= 3'b0;
end
else if (rd_en_i && !empty_o) begin
rd_ptr <= rd_ptr + 3'b1;
end
end
/*---------------------------------------------------\
--------------- element counter ------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
elem_cnt <= 4'b0;
end
else if (wr_en_i && rd_en_i && !full_o && !empty_o) begin
elem_cnt <= elem_cnt;
end
else if(wr_en_i && !full_o) begin
elem_cnt <= elem_cnt + 1'b1;
end
else if(rd_en_i && !empty_o) begin
elem_cnt <= elem_cnt - 1'b1;
end
end
/*---------------------------------------------------\
------------- generate the flags -----------------
\---------------------------------------------------*/
always @(*) begin
if(!rst_n_i) begin
elem_cnt_nxt = 1'b0;
end
else if(elem_cnt != 4'd0 && rd_en_i && !empty_o) begin
elem_cnt_nxt = elem_cnt - 1'b1;
end
else if(elem_cnt != 4'd8 && wr_en_i && !full_o) begin
elem_cnt_nxt = elem_cnt + 1'b1;
end
else begin
elem_cnt_nxt = elem_cnt;
end
end
assign full_comb = (elem_cnt_nxt == 4'd8);
assign empty_comb = (elem_cnt_nxt == 4'd0);
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
full_o <= 1'b0;
end
else begin
full_o <= full_comb;
end
end
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
empty_o <= 1'b1;
end
else begin
empty_o <= empty_comb;
end
end
/*---------------------------------------------------\
-------------------- read data -------------------
\---------------------------------------------------*/
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
rd_data_o <= 32'b0;
end
else if(rd_en_i && !empty_o) begin
rd_data_o <= regs_array[rd_ptr];
end
end
/*---------------------------------------------------\
------------------- write data -------------------
\---------------------------------------------------*/
reg [PTR_WIDTH:0] i;
always @ (posedge clk_i or negedge rst_n_i) begin
if (!rst_n_i) begin
for(i=0;i<DATA_DEPTH;i=i+1) begin
regs_array[i] <= 32'b0;
end
end
else if(wr_en_i && !full_o) begin
regs_array[wr_ptr] <= wr_data_i;
end
end
endmodule
- tb
module tb_sync_fifo;
reg clk_i ;
reg rst_n_i ;
reg wr_en_i ;
reg [31:0] wr_data_i;
reg rd_en_i ;
reg [31:0] rd_data_o;
wire full_o ;
reg empty_o ;
initial begin
rst_n_i = 1 ;
clk_i = 0 ;
rd_en_i = 0 ;
wr_en_i = 0 ;
wr_data_i = 32'b0;
#2 rst_n_i = 0 ;
#5 rst_n_i = 1 ;
end
initial begin
#10 wr_en_i = 1;
rd_en_i = 0;
#10 wr_en_i = 0;
rd_en_i = 1;
#10 wr_en_i = 1;
rd_en_i = 0;
#3 rd_en_i = 1;
#10
repeat(100) begin
#5 wr_en_i = {$random}%2;
rd_en_i = {$random}%2;
end
end
initial #2000 $finish;
always #0.5 clk_i = ~clk_i ;
always #1 wr_data_i = {$random}%10;
sync_fifo u_sync_fifo
(
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.wr_en_i (wr_en_i ),
.wr_data_i(wr_data_i),
.rd_en_i (rd_en_i ),
.rd_data_o(rd_data_o),
.full_o (full_o ),
.empty_o (empty_o )
);
initial begin
$fsdbDumpfile("sync_fifo.fsdb");
$fsdbDumpvars ;
$fsdbDumpMDA ;
end
endmodule
5. Analysis and Summary
(1) Analysis
- writing stage
After reset, write operation is performed until full. After the full flag is generated, no new data will be written.
- reading stage
Perform reading operations until the read is empty. After the empty flag is generated, no new data will be read.
- Simultaneous reading and writing stage
Perform writing operations first, and after writing three new data, read and write operations are performed at the same time. During this period, new data is written and data is read, but the elem_cnt counter does not change and is dynamically balanced.
(2) Summary
Design idea: first analyze the requirements, define the interface, and draw a specific implementation block diagram; draw the corresponding sequence diagram according to the protocol and understanding; write the program based on the diagram , verify whether the simulation waveform corresponds to the timing diagram.
The key point of synchronous FIFO design is when to generate the empty and full flag, that is, how to measure whether the array is full or empty. Here, I use the 4-bit elem_cnt representation, and use the value of elem_cnt to represent the resource usage of the current array storage array. 0 means there is no data, that is, an empty state; 8 means it is full, because the storage depth of the array is 8. It is mentioned in the spec that the FIFO can be configured. Here, only a synchronous fifo design with a width of 32 bits and a depth of 8 is implemented. The preliminary verification of the simulation waveform corresponds to the timing diagram.
————————————————
Copyright statement: This article is an original article by CSDN blogger "xlinxdu" and follows CC 4.0 BY- SA Copyright Agreement, please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/qq_43244515/article/details/124163224