Skip to content

Instantly share code, notes, and snippets.

@buttercutter
Last active December 5, 2018 01:32
Show Gist options
  • Save buttercutter/340152cd4f62aec0df53052273193920 to your computer and use it in GitHub Desktop.
Save buttercutter/340152cd4f62aec0df53052273193920 to your computer and use it in GitHub Desktop.
SPI flash controller for Winbond W25Q32 flash
module test_SPI(clk, rst, serial_in, serial_out, o_busy_n, rx_error_n, MISO, MOSI);
parameter MOSI_DATA_BITWIDTH = 8 + 24 + 256*8; // to accomodate the maximum bit length for page_program(02h)
parameter MISO_DATA_BITWIDTH = 8;
localparam UART_LENGTH = 8; // 8N1 uart system
localparam NUM_OF_MUX_BIT = 1;
// hex ASCII code
localparam START_CHARACTER = 'h7F; // del character
localparam INSTRUCTION_CHARACTER = 'h53; // 'S'
localparam ADDRESS_SIGNAL = 'h41;
localparam DATA_SIGNAL = 'h44;
localparam INTERRUPT_SIGNAL = 'h49;
localparam ACKNOWLEDGE_SIGNAL = 'h4B;
localparam RESET_SIGNAL = 'h54;
input clk, rst;
input serial_in;
output serial_out, o_busy_n, rx_error_n; // busy and error signals are negatively asserted due to LED diode terminal connection on my own FPGA board schematics
input MISO;
output MOSI;
wire CS_flash;
reg clk_flash;
wire [UART_LENGTH-1:0] uart_tx_data;
wire [UART_LENGTH-1:0] received_data; // UART Rx data
reg [UART_LENGTH-1:0] received_data_previous;
wire data_ready, data_valid;
wire [MISO_DATA_BITWIDTH-1:0] data_MISO;
reg [$clog2(MOSI_DATA_BITWIDTH)-1:0] data_MOSI;
// for power-up hardware reset as well as HOST(linux) software reset
wire reset = ((rst & !rst_previous) || ((received_data[0 +: UART_LENGTH-1-NUM_OF_MUX_BIT] == RESET_SIGNAL) && (received_data_previous[0 +: UART_LENGTH-1-NUM_OF_MUX_BIT] == START_CHARACTER))) ? 1 : 0;
reg rst_previous;
always @(posedge clk) rst_previous <= rst;
always @(posedge clk) received_data_previous <= received_data;
wire o_busy, rx_error;
assign o_busy_n = !o_busy;
assign rx_error_n = !rx_error;
UART uart(.tx_clk(clk), .rx_clk(clk), .reset_tx(reset), .reset_rx(reset), .serial_out(serial_out), .enable(data_ready), .i_data(uart_tx_data), .o_busy(o_busy), .serial_in(serial_in), .received_data(received_data), .data_is_valid(data_valid), .rx_error(rx_error));
// SPI flash controller
SPI spi(.clk(clk), .reset(reset), .data_valid(data_valid), .data_MOSI(data_MOSI), .data_ready(data_ready), .data_MISO(data_MISO), .MISO(MISO), .MOSI(MOSI), .clk_flash(clk_flash), .CS_flash(CS_flash));
// UART-to-SPI bridge details
/*
MUX is used to multiplex two different device endpoints.
MUX = 0 is used as console debug port
MUX = 1 is used for SPI-related transactions
Possible COMMAND: 'A'ddress, 'D'ata, 'I'nterrupt, ac'K'owledge, in'S'truction, rese'T'
Possible DATA range: all 7-bit binary characters (except DEL character) listed in the standard ASCII table
this scheme uses "NUM_OF_BYTES" which is basically the number of bytes required for page_program(02h) and read_data(03h) instructions
{1-bit MUX, 7-bit COMMAND or DATA in ASCII hex} occupy 8N1 UART
To start a transaction across the bridge, send the DEL character(which is of hex value 7F) first.
Then, send the in'S'truction character(which is of hex value 53), followed by optional ADDRESS and/or DATA bits
Note: I only implement write_enable(06h), sector_erase(20h), page_program(02h), read_data(03h) and read_status_register_1(05h) instructions
be careful with read_data(03h) and read_status_register_1(05h) instructions in which the DO bits are shifted out at negative clock edge right after the positive clock edge of
the LSB of 24-bit address
ADDRESS is used to indicate that the following bits are of 24-bit SPI slave(flash) address.
INTERRUPT is sent by SPI slave (flash) to let master (FPGA) knows that erase(20h), page program(02h) or write status register(01h) instructions had finished,
with the use of read status register(05h) instruction for checking whether busy bit is 0 or not.
ACKNOWLEDGE is used to ack signals from HOST.
DATA is used to indicate that the following bits are data or status register for reading/writing, the bits are transmitted to/from hardware(FPGA) from/to HOST.
RESET is used to let hardware(FPGA) knows that HOST softwware had reset itself, so as to formally inform the FPGA to do the same reset as well in hardware.
*/
// for page_program(02h) and read_data(03h) instructions only
localparam MAX_NUM_OF_DATA_BYTES_IN_A_SPI_TRANSACTION = 260; // for max data byte in page_program(02h)
reg [$clog2(MAX_NUM_OF_DATA_BYTES_IN_A_SPI_TRANSACTION)-1:0] TOTAL_NUM_OF_DATA_BYTES_IN_A_SPI_TRANSACTION; // dynamically determined from the information sent over the bridge
// bits from from hardware(FPGA) to HOST(linux)
always @(posedge clk)
begin
if(reset) begin
uart_tx_data <= {UART_LENGTH{1'b1}}; // reset to idle state
end
else begin
uart_tx_data <= ;
end
end
// bits from from HOST(linux) to hardware(FPGA)
always @(posedge clk)
begin
if(reset) begin
data_MOSI <= 0;
end
else begin
data_MOSI <= ;
end
end
always @(posedge clk)
begin
if(reset) begin
clk_flash <= 0;
end
else begin
clk_flash <= 1;
end
end
endmodule
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment