A construction of a minimal SPI master.

- NOTE: This is an optional feature, not built by default. Not included
  in the tk1 for sale at Tillitis shop.
- This makes it possible to interface the SPI flash onboard TKey.
- To include the SPI master in the build, use `make application_fpga.bin
  YOSYS_FLAG=-DINCLUDE_SPI_MASTER`.

Signed-off-by: Joachim Strömbergson <joachim@assured.se>
This commit is contained in:
Joachim Strömbergson 2023-05-16 16:14:21 +02:00 committed by dehanj
parent e961f46e79
commit 59b6f897fb
No known key found for this signature in database
GPG Key ID: 3707A9DBF4BB8F1A
8 changed files with 502 additions and 3 deletions

View File

@ -33,6 +33,11 @@ run-make:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean application_fpga.bin
run-make-spi:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make clean application_fpga.bin YOSYS_FLAG=-DINCLUDE_SPI_MASTER
run-tb:
podman run --rm --mount type=bind,source="`pwd`/../hw/application_fpga",target=/build -w /build -it \
$(IMAGE) make tb

View File

@ -68,6 +68,7 @@ VERILOG_SRCS = \
$(P)/core/uds/rtl/uds_rom.v \
$(P)/core/touch_sense/rtl/touch_sense.v \
$(P)/core/tk1/rtl/tk1.v \
$(P)/core/tk1/rtl/tk1_spi_master.v \
$(P)/core/tk1/rtl/udi_rom.v \
$(P)/core/uart/rtl/uart_core.v \
$(P)/core/uart/rtl/uart_fifo.v \
@ -236,9 +237,19 @@ tb:
#-------------------------------------------------------------------
# Main FPGA build flow.
# Synthesis. Place & Route. Bitstream generation.
#
# To include the SPI-master, add the flag -DINCLUDE_SPI_MASTER to Yosys cmd.
# This can, for example, be done using
# 'make application_fpga.bin YOSYS_FLAG=-DINCLUDE_SPI_MASTER'.
# Important: do a make clean between builds with and wihtout the SPI master.
# Otherwise, there is a risk of unintended components persisting between
# builds.
#-------------------------------------------------------------------
synth.json: $(FPGA_SRC) $(VERILOG_SRCS) bram_fw.hex
$(YOSYS_PATH)yosys -v3 -l synth.log -DBRAM_FW_SIZE=$(BRAM_FW_SIZE) \
YOSYS_FLAG ?=
synth.json: $(FPGA_SRC) $(VERILOG_SRCS) bram_fw.hex $(P)/data/uds.hex $(P)/data/udi.hex
$(YOSYS_PATH)yosys -v3 -l synth.log $(YOSYS_FLAG) -DBRAM_FW_SIZE=$(BRAM_FW_SIZE) \
-DFIRMWARE_HEX=\"$(P)/bram_fw.hex\" \
-p 'synth_ice40 -dsp -top application_fpga -json $@; write_verilog -attr2comment synth.v' \
$(filter %.v, $^)

View File

