Communication
SPI controller
spi_master
SPI controller in mode 0: the clock idles low, data changes on the falling edge and is sampled on the rising edge, 8 bits, most significant first. Pulse start while busy is low to send tx_data and receive rx_data; done pulses when rx_data is ready. The SPI clock runs at clk / (2 × CLK_DIV).
Instantiate it
Paste into your design and connect the signals.
spi_master #(.CLK_DIV(4)) u_spi (
.clk(clk), .rst(rst),
.start(spi_start), .tx_data(spi_tx), .rx_data(spi_rx), .busy(spi_busy), .done(spi_done),
.sclk(spi_sclk), .mosi(spi_mosi), .miso(spi_miso), .cs_n(spi_cs_n)
);Parameters
| Name | Default | Meaning |
|---|---|---|
| CLK_DIV | 4 | Clock cycles per SPI clock half period (1 or more). |
Ports
| Name | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | Clock; everything changes on its rising edge. |
| rst | input | 1 | Synchronous reset, active high. |
| start | input | 1 | Pulse for one cycle to start an exchange (ignored while busy). |
| tx_data | input | 8 | Byte to send, read when start is high. |
| rx_data | output | 8 | Byte received in the last exchange. |
| busy | output | 1 | High during an exchange. |
| done | output | 1 | Pulses when rx_data is ready. |
| sclk | output | 1 | SPI clock to the peripheral. |
| mosi | output | 1 | Data to the peripheral. |
| miso | input | 1 | Data from the peripheral. |
| cs_n | output | 1 | Chip select, active low. |
Add it to a project
It is copied to ip/spi_master/spi_master.sv with a README (ip/spi_master/README.md). When your design needs it, the Architect (new designs) or the Architect (changes) suggests it and asks before using it. Or add it yourself:
- In the workspace: the Tapeo Hub button in the project header, then Add to project.
- In a terminal (in the project folder):
$ tapeo hub add spi_master - In VS Code: Tapeo: Add from Tapeo Hub in the Command Palette.
How it is verified
Before every release Tapeo checks this block: its self-checking testbench passes in Icarus Verilog, Verilator -Wall reports nothing, and Yosys synthesizes it (82 generic cells at the default parameters). That shows it works for the tested cases; your copy is part of your project, and your own testbench checks it in your design.
Source
// SPI controller, mode 0 (clock idles low; data changes on the falling edge
// and is sampled on the rising edge), 8 bits, most significant bit first.
// Pulse `start` while `busy` is low to exchange `tx_data` for `rx_data`;
// `done` pulses when `rx_data` is ready. The SPI clock runs at
// clk / (2 * CLK_DIV). Tapeo Hub (verified by Tapeo).
module spi_master #(
parameter integer CLK_DIV = 4 // clk cycles per SPI clock half period, 1 or more
) (
input logic clk,
input logic rst,
input logic start,
input logic [7:0] tx_data,
output logic [7:0] rx_data,
output logic busy,
output logic done,
output logic sclk,
output logic mosi,
input logic miso,
output logic cs_n
);
localparam integer COUNT_WIDTH = (CLK_DIV > 1) ? $clog2(CLK_DIV) : 1;
localparam logic [COUNT_WIDTH-1:0] LAST_TICK = COUNT_WIDTH'(CLK_DIV - 1);
typedef enum logic [1:0] { IDLE, TRANSFER, FINISH } state_t;
state_t state;
logic [COUNT_WIDTH-1:0] ticks;
logic [2:0] bit_index;
logic [7:0] shift_tx, shift_rx;
always_ff @(posedge clk) begin
if (rst) begin
state <= IDLE;
ticks <= '0;
bit_index <= '0;
shift_tx <= '0;
shift_rx <= '0;
rx_data <= '0;
sclk <= 1'b0;
cs_n <= 1'b1;
done <= 1'b0;
end else begin
done <= 1'b0;
case (state)
IDLE: begin
sclk <= 1'b0;
cs_n <= 1'b1;
if (start) begin
cs_n <= 1'b0;
shift_tx <= tx_data;
bit_index <= '0;
ticks <= '0;
state <= TRANSFER;
end
end
TRANSFER: begin
if (ticks == LAST_TICK) begin
ticks <= '0;
if (!sclk) begin
sclk <= 1'b1; // rising edge: sample
shift_rx <= {shift_rx[6:0], miso};
end else begin
sclk <= 1'b0; // falling edge: next bit
if (bit_index == 3'd7) state <= FINISH;
else begin
bit_index <= bit_index + 1'b1;
shift_tx <= {shift_tx[6:0], 1'b0};
end
end
end else ticks <= ticks + 1'b1;
end
FINISH: begin
cs_n <= 1'b1;
rx_data <= shift_rx;
done <= 1'b1;
state <= IDLE;
end
default: state <= IDLE;
endcase
end
end
assign busy = (state != IDLE);
assign mosi = shift_tx[7]; // the bit being sent; ignored while cs_n is high
endmodule