mirror of
https://gitlab.com/hyperglitch/jellyfish.git
synced 2025-11-09 21:27:59 +00:00
- testbenches updated with TB_DEPS directive for granular dependency definition - async_fifo added for transferring ADC data to QSPI - qspi reads fixed - adc_ctrl properly sampling the ADC - DAC tested and working - periodic ADC trigger added
353 lines
13 KiB
Verilog
353 lines
13 KiB
Verilog
// SPDX-FileCopyrightText: 2021 Shawn Hymel
|
|
// SPDX-License-Identifier: 0BSD
|
|
|
|
// Verilog code written by Shawn Hymel based on the Clifford E. Cummings
|
|
// paper http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf
|
|
|
|
|
|
`default_nettype none
|
|
module async_fifo #(
|
|
|
|
// Parameters
|
|
parameter DATA_SIZE = 8, // Number of data bits
|
|
parameter ADDR_SIZE = 4 // Number of bits for address
|
|
) (
|
|
|
|
// Inputs
|
|
input [DATA_SIZE-1:0] w_data, // Data to be written to FIFO
|
|
input w_en, // Write data and increment addr.
|
|
input w_clk, // Write domain clock
|
|
input w_rst, // Write domain reset
|
|
input r_en, // Read data and increment addr.
|
|
input r_clk, // Read domain clock
|
|
input r_rst, // Read domain reset
|
|
|
|
// Outputs
|
|
output w_full, // Flag: 1 if FIFO is full
|
|
output w_almost_full, // Flag: 1 if FIFO is almost full
|
|
output reg [DATA_SIZE-1:0] r_data, // Data to be read from FIFO
|
|
output r_empty, // Flag: 1 if FIFO is empty
|
|
output reg dbgled
|
|
);
|
|
|
|
// Constants
|
|
localparam FIFO_DEPTH = (1 << ADDR_SIZE);
|
|
|
|
// Internal signals
|
|
wire [ADDR_SIZE-1:0] w_addr;
|
|
wire [ADDR_SIZE:0] w_gray;
|
|
wire [ADDR_SIZE-1:0] r_addr;
|
|
wire [ADDR_SIZE:0] r_gray;
|
|
|
|
// Internal storage elements
|
|
reg [ADDR_SIZE:0] w_syn_r_gray;
|
|
reg [ADDR_SIZE:0] w_syn_r_gray_pipe;
|
|
reg [ADDR_SIZE:0] r_syn_w_gray;
|
|
reg [ADDR_SIZE:0] r_syn_w_gray_pipe;
|
|
|
|
// Declare memory
|
|
reg [DATA_SIZE-1:0] mem [0:FIFO_DEPTH-1];
|
|
|
|
initial mem[0] <= 255;// fill the first byte to let Yosys infer to BRAM.
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Dual-port memory (should be inferred as block RAM)
|
|
|
|
// Write data logic for dual-port memory (separate write clock)
|
|
// Do not write if FIFO is full!
|
|
always @ (posedge w_clk) begin
|
|
if (w_en & ~w_full) begin
|
|
dbgled <= ~dbgled;
|
|
mem[w_addr] <= w_data;
|
|
end
|
|
end
|
|
|
|
// Read data logic for dual-port memory (separate read clock)
|
|
// Do not read if FIFO is empty!
|
|
always @ (posedge r_clk) begin
|
|
if (r_en & ~r_empty) begin
|
|
r_data <= mem[r_addr];
|
|
end
|
|
end
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Synchronizer logic
|
|
|
|
// Pass read-domain Gray code pointer to write domain
|
|
always @ (posedge w_clk or posedge w_rst) begin
|
|
if (w_rst == 1'b1) begin
|
|
w_syn_r_gray_pipe <= 0;
|
|
w_syn_r_gray <= 0;
|
|
end else begin
|
|
w_syn_r_gray_pipe <= r_gray;
|
|
w_syn_r_gray <= w_syn_r_gray_pipe;
|
|
end
|
|
end
|
|
|
|
// Pass write-domain Gray code pointer to read domain
|
|
always @ (posedge r_clk or posedge r_rst) begin
|
|
if (r_rst == 1'b1) begin
|
|
r_syn_w_gray_pipe <= 0;
|
|
r_syn_w_gray <= 0;
|
|
end else begin
|
|
r_syn_w_gray_pipe <= w_gray;
|
|
r_syn_w_gray <= r_syn_w_gray_pipe;
|
|
end
|
|
end
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Instantiate incrementer and full/empty checker modules
|
|
|
|
// Write address increment and full check module
|
|
w_ptr_full #(.ADDR_SIZE(ADDR_SIZE)) w_ptr_full (
|
|
.w_syn_r_gray(w_syn_r_gray),
|
|
.w_inc(w_en),
|
|
.w_clk(w_clk),
|
|
.w_rst(w_rst),
|
|
.w_addr(w_addr),
|
|
.w_gray(w_gray),
|
|
.w_full(w_full),
|
|
.w_almost_full(w_almost_full)
|
|
);
|
|
|
|
// Read address increment and empty check module
|
|
r_ptr_empty #(.ADDR_SIZE(ADDR_SIZE)) r_ptr_empty (
|
|
.r_syn_w_gray(r_syn_w_gray),
|
|
.r_inc(r_en),
|
|
.r_clk(r_clk),
|
|
.r_rst(r_rst),
|
|
.r_addr(r_addr),
|
|
.r_gray(r_gray),
|
|
.r_empty(r_empty)
|
|
);
|
|
|
|
endmodule
|
|
|
|
// Increment read address and check if FIFO is empty
|
|
module r_ptr_empty #(
|
|
|
|
// Parameters
|
|
parameter ADDR_SIZE = 4 // Number of bits for address
|
|
) (
|
|
|
|
// Inputs
|
|
input [ADDR_SIZE:0] r_syn_w_gray, // Synced write Gray pointer
|
|
input r_inc, // 1 to increment address
|
|
input r_clk, // Read domain clock
|
|
input r_rst, // Read domain reset
|
|
|
|
// Outputs
|
|
output [ADDR_SIZE-1:0] r_addr, // Mem address to read from
|
|
output reg [ADDR_SIZE:0] r_gray, // Gray address with +1 MSb
|
|
output reg r_empty // 1 if FIFO is empty
|
|
);
|
|
|
|
// Internal signals
|
|
wire [ADDR_SIZE:0] r_gray_next; // Gray code version of address
|
|
wire [ADDR_SIZE:0] r_bin_next; // Binary version of address
|
|
wire r_empty_val; // FIFO is empty
|
|
|
|
// Internal storage elements
|
|
reg [ADDR_SIZE:0] r_bin; // Registered binary address
|
|
|
|
// Drop extra most significant bit (MSb) for addressing into memory
|
|
assign r_addr = r_bin[ADDR_SIZE-1:0];
|
|
|
|
// Be ready with next (incremented) address (if inc set and not empty)
|
|
assign r_bin_next = r_bin + (r_inc & ~r_empty);
|
|
|
|
// Convert next binary address to Gray code value
|
|
assign r_gray_next = (r_bin_next >> 1) ^ r_bin_next;
|
|
|
|
// If the synced write Gray code is equal to the current read Gray code,
|
|
// then the pointers have caught up to each other and the FIFO is empty
|
|
assign r_empty_val = (r_gray_next == r_syn_w_gray);
|
|
|
|
// Register the binary and Gray code pointers in the read clock domain
|
|
always @ (posedge r_clk or posedge r_rst) begin
|
|
if (r_rst == 1'b1) begin
|
|
r_bin <= 0;
|
|
r_gray <= 0;
|
|
end else begin
|
|
r_bin <= r_bin_next;
|
|
r_gray <= r_gray_next;
|
|
end
|
|
end
|
|
|
|
// Register the empty flag
|
|
always @ (posedge r_clk or posedge r_rst) begin
|
|
if (r_rst == 1'b1) begin
|
|
r_empty <= 1'b1;
|
|
end else begin
|
|
r_empty <= r_empty_val;
|
|
end
|
|
end
|
|
|
|
endmodule
|
|
|
|
|
|
// Increment write address and check if FIFO is full
|
|
module w_ptr_full #(
|
|
|
|
// Parameters
|
|
parameter ADDR_SIZE = 4 // Number of bits for address
|
|
) (
|
|
|
|
// Inputs
|
|
input [ADDR_SIZE:0] w_syn_r_gray, // Synced read Gray pointer
|
|
input w_inc, // 1 to increment address
|
|
input w_clk, // Write domain clock
|
|
input w_rst, // Write domain reset
|
|
|
|
// Outputs
|
|
output [ADDR_SIZE-1:0] w_addr, // Mem address to write to
|
|
output reg [ADDR_SIZE:0] w_gray, // Gray adress with +1 MSb
|
|
output reg w_full, // 1 if FIFO is full
|
|
output reg w_almost_full // 1 if FIFO is almost full
|
|
);
|
|
|
|
// Internal signals
|
|
wire [ADDR_SIZE:0] w_gray_next; // Gray code version of address
|
|
wire [ADDR_SIZE:0] w_bin_next; // Binary version of address
|
|
wire w_full_val; // FIFO is full
|
|
|
|
wire [ADDR_SIZE:0] w_almost_full_val8;
|
|
wire [ADDR_SIZE:0] w_almost_full_val7;
|
|
wire [ADDR_SIZE:0] w_almost_full_val6;
|
|
wire [ADDR_SIZE:0] w_almost_full_val5;
|
|
wire [ADDR_SIZE:0] w_almost_full_val4;
|
|
wire [ADDR_SIZE:0] w_almost_full_val3;
|
|
wire [ADDR_SIZE:0] w_almost_full_val2;
|
|
wire [ADDR_SIZE:0] w_almost_full_val1;
|
|
|
|
// Internal storage elements
|
|
reg [ADDR_SIZE:0] w_bin; // Registered binary address
|
|
|
|
// Drop extra most significant bit (MSb) for addressing into memory
|
|
assign w_addr = w_bin[ADDR_SIZE-1:0];
|
|
|
|
// Be ready with next (incremented) address (if inc set and not full)
|
|
assign w_bin_next = w_bin + (w_inc & ~w_full);
|
|
|
|
// Convert next binary address to Gray code value
|
|
assign w_gray_next = (w_bin_next >> 1) ^ w_bin_next;
|
|
|
|
// with this approach for making the "almost full" flag, the flag is
|
|
// active only when the available space is frame_size-1. In order not to
|
|
// complicate the code too much, the frame size is fixed to 8 and there
|
|
// are conditions for each of the lower values of the frame size so that
|
|
// the flag is kept active for all of them.
|
|
wire [ADDR_SIZE:0] w_bin_plus_8 = w_bin + 8;
|
|
wire [ADDR_SIZE:0] w_gray_plus_8 = (w_bin_plus_8 >> 1) ^ w_bin_plus_8;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_7 = w_bin + 7;
|
|
wire [ADDR_SIZE:0] w_gray_plus_7 = (w_bin_plus_7 >> 1) ^ w_bin_plus_7;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_6 = w_bin + 6;
|
|
wire [ADDR_SIZE:0] w_gray_plus_6 = (w_bin_plus_6 >> 1) ^ w_bin_plus_6;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_5 = w_bin + 5;
|
|
wire [ADDR_SIZE:0] w_gray_plus_5 = (w_bin_plus_5 >> 1) ^ w_bin_plus_5;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_4 = w_bin + 4;
|
|
wire [ADDR_SIZE:0] w_gray_plus_4 = (w_bin_plus_4 >> 1) ^ w_bin_plus_4;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_3 = w_bin + 3;
|
|
wire [ADDR_SIZE:0] w_gray_plus_3 = (w_bin_plus_3 >> 1) ^ w_bin_plus_3;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_2 = w_bin + 2;
|
|
wire [ADDR_SIZE:0] w_gray_plus_2 = (w_bin_plus_2 >> 1) ^ w_bin_plus_2;
|
|
|
|
wire [ADDR_SIZE:0] w_bin_plus_1 = w_bin + 1;
|
|
wire [ADDR_SIZE:0] w_gray_plus_1 = (w_bin_plus_1 >> 1) ^ w_bin_plus_1;
|
|
|
|
|
|
// Compare write Gray code to synced read Gray code to see if FIFO is full
|
|
// If: extra MSb of read and write Gray codes are not equal AND
|
|
// 2nd MSb of read and write Gray codes are not equal AND
|
|
// the rest of the bits are equal
|
|
// Then: address pointers are same with write pointer ahead by 2^ADDR_SIZE
|
|
// elements (i.e. wrapped around), so FIFO is full.
|
|
assign w_full_val = ((w_gray_next[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_next[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_next[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0]));
|
|
|
|
|
|
assign w_almost_full_val8 = (
|
|
(w_gray_plus_8[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_8[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_8[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val7 = (
|
|
(w_gray_plus_7[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_7[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_7[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val6 = (
|
|
(w_gray_plus_6[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_6[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_6[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val5 = (
|
|
(w_gray_plus_5[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_5[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_5[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val4 = (
|
|
(w_gray_plus_4[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_4[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_4[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val3 = (
|
|
(w_gray_plus_3[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_3[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_3[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val2 = (
|
|
(w_gray_plus_2[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_2[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_2[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
assign w_almost_full_val1 = (
|
|
(w_gray_plus_1[ADDR_SIZE] != w_syn_r_gray[ADDR_SIZE]) &&
|
|
(w_gray_plus_1[ADDR_SIZE-1] != w_syn_r_gray[ADDR_SIZE-1]) &&
|
|
(w_gray_plus_1[ADDR_SIZE-2:0] == w_syn_r_gray[ADDR_SIZE-2:0])
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Register the binary and Gray code pointers in the write clock domain
|
|
always @ (posedge w_clk or posedge w_rst) begin
|
|
if (w_rst == 1'b1) begin
|
|
w_bin <= 0;
|
|
w_gray <= 0;
|
|
end else begin
|
|
w_bin <= w_bin_next;
|
|
w_gray <= w_gray_next;
|
|
end
|
|
end
|
|
|
|
// Register the full flag
|
|
always @ (posedge w_clk or posedge w_rst) begin
|
|
if (w_rst == 1'b1) begin
|
|
w_full <= 1'b0;
|
|
w_almost_full <= 1'b0;
|
|
end else begin
|
|
w_full <= w_full_val;
|
|
w_almost_full <= w_almost_full_val8 | w_almost_full_val7 | w_almost_full_val6 | w_almost_full_val5 | w_almost_full_val4 | w_almost_full_val3 | w_almost_full_val2 | w_almost_full_val1 | w_full_val;
|
|
end
|
|
end
|
|
|
|
endmodule
|