@ -190,6 +190,60 @@ core will detect that and start flashing the status LED with a red
light indicating that the CPU is in a trapped state and no further
execution is possible.
## SPI-master
The TK1 includes a minimal SPI-master that provides access to the
Winbond Flash memory mounted on the board. The SPI-master is byte
oriented and very minimalistic.
In order to transfer more than a single byte, SW must read status and
write commands needed to send a sequence of bytes. In order to read
out a sequence of bytes from the memory, SW must send as many dummy
bytes as the data being read from the memory.
The SPI-master is controlled using a few API
addresses:
```
localparam ADDR_SPI_EN = 8'h80;
localparam ADDR_SPI_XFER = 8'h81;
localparam ADDR_SPI_DATA = 8'h82;
```
**ADDR_SPI_EN** enables and disabled the SPI-master. Writing a 0x01 will
lower the SPI chip select to the memory. Writing a 0x00 will raise the
chip select.
Writing to the **ADDR_SPI_XFER** starts a byte transfer. Reading from
the address returns the status for the SPI-master. If the return value
is not zero, the SPI-master is ready to send a byte.
**ADDR_SPI_DATA** is the address used to send and receive a byte.
data. The least significant byte will be sent to the memory during a
transfer. The byte returned from the memory will be presented to SW if
the address is read after a transfer has completed.
The sequence of operations needed to perform is thus:
1. Activate the SPI-master by writing a 0x00000001 to ADDR_SPI_EN
2. Write a byte to ADDR_SPI_DATA
3. Read ADDR_SPI_XFER to check status. Repeat until the read
operation returns non-zero value
4. Write to ADDR_SPI_XFER
5. Read ADDR_SPI_XFER to check status. Repeat until the read operation
returns a non-zero value
6. Read out the received byte frm ADDR_SPI_DATA
7. Repeat 2..6 as many times as needed to send a command and data to
the memory and getting the expected status, data back.
8. Deactivate the SPI-master by writing 0x00000000 to ADDR_SPI_EN
The SPI connected memory on the board is the Winbond W25Q80. For
information about the memory including support commands and protocol,
see the datasheet:
https://www.mouser.se/datasheet/2/949/w25q80dv_dl_revh_10022015-1489677.pdf
## Implementation
The core is implemented as a single module. Future versions will

View File

