Verilog Implementation: FIFO to SPI Bridge
This design functions as an efficient hardware bridge interfacing an upstream synchronous data FIFO with a downstream SPI peripheral device. The system automatically fetches data bytes from the FIFO when available, converts them from parallel to serial, and handles full transaction execution using standard SPI Mode 0 (CPOL=0, CPHA=0).
1. Architectural Block Diagram
[ Upstream FIFO ] [ FIFO to SPI Bridge ] [ Downstream SPI ]
┌───────────────┐ ┌──────────────────────┐ ┌────────────────┐
│ empty ├───────────────────>│ empty │ │ │
│ rd_data[7:0]├───────────────────>│ fifo_data[7:0] │ │ │
│ rd_en │<───────────────────┤ fifo_rd_en sclk├────────────────>│SCLK │
└───────────────┘ │ mosi├────────────────>│MOSI │
│ ss├────────────────>│SS │
└──────────────────────┘ └────────────────┘
2. Bridge RTL Source Code (fifo_to_spi_bridge.v)
// ============================================================================
// Module Name: fifo_to_spi_bridge
// Description: Bridge that reads 8-bit data from a FIFO and transmits it
// over a standard SPI interface (SPI Mode 0: CPOL=0, CPHA=0).
// ============================================================================
module fifo_to_spi_bridge #(
parameter CLK_DIVIDE = 4 // System clock division factor for SCLK generation
)(
// Clock and Reset
input wire clk, // System Clock
input wire rst_n, // Active Low Asynchronous Reset
// FIFO Interface
input wire [7:0] fifo_data, // 8-bit parallel data from FIFO
input wire fifo_empty, // Empty flag from upstream FIFO
output reg fifo_rd_en, // Read enable strobe to FIFO
// SPI Interface
output reg spi_sclk, // SPI Serial Clock
output reg spi_mosi, // SPI Master Out Slave In
output reg spi_ss // SPI Slave Select / Chip Select (Active Low)
);
// --- State Machine Definitions ---
localparam STATE_IDLE = 3'b000;
localparam STATE_FETCH = 3'b001;
localparam STATE_WAIT_FIFO = 3'b010;
localparam STATE_TX_BIT = 3'b011;
localparam STATE_DONE = 3'b100;
reg [2:0] current_state, next_state;
// --- Internal Registers & Counters ---
reg [7:0] shift_reg; // Parallel-to-serial shift register
reg [2:0] bit_counter; // Tracks which of the 8 bits is transmitting
reg [7:0] sclk_divider; // Clock divider counter for SCLK generation
reg sclk_en; // Enables internal SCLK ticking
reg sclk_edge_toggle; // Toggles on every half-period of SCLK
// --- Clock Divider logic for SPI SCLK ---
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk_divider <= 0;
sclk_edge_toggle <= 0;
end else if (sclk_en) begin
if (sclk_divider == (CLK_DIVIDE/2) - 1) begin
sclk_divider <= 0;
sclk_edge_toggle <= 1'b1;
end else begin
sclk_divider <= sclk_divider + 1;
sclk_edge_toggle <= 1'b0;
end
end else begin
sclk_divider <= 0;
sclk_edge_toggle <= 1'b0;
end
end
// --- State Machine Sequential Logic ---
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= STATE_IDLE;
end else begin
current_state <= next_state;
end
end
// --- State Machine Combinational Logic ---
always @(*) begin
next_state = current_state;
case (current_state)
STATE_IDLE: begin
if (!fifo_empty) next_state = STATE_FETCH;
end
STATE_FETCH: next_state = STATE_WAIT_FIFO;
STATE_WAIT_FIFO: next_state = STATE_TX_BIT;
STATE_TX_BIT: begin
if (bit_counter == 3'd7 && spi_sclk == 1'b1 && sclk_edge_toggle)
next_state = STATE_DONE;
end
STATE_DONE: begin
if (sclk_edge_toggle) next_state = STATE_IDLE;
end
default: next_state = STATE_IDLE;
endcase
end
// --- Datapath Control ---
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
fifo_rd_en <= 1'b0;
shift_reg <= 8'h00;
bit_counter <= 3'd0;
sclk_en <= 1'b0;
spi_sclk <= 1'b0;
spi_mosi <= 1'b0;
spi_ss <= 1'b1;
end else begin
case (current_state)
STATE_IDLE: begin
fifo_rd_en <= 1'b0;
bit_counter <= 3'd0;
sclk_en <= 1'b0;
spi_sclk <= 1'b0;
spi_mosi <= 1'b0;
spi_ss <= 1'b1;
end
STATE_FETCH: begin
fifo_rd_en <= 1'b1;
end
STATE_WAIT_FIFO: begin
fifo_rd_en <= 1'b0;
shift_reg <= fifo_data;
spi_ss <= 1'b0;
end
STATE_TX_BIT: begin
sclk_en <= 1'b1;
if (sclk_edge_toggle) begin
if (spi_sclk == 1'b0) begin
spi_sclk <= 1'b1;
end else begin
spi_sclk <= 1'b0;
shift_reg <= {shift_reg[6:0], 1'b0};
bit_counter <= bit_counter + 1;
end
end
spi_mosi <= shift_reg[7];
end
STATE_DONE: begin
if (sclk_edge_toggle) begin
sclk_en <= 1'b0;
spi_ss <= 1'b1;
spi_mosi <= 1'b0;
end
end
endcase
end
end
endmodule
3. Testbench Simulation (tb_fifo_to_spi_bridge.v)
`timescale 1ns / 1ps
module tb_fifo_to_spi_bridge;
// Inputs
reg clk;
reg rst_n;
reg [7:0] fifo_data;
reg fifo_empty;
// Outputs
wire fifo_rd_en;
wire spi_sclk;
wire spi_mosi;
wire spi_ss;
// Instantiate Unit Under Test (UUT)
fifo_to_spi_bridge #(
.CLK_DIVIDE(4)
) uut (
.clk(clk),
.rst_n(rst_n),
.fifo_data(fifo_data),
.fifo_empty(fifo_empty),
.fifo_rd_en(fifo_rd_en),
.spi_sclk(spi_sclk),
.spi_mosi(spi_mosi),
.spi_ss(spi_ss)
);
// Generate Clock (50MHz -> Period = 20ns)
always #10 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
fifo_data = 8'h00;
fifo_empty = 1;
#40;
rst_n = 1;
#40;
// Transmit Data Byte 0xA5
wait(clk);
fifo_data = 8'hA5;
fifo_empty = 0;
@(posedge fifo_rd_en);
@(negedge clk);
fifo_empty = 1;
@(posedge spi_ss);
#100;
$finish;
end
endmodule
4. Architectural & Optimization Guidelines
- Deterministic Handshaking: The structural data capture routing takes place precisely during the
STATE_WAIT_FIFOcycle, securing high-speed immunity against downstream glitches or unexpected timing changes on the external FIFO lines. - Scalable Clock Dividers: The
CLK_DIVIDEdesign parameter allows engineers to adapt serial output frequencies to meet the timing restrictions of various targeted host chips without breaking the main system fabric layout.
No comments:
Post a Comment