From 3bc24532878d560d8552fb1ee2f8b7ea1c2e8562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joachim=20Str=C3=B6mbergson?= Date: Tue, 16 May 2023 16:14:21 +0200 Subject: [PATCH] A construction of a minimal SPI master. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- contrib/Makefile | 5 + hw/application_fpga/Makefile | 15 +- hw/application_fpga/core/tk1/README.md | 54 +++ hw/application_fpga/core/tk1/rtl/tk1.v | 79 ++++- .../core/tk1/rtl/tk1_spi_master.v | 327 ++++++++++++++++++ .../core/tk1/toolruns/Makefile | 4 + .../data/application_fpga_tk1.pcf | 7 + hw/application_fpga/fw/tk1_mem.h | 4 + hw/application_fpga/rtl/application_fpga.v | 14 + 9 files changed, 506 insertions(+), 3 deletions(-) create mode 100644 hw/application_fpga/core/tk1/rtl/tk1_spi_master.v diff --git a/contrib/Makefile b/contrib/Makefile index e38a0ce..85de386 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -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 diff --git a/hw/application_fpga/Makefile b/hw/application_fpga/Makefile index a50eb94..a32203c 100644 --- a/hw/application_fpga/Makefile +++ b/hw/application_fpga/Makefile @@ -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, $^) diff --git a/hw/application_fpga/core/tk1/README.md b/hw/application_fpga/core/tk1/README.md index ee46e90..9d9df18 100644 --- a/hw/application_fpga/core/tk1/README.md +++ b/hw/application_fpga/core/tk1/README.md @@ -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 from 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 diff --git a/hw/application_fpga/core/tk1/rtl/tk1.v b/hw/application_fpga/core/tk1/rtl/tk1.v index bd45642..12b64c7 100644 --- a/hw/application_fpga/core/tk1/rtl/tk1.v +++ b/hw/application_fpga/core/tk1/rtl/tk1.v @@ -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 diff --git a/hw/application_fpga/core/tk1/rtl/tk1_spi_master.v b/hw/application_fpga/core/tk1/rtl/tk1_spi_master.v new file mode 100644 index 0000000..95a2d54 --- /dev/null +++ b/hw/application_fpga/core/tk1/rtl/tk1_spi_master.v @@ -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 +//====================================================================== diff --git a/hw/application_fpga/core/tk1/toolruns/Makefile b/hw/application_fpga/core/tk1/toolruns/Makefile index b7b4bff..401b50f 100755 --- a/hw/application_fpga/core/tk1/toolruns/Makefile +++ b/hw/application_fpga/core/tk1/toolruns/Makefile @@ -37,6 +37,10 @@ lint-top: $(LINT_SRC) $(LINT) $(LINT_FLAGS) $(LINT_SRC) +lint-spi: $(SPI_SRC) + $(LINT) $(LINT_FLAGS) $^ + + clean: rm -f top.sim diff --git a/hw/application_fpga/data/application_fpga_tk1.pcf b/hw/application_fpga/data/application_fpga_tk1.pcf index c22b7fa..df4541b 100644 --- a/hw/application_fpga/data/application_fpga_tk1.pcf +++ b/hw/application_fpga/data/application_fpga_tk1.pcf @@ -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 diff --git a/hw/application_fpga/fw/tk1_mem.h b/hw/application_fpga/fw/tk1_mem.h index 36ffdc2..5b58b7c 100644 --- a/hw/application_fpga/fw/tk1_mem.h +++ b/hw/application_fpga/fw/tk1_mem.h @@ -141,4 +141,8 @@ #define TK1_MMIO_TK1_CPU_MON_CTRL 0xff000180 #define TK1_MMIO_TK1_CPU_MON_FIRST 0xff000184 #define TK1_MMIO_TK1_CPU_MON_LAST 0xff000188 + +#define TK1_MMIO_TK1_SPI_EN 0xff000200 +#define TK1_MMIO_TK1_SPI_XFER 0xff000204 +#define TK1_MMIO_TK1_SPI_DATA 0xff000208 #endif diff --git a/hw/application_fpga/rtl/application_fpga.v b/hw/application_fpga/rtl/application_fpga.v index 4f8b5e5..3f7b5df 100644 --- a/hw/application_fpga/rtl/application_fpga.v +++ b/hw/application_fpga/rtl/application_fpga.v @@ -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),