@ -28,6 +28,13 @@ module tk1(
output wire [14 : 0] ram_aslr,
output wire [31 : 0] ram_scramble,
`ifdef INCLUDE_SPI_MASTER
output wire spi_ss,
output wire spi_sck,
output wire spi_mosi,
input wire spi_miso,
`endif // INCLUDE_SPI_MASTER
output wire led_r,
output wire led_g,
output wire led_b,
@ -86,6 +93,11 @@ module tk1(
localparam ADDR_CPU_MON_FIRST = 8'h61;
localparam ADDR_CPU_MON_LAST = 8'h62;
`ifdef INCLUDE_SPI_MASTER
localparam ADDR_SPI_EN = 8'h80;
localparam ADDR_SPI_XFER = 8'h81;
localparam ADDR_SPI_DATA = 8'h82;
`endif // INCLUDE_SPI_MASTER
localparam TK1_NAME0 = 32'h746B3120; // "tk1 "
localparam TK1_NAME1 = 32'h6d6b6466; // "mkdf"
@ -157,6 +169,17 @@ module tk1(
wire [31:0] udi_rdata;
`ifdef INCLUDE_SPI_MASTER
reg spi_enable;
reg spi_enable_vld;
reg spi_start;
reg [7 : 0] spi_tx_data;
reg spi_tx_data_vld;
wire spi_ready;
wire [7 : 0] spi_rx_data;
`endif // INCLUDE_SPI_MASTER
//----------------------------------------------------------------
// Concurrent connectivity for ports etc.
//----------------------------------------------------------------
@ -195,6 +218,26 @@ module tk1(
);
/* verilator lint_on PINMISSING */
`ifdef INCLUDE_SPI_MASTER
tk1_spi_master spi_master(
.clk(clk),
.reset_n(reset_n),
.spi_ss(spi_ss),
.spi_sck(spi_sck),
.spi_mosi(spi_mosi),
.spi_miso(spi_miso),
.spi_enable(spi_enable),
.spi_enable_vld(spi_enable_vld),
.spi_start(spi_start),
.spi_tx_data(spi_tx_data),
.spi_tx_data_vld(spi_tx_data_vld),
.spi_rx_data(spi_rx_data),
.spi_ready(spi_ready)
);
`endif // INCLUDE_SPI_MASTER
udi_rom rom_i(
.addr(address[0]),
@ -393,6 +436,15 @@ module tk1(
tmp_read_data = 32'h0;
tmp_ready = 1'h0;
`ifdef INCLUDE_SPI_MASTER
spi_enable_vld = 1'h0;
spi_start = 1'h0;
spi_tx_data_vld = 1'h0;
spi_enable = write_data[0];
spi_tx_data = write_data[7 : 0];
`endif // INCLUDE_SPI_MASTER
if (cs) begin
tmp_ready = 1'h1;
if (we) begin
@ -460,8 +512,22 @@ module tk1(
cpu_mon_last_we = 1'h1;
end
end
end
`ifdef INCLUDE_SPI_MASTER
if (address == ADDR_SPI_EN) begin
spi_enable_vld = 1'h1;
end
if (address == ADDR_SPI_XFER) begin
spi_start = 1'h1;
end
if (address == ADDR_SPI_DATA) begin
spi_tx_data_vld = 1'h1;
end
`endif // INCLUDE_SPI_MASTER
end
else begin
if (address == ADDR_NAME0) begin
tmp_read_data = TK1_NAME0;
@ -509,6 +575,17 @@ module tk1(
tmp_read_data = udi_rdata;
end
end
`ifdef INCLUDE_SPI_MASTER
if (address == ADDR_SPI_XFER) begin
tmp_read_data[0] = spi_ready;
end
if (address == ADDR_SPI_DATA) begin
tmp_read_data[7 : 0] = spi_rx_data;
end
`endif // INCLUDE_SPI_MASTER
end
end
end // api

View File

@ -0,0 +1,327 @@
//======================================================================
//
// tk1_spi_master.v
// ----------------
// Minimal SPI master to be integrated into the tk1 module.
// The SPI master is able to generate a clock, and transfer,
// exchange a single byte with the slave.
//
// This master is compatible with the Winbond W25Q80DV memory.
// This means that MSB of a response from the memory is provided
// on the falling clock edge on the LSB of the command byte, not
// on a dummy byte. This means that the response spans the boundary
// of the bytes, The core handles this by sampling the MISO
// just prior to settint the positive clock flank at the start
// of a byte transfer.
//
//
// Author: Joachim Strombergson
// Copyright (C) 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
//
//======================================================================
`default_nettype none
module tk1_spi_master(
input wire clk,
input wire reset_n,
output wire spi_ss,
output wire spi_sck,
output wire spi_mosi,
input wire spi_miso,
input wire spi_enable,
input wire spi_enable_vld,
input wire spi_start,
input wire [7 : 0] spi_tx_data,
input wire spi_tx_data_vld,
output wire [7 : 0] spi_rx_data,
output wire spi_ready
);
//----------------------------------------------------------------
// Internal constant and parameter definitions.
//----------------------------------------------------------------
parameter CTRL_IDLE = 3'h0;
parameter CTRL_POS_FLANK = 3'h1;
parameter CTRL_WAIT_POS = 3'h2;
parameter CTRL_NEG_FLANK = 3'h3;
parameter CTRL_WAIT_NEG = 3'h4;
parameter CTRL_NEXT = 3'h5;
//----------------------------------------------------------------
// Registers including update variables and write enable.
//----------------------------------------------------------------
reg spi_ss_reg;
reg spi_csk_reg;
reg spi_csk_new;
reg spi_csk_we;
reg [7 : 0] spi_tx_data_reg;
reg [7 : 0] spi_tx_data_new;
reg spi_tx_data_nxt;
reg spi_tx_data_we;
reg [7 : 0] spi_rx_data_reg;
reg [7 : 0] spi_rx_data_new;
reg spi_rx_data_nxt;
reg spi_rx_data_we;
reg spi_miso_sample_reg;
reg [3 : 0] spi_clk_ctr_reg;
reg [3 : 0] spi_clk_ctr_new;
reg spi_clk_ctr_rst;
reg [2 : 0] spi_bit_ctr_reg;
reg [2 : 0] spi_bit_ctr_new;
reg spi_bit_ctr_rst;
reg spi_bit_ctr_inc;
reg spi_bit_ctr_we;
reg spi_ready_reg;
reg spi_ready_new;
reg spi_ready_we;
reg [2 : 0] spi_ctrl_reg;
reg [2 : 0] spi_ctrl_new;
reg spi_ctrl_we;
//----------------------------------------------------------------
// Concurrent connectivity for ports etc.
//----------------------------------------------------------------
assign spi_ss = spi_ss_reg;
assign spi_sck = spi_csk_reg;
assign spi_mosi = spi_tx_data_reg[7];
assign spi_rx_data = spi_rx_data_reg;
assign spi_ready = spi_ready_reg;
//----------------------------------------------------------------
// reg_update
//----------------------------------------------------------------
always @ (posedge clk)
begin : reg_update
if (!reset_n) begin
spi_ss_reg <= 1'h1;
spi_csk_reg <= 1'h0;
spi_miso_sample_reg <= 1'h0;
spi_tx_data_reg <= 8'h0;
spi_rx_data_reg <= 8'h0;
spi_clk_ctr_reg <= 4'h0;
spi_bit_ctr_reg <= 3'h0;
spi_ready_reg <= 1'h1;
spi_ctrl_reg <= CTRL_IDLE;
end
else begin
spi_miso_sample_reg <= spi_miso;
spi_clk_ctr_reg <= spi_clk_ctr_new;
if (spi_enable_vld) begin
spi_ss_reg <= ~spi_enable;
end
if (spi_csk_we) begin
spi_csk_reg <= spi_csk_new;
end
if (spi_tx_data_we) begin
spi_tx_data_reg <= spi_tx_data_new;
end
if (spi_rx_data_we) begin
spi_rx_data_reg <= spi_rx_data_new;
end
if (spi_ready_we) begin
spi_ready_reg <= spi_ready_new;
end
if (spi_bit_ctr_we) begin
spi_bit_ctr_reg <= spi_bit_ctr_new;
end
if (spi_ctrl_we) begin
spi_ctrl_reg <= spi_ctrl_new;
end
end
end // reg_update
//----------------------------------------------------------------
// clk_ctr
//
// Continuously running clock cycle counter that can be
// reset to zero.
//----------------------------------------------------------------
always @*
begin : clk_ctr
if (spi_clk_ctr_rst) begin
spi_clk_ctr_new = 4'h0;
end
else begin
spi_clk_ctr_new = spi_clk_ctr_reg + 1'h1;
end
end
//----------------------------------------------------------------
// bit_ctr
//----------------------------------------------------------------
always @*
begin : bit_ctr
spi_bit_ctr_new = 3'h0;
spi_bit_ctr_we = 1'h0;
if (spi_bit_ctr_rst) begin
spi_bit_ctr_new = 3'h0;
spi_bit_ctr_we = 1'h1;
end
else if (spi_bit_ctr_inc) begin
spi_bit_ctr_new = spi_bit_ctr_reg + 1'h1;
spi_bit_ctr_we = 1'h1;
end
end
//----------------------------------------------------------------
// spi_tx_data_logic
// Logic for the tx_data shift register.
// Either load or shift the data register.
//----------------------------------------------------------------
always @*
begin : spi_tx_data_logic
spi_tx_data_new = 8'h0;
spi_tx_data_we = 1'h0;
if (spi_tx_data_vld) begin
if (spi_ready_reg) begin
spi_tx_data_new = spi_tx_data;
spi_tx_data_we = 1'h1;
end
end
if (spi_tx_data_nxt) begin
spi_tx_data_new = {spi_tx_data_reg[6 : 0], 1'h0};
spi_tx_data_we = 1'h1;
end
end
//----------------------------------------------------------------
// spi_rx_data_logic
// Logic for the rx_data shift register.
//----------------------------------------------------------------
always @*
begin : spi_rx_data_logic
spi_rx_data_new = 8'h0;
spi_rx_data_we = 1'h0;
if (spi_ss) begin
spi_rx_data_new = 8'h0;
spi_rx_data_we = 1'h1;
end
else if (spi_rx_data_nxt) begin
spi_rx_data_new = {spi_rx_data_reg[6 : 0], spi_miso_sample_reg};
spi_rx_data_we = 1'h1;
end
end
//----------------------------------------------------------------
// spi_master_ctrl
//----------------------------------------------------------------
always @*
begin : spi_master_ctrl
spi_rx_data_nxt = 1'h0;
spi_tx_data_nxt = 1'h0;
spi_clk_ctr_rst = 1'h0;
spi_csk_new = 1'h0;
spi_csk_we = 1'h0;
spi_bit_ctr_rst = 1'h0;
spi_bit_ctr_inc = 1'h0;
spi_ready_new = 1'h0;
spi_ready_we = 1'h0;
spi_ctrl_new = CTRL_IDLE;
spi_ctrl_we = 1'h0;
case (spi_ctrl_reg)
CTRL_IDLE: begin
if (spi_start) begin
spi_csk_new = 1'h0;
spi_csk_we = 1'h1;
spi_bit_ctr_rst = 1'h1;
spi_ready_new = 1'h0;
spi_ready_we = 1'h1;
spi_ctrl_new = CTRL_POS_FLANK;
spi_ctrl_we = 1'h1;
end
end
CTRL_POS_FLANK: begin
spi_rx_data_nxt = 1'h1;
spi_csk_new = 1'h1;
spi_csk_we = 1'h1;
spi_clk_ctr_rst = 1'h1;
spi_ctrl_new = CTRL_WAIT_POS;
spi_ctrl_we = 1'h1;
end
CTRL_WAIT_POS: begin
if (spi_clk_ctr_reg == 4'hf) begin
spi_ctrl_new = CTRL_NEG_FLANK;
spi_ctrl_we = 1'h1;
end
end
CTRL_NEG_FLANK: begin
spi_csk_new = 1'h0;
spi_csk_we = 1'h1;
spi_clk_ctr_rst = 1'h1;
spi_ctrl_new = CTRL_WAIT_NEG;
spi_ctrl_we = 1'h1;
end
CTRL_WAIT_NEG: begin
if (spi_clk_ctr_reg == 4'hf) begin
spi_ctrl_new = CTRL_NEXT;
spi_ctrl_we = 1'h1;
end
end
CTRL_NEXT: begin
if (spi_bit_ctr_reg == 3'h7) begin
spi_ready_new = 1'h1;
spi_ready_we = 1'h1;
spi_ctrl_new = CTRL_IDLE;
spi_ctrl_we = 1'h1;
end
else begin
spi_tx_data_nxt = 1'h1;
spi_bit_ctr_inc = 1'h1;
spi_ctrl_new = CTRL_POS_FLANK;
spi_ctrl_we = 1'h1;
end
end
default: begin
end
endcase // case (spi_ctrl_reg)
end
endmodule // tk1_spi_master
//======================================================================
// EOF tk1_spi_master.v
//======================================================================

View File

@ -36,6 +36,10 @@ lint-top: $(TOP_SRC)
$(LINT) $(LINT_FLAGS) $(TOP_SRC)
lint-spi: $(SPI_SRC)
$(LINT) $(LINT_FLAGS) $^
clean:
rm -f top.sim

View File

@ -18,6 +18,13 @@ set_io interface_tx 25
# set_io interface_rts 28
# SPI master to flash memory.
set_io spi_miso 17
set_io spi_sck 15
set_io spi_ss 16
set_io spi_mosi 14
# Touch sense.
set_io touch_event 6

View File

@ -20,6 +20,13 @@ module application_fpga(
output wire interface_rx,
input wire interface_tx,
`ifdef INCLUDE_SPI_MASTER
output wire spi_ss,
output wire spi_sck,
output wire spi_mosi,
input wire spi_miso,
`endif // INCLUDE_SPI_MASTER
input wire touch_event,
input wire app_gpio1,
@ -317,6 +324,13 @@ module application_fpga(
.ram_aslr(ram_aslr),
.ram_scramble(ram_scramble),
`ifdef INCLUDE_SPI_MASTER
.spi_ss(spi_ss),
.spi_sck(spi_sck),
.spi_mosi(spi_mosi),
.spi_miso(spi_miso),
`endif // INCLUDE_SPI_MASTER
.led_r(led_r),
.led_g(led_g),
.led_b(led_b),