Foreword:
This column aims to record high-frequency pen interview hand-torn code questions for digital front-end autumn recruits. All articles in this column provide principle analysis, codes and waveforms, and all codes have been verified by myself.
The directory is as follows:
1. Digital IC hand-tear code-frequency divider (any even number frequency division)
2. Digital IC hand-tear code-frequency divider (any odd frequency division)
3. Digital IC hand-tear code-frequency divider (any decimal frequency division)
4. Digital IC hand tearing code - asynchronous reset and synchronous release
5. Digital IC hand tear code - edge detection (rising edge, falling edge, double edge)
6. Digital IC hand tearing code-sequence detection (state machine writing method)
7. Digital IC hand tearing code-sequence detection (shift register writing method)
8. Digital IC tearing code - half adder, full adder
9. Digital IC hand tearing code - serial to parallel, parallel to serial
10. Digital IC hand tearing code-data bit width converter (width-narrow, narrow-width conversion)
11. Digital IC hand tearing code - finite state machine FSM - beverage machine
12. Digital IC hand tear code - handshake signal (READY-VALID)
14. Digital IC hand tearing code - Telink micro written test questions
15. Digital IC hand tearing code - Pingtouge technology final face hand tearing real question
16. Digital IC manual tearing code-Zhaoyi innovation written test real questions
18. Digital IC tearing code - dual-port RAM (dual-port-RAM)
...Continually updated
For more hand-tearing code questions, you can go to the digital IC hand-tearing code--question bank .
Table of contents
The counter is judged to be empty
Principle introduction
When you design a system, you include elements such as processors and peripherals that operate at different clock frequencies. FIFO first in first out arrays play an important role when data is transferred between these elements. A FIFO is a simple storage structure used to arrange data for transmission on a communication bus.
Therefore, FIFOs are often used to transfer data across different clock domains.
This section introduces a simple synchronous FIFO architecture, which uses the same clock for reading and writing, paving the way for our subsequent writing of asynchronous FIFOs (the reading and writing clocks are not of the same source).
The general structure of a synchronous FIFO is given below . DPRAM (Dual Port RAM) is used as a memory to store information. After adding a component to judge whether the DPRAM is full or empty, the entire module is a synchronous FIFO. Read and write Use different enable and address signals (read and write enable, separate read and write addresses), so that the entire module can be read and written at the same time.
Generate respective read and write addresses through the read and write pointers, and send them to the read and write ports. The write pointer points to the next address to be written, and the read pointer points to the next address to be read. An active write enable increments the write pointer, and an active read enable increments the read pointer.
The "status module" in the figure generates a FIFO empty-full signal. If "fifo_full" is valid, it means that the space inside the FIFO is full and no more data can be written. If "fifo_empty" is valid, it means that there is next valid data available for reading in FIFO. By judging the position of the read-write pointer, the module can also indicate the number of empty or full areas of the FIFO at any time.
How synchronous FIFOs work
After reset, both read and write pointers return to 0. At this time, the "fifo_empty" signal is asserted and "fifo_full" remains low. Because the FIFO is empty, the read operation to the FIFO is blocked and only write operations can be performed. Subsequent write operations increment the write pointer and deassert the "fifo_empty" signal. When the last data is written, the write pointer is equal to RAM_SIZE-1. Performing a write pointer at this time will roll the write pointer back to 0 and set the "fifo_full" signal high.
In short, when the read and write pointers are equal, the FIFO is either empty or full, so it is necessary to distinguish between the two cases.
FIFO full generation
Take a FIFO with a depth of 4 as an example. At the beginning, the read and write pointers point to the same position, and the FIFO is empty. After writing three data, the write pointer points to the position of RAM_SIZE-1=3. At this time, another data is written, and the write pointer (wr_ptr) rolls back to 0, which points to the same position as the read pointer. At this time, the FIFO is full.
According to this logic, it is easy to deduce such a conclusion: no matter where the read-write pointer points at this time, when wr_ptr+1==rd_ptr, the FIFO will be full after writing one more data, so there are:
fifo_full = (rd_ptr == (wr_ptr + 1'b1))&& wr_fifo
Thus, there is an RTL code for judging that the FIFO is full:
always@(posedge clk or negedge rstn)begin
if(!rstn)
fifo_full <= 1'b0;
else if(wr_fifo && rd_fifo)
;//do nothing
else if(rd_fifo)
fifo_full <= 1'b0;
else if((rd_ptr = wr_ptr + 1'b1) && wr_fifo)
fifo_full <= 1'b1;
end
Similarly, when a read operation makes the two pointers equal on the next clock, the FIFO becomes empty and the "fifo_empty" signal is generated. There is the following relationship: No matter what position the read/write pointer points to at this time, when rd_ptr+1==wr_ptr, the FIFO will be empty after reading another data.
fifo_empty = (wr_ptr == (rd_ptr + 1'b1))&& rd_fifo
Therefore, there is an RTL code for judging that the FIFO is empty:
always @(posedge clk or negedge rstn)begin
if(!rstn)
fifo_empty <= 1'b1;
else if(wr_fifo && rd_fifo)
;//do nothing
else if(wr_fifo)
fifo_empty <= 1'b0;
else if((wr_ptr = rd_ptr + 1'b1) && rd_fifo)
fifo_empty <= 1'b1;
end
The counter is judged to be empty
FIFOs have another way of using counters to indicate when a FIFO is full.
The width of the counter should be equal to the depth of the FIFO, so that the counter can record the maximum number of FIFO data. The counter is initialized to 0 at reset, any subsequent write operation will increment it by 1, and any read operation will decrement it by 1.
When the counter is 0, it is easy to judge that the FIFO is in an empty state, and when the value of the counter is equal to the size of the FIFO, it can be judged that the FIFO is in a full state.
It is relatively simple to implement this method of using a counter to judge whether it is empty or full, but compared with the previous method of comparing the position of the read and write pointers, the resource usage will be higher. Because this method requires additional hardware (counter) to count.
the code
To put it simply, FIFO is a dual-port RAM with logic for judging whether it is empty or full. Let’s write a synchronous FIFO that judges whether it is full or empty by a pointer cycle. But before we write a dual-port RAM to store data .
Dual Port RAM
module dual_port_ram#(
parameter DEPTH = 16,
parameter WIDTH = 8
)(
input wr_clk ,
input wr_en ,
input [$clog(DEPTH)-1:0] wr_addr ,
input [WIDTH-1:0] wr_data ,
input rd_clk ,
input rd_en ,
input [$clog(DEPTH)-1:0] rd_addr ,
output [WIDTH-1:0] rd_data
);
reg [WIDTH-1:0] RAM_MEM [DEPTH-1:0];
always @(posedge wr_clk)begin
if(wr_en)
RAM_MEM[wr_addr] <= wr_data;
end
always @(posedge rd_clk)begin
if(rd_en)
RAM_MEM[rd_addr] <= rd_data;
end
endmodule
The entire dual-port RAM is actually a simple write address that writes data into the input when the write is enabled. When the read is enabled, the function of reading data from the address is followed by instantiating the Dual Port RAM module in the FIFO.
sync_FIFO
`include "clog.v"
module sync_fifo#(
parameter WIDTH = 8 ,
parameter DEPTH = 16
)(
input clk ,
input rstn , // reset while rstn is negative
//write interface
input [WIDTH-1:0] data_in , // input data
input wr_en , // write enable
//read interface
input rd_en , // read enable
output [WIDTH-1:0] data_out ,
output reg fifo_empty ,
output reg fifo_full
);
//signal define
reg [clog(DEPTH)-1:0] wr_ptr;
reg [clog(DEPTH)-1:0] rd_ptr;
wire wr_fifo;
wire rd_fifo;
//write data opration
always @(posedge clk or negedge rstn)begin
if(!rstn)
wr_ptr <= 1'b0;
else if(wr_fifo)
wr_ptr <= wr_ptr + 1'b1;
end
assign wr_fifo = wr_en && !fifo_full;
//read data opration
always @(posedge clk or negedge rstn)begin
if(!rstn)
rd_ptr <= 1'b0;
else if(rd_fifo)
rd_ptr <= rd_ptr + 1'b1;
end
assign rd_fifo = rd_en && !fifo_empty;
//full signal judgment
always@(posedge clk or negedge rstn)begin
if(!rstn)
fifo_full <= 1'b0;
else if(wr_fifo && rd_fifo)
;//do nothing
else if(rd_fifo)
fifo_full <= 1'b0;
else if((rd_ptr == wr_ptr + 1'b1) && wr_fifo)
fifo_full <= 1'b1;
end
//empty signal judgment
always @(posedge clk or negedge rstn)begin
if(!rstn)
fifo_empty <= 1'b1;
else if(wr_fifo && rd_fifo)
;//do nothing
else if(wr_fifo)
fifo_empty <= 1'b0;
else if((wr_ptr == rd_ptr + 1'b1) && rd_fifo)
fifo_empty <= 1'b1;
end
dual_port_ram #(
.DEPTH (DEPTH) ,
.WIDTH (WIDTH)
)u_dual_port_ram
(
.wr_clk (clk) , //sync FIFO ,wr_clk = rd_clk
.wr_en (wr_fifo) ,
.wr_addr (wr_ptr) ,
.wr_data (data_in) ,
.rd_clk (clk) ,
.rd_en (rd_fifo) ,
.rd_addr (rd_ptr) ,
.rd_data (data_out)
);
endmodule
In addition, I also wrote a function clog.v to judge the bit width
`ifndef MY_CLOG
`define MY_CLOG
function integer clog (input integer depth);
begin
for (clog=0; depth-1>0; clog=clog+1)
depth = depth >>1;
end
endfunction
`endif
testbench
`timescale 1ns/1ns
module sync_fifo_tb();
parameter WIDTH = 8;
parameter DEPTH = 16;
reg clk ;
reg rstn ;
reg [WIDTH-1:0] data_in ;
reg rd_en ;
reg wr_en ;
wire [WIDTH-1:0] data_out ;
wire empty ;
wire full ;
always #5 clk = ~clk;
initial begin
clk <= 1'b0;
rstn <= 1'b0;
data_in <= 'd0;
rd_en <= 1'b0;
wr_en <= 1'b0;
//write 16 times to make fifo full
#10
rstn <= 1'b1;
repeat(16)begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; // generate 8bit random number data_in
end
end
//read 16 times to make fifo empty
repeat(16)begin
@(negedge clk)begin
wr_en <= 1'b0;
rd_en <= 1'b1;
end
end
//read and write 8 times
repeat(8)begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random;
rd_en <= 1'b0;
end
end
//Continuous read and write
forever begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random;
rd_en <= 1'b1;
end
end
end
initial begin
#800
$finish();
end
initial begin
$fsdbDumpfile("sync_fifo.fsdb");
$fsdbDumpvars(0);
end
sync_fifo #(
.WIDTH (WIDTH) ,
.DEPTH (DEPTH)
)u_sync_fifo
(
.clk (clk) ,
.rstn (rstn) ,
.data_in (data_in) ,
.rd_en (rd_en) ,
.wr_en (wr_en) ,
.data_out (data_out) ,
.fifo_empty (empty) ,
.fifo_full (full)
);
endmodule
Waveform
The simulation results are consistent with the analysis results. Write 16 data to fill the FIFO, and the full signal is pulled high at this time; read 16 data to empty the FIFO, and the empty signal is pulled high at this time; after writing 8 data, simultaneously Read and write, write data and read data are consistent and function correctly.
After understanding the design method of synchronous FIFO, it is relatively simple to design asynchronous FIFO. The next blog records how to write an asynchronous FIFO to solve the problem of crossing clock domains in FIFO.
For more hand-tearing code questions, you can go to the digital IC hand-tearing code--question bank .