0
mirror of https://gitlab.com/hyperglitch/jellyfish.git synced 2025-11-09 21:27:59 +00:00
Igor Brkic b4a27f8ac4 add ADC sampling and data transfer
- 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
2025-04-27 21:36:04 +02:00

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