This commit is contained in:
Daniel Jobson 2024-05-13 10:36:06 +00:00 committed by GitHub
commit ded1e5f6a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1349 additions and 10 deletions

2
.gitignore vendored
View File

@ -60,6 +60,6 @@ fp-info-cache
*.net
*.dsn
*.ses
__pycache__
application_fpga_par.json
MEM.TXT

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,9 +190,76 @@ 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
probably be separated into separate modules.
## Winbond Flash memory model
The testbench for the SPI master requires a memory model of the
Winbond Flash memory. The model [can be downloaded from
Winbond](https://www.winbond.com/hq/support/documentation/downloadV2022.jsp?__locale=en&xmlPath=/support/resources/.content/item/DA02-KAG049.html&level=2)
by providing the requested information and supplying the received
verification code.
From the downloaded file 'W25Q80DL.zip', please extract the file
W25Q80DL.v and place it in the 'tb' directory before building the
simulation model.
---

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

@ -58,6 +58,10 @@ module tb_tk1();
localparam ADDR_CPU_MON_FIRST = 8'h61;
localparam ADDR_CPU_MON_LAST = 8'h62;
localparam ADDR_SPI_EN = 8'h80;
localparam ADDR_SPI_XFER = 8'h81;
localparam ADDR_SPI_DATA = 8'h82;
//----------------------------------------------------------------
// Register and Wire declarations.
@ -89,6 +93,11 @@ module tb_tk1();
wire tb_gpio3;
wire tb_gpio4;
wire tb_spi_ss;
wire tb_spi_sck;
wire tb_spi_mosi;
wire tb_spi_miso;
reg tb_cs;
reg tb_we;
reg [7 : 0] tb_address;
@ -96,6 +105,12 @@ module tb_tk1();
wire [31 : 0] tb_read_data;
wire tb_ready;
//----------------------------------------------------------------
// Continuous assignments.
//----------------------------------------------------------------
// Inverted loopback of SPI data lines.
assign tb_spi_miso = ~tb_spi_mosi;
//----------------------------------------------------------------
// Device Under Test.
@ -124,6 +139,11 @@ module tb_tk1();
.gpio3(tb_gpio3),
.gpio4(tb_gpio4),
.spi_ss(tb_spi_ss),
.spi_sck(tb_spi_sck),
.spi_mosi(tb_spi_mosi),
.spi_miso(tb_spi_miso),
.cs(tb_cs),
.we(tb_we),
.address(tb_address),
@ -614,7 +634,38 @@ module tb_tk1();
$display("--- test9: completed.");
$display("");
end
endtask // test8
endtask // test9
//----------------------------------------------------------------
// test10()
// SPI master loopback test.
//----------------------------------------------------------------
task test10;
begin
tc_ctr = tc_ctr + 1;
$display("");
$display("--- test10: Loopback in SPI Master started.");
$display("--- test10: Sending a byte.");
write_word(ADDR_SPI_EN, 32'h1);
write_word(ADDR_SPI_DATA, 32'ha7);
write_word(ADDR_SPI_XFER, 32'h1);
while (!dut.spi_ready) begin
#(CLK_PERIOD);
end
$display("--- test10: Byte should have been sent.");
write_word(ADDR_SPI_EN, 32'h0);
// 0x58 is the inverse of 0xa7.
read_word(ADDR_SPI_DATA, 32'h58);
$display("--- test10: completed.");
$display("");
end
endtask // test10
//----------------------------------------------------------------
@ -639,6 +690,8 @@ module tb_tk1();
test7();
test8();
test9();
test9();
test10();
display_test_result();
$display("");

View File

@ -0,0 +1,732 @@
//======================================================================
//
// tb_tk1_spi_master.v
// -------------------
// Testbench for the TK1_SPI_MASTER core.
//
//
// Author: Joachim Strombergson
// Copyright (C) 2023 - Tillitis AB
// SPDX-License-Identifier: GPL-2.0-only
//
//======================================================================
`default_nettype none
`timescale 1ns / 1ns
module tb_tk1_spi_master();
//----------------------------------------------------------------
// Internal constant and parameter definitions.
//----------------------------------------------------------------
parameter DEBUG = 1;
parameter CLK_HALF_PERIOD = 1;
parameter CLK_PERIOD = 2 * CLK_HALF_PERIOD;
parameter MISO_ALL_ZERO = 0;
parameter MISO_ALL_ONE = 1;
parameter MISO_MOSI = 2;
parameter MISO_INV_MOSI = 3;
//----------------------------------------------------------------
// Register and Wire declarations.
//----------------------------------------------------------------
reg [31 : 0] cycle_ctr;
reg [31 : 0] error_ctr;
reg [31 : 0] tc_ctr;
reg monitor;
reg verbose;
reg tb_clk;
reg tb_reset_n;
wire tb_spi_ss;
wire tb_spi_sck;
wire tb_spi_mosi;
wire tb_spi_miso;
reg tb_spi_enable;
reg tb_spi_enable_vld;
reg tb_spi_start;
reg [7 : 0] tb_spi_tx_data;
reg tb_spi_tx_data_vld;
wire [7 : 0] tb_spi_rx_data;
wire tb_spi_ready;
wire mem_model_WPn;
wire mem_model_HOLDn;
reg [1 : 0] tb_miso_mux_ctrl;
reg my_tb_spi_ss;
//----------------------------------------------------------------
// Assignments.
//----------------------------------------------------------------
assign mem_model_WPn = 1'h1;
//----------------------------------------------------------------
// Device Under Test.
//----------------------------------------------------------------
tk1_spi_master dut(
.clk(tb_clk),
.reset_n(tb_reset_n),
.spi_ss(tb_spi_ss),
.spi_sck(tb_spi_sck),
.spi_mosi(tb_spi_mosi),
.spi_miso(tb_spi_miso),
.spi_enable(tb_spi_enable),
.spi_enable_vld(tb_spi_enable_vld),
.spi_start(tb_spi_start),
.spi_tx_data(tb_spi_tx_data),
.spi_tx_data_vld(tb_spi_tx_data_vld),
.spi_rx_data(tb_spi_rx_data),
.spi_ready(tb_spi_ready)
);
//----------------------------------------------------------------
// spi_memory
//----------------------------------------------------------------
W25Q80DL spi_memory(
.CSn(tb_spi_ss),
.CLK(tb_spi_sck),
.DIO(tb_spi_mosi),
.DO(tb_spi_miso),
.WPn(mem_model_WPn),
.HOLDn(mem_model_HOLDn)
);
//----------------------------------------------------------------
// clk_gen
//
// Always running clock generator process.
//----------------------------------------------------------------
always
begin : clk_gen
#CLK_HALF_PERIOD;
tb_clk = !tb_clk;
end // clk_gen
//----------------------------------------------------------------
// sys_monitor()
//
// An always running process that creates a cycle counter and
// conditionally displays information about the DUT.
//----------------------------------------------------------------
always
begin : sys_monitor
cycle_ctr = cycle_ctr + 1;
#(CLK_PERIOD);
if (monitor)
begin
dump_dut_state();
end
end
//----------------------------------------------------------------
// dump_dut_state()
//
// Dump the state of the dump when needed.
//----------------------------------------------------------------
task dump_dut_state;
begin : dump_dut_state
$display("");
$display("State of DUT at cycle: %08d", cycle_ctr);
$display("------------");
$display("Inputs and outputs:");
$display("spi_ss: 0x%1x, spi_sck: 0x%1x, spi_mosi: 0x%1x, spi_miso:0x%1x",
dut.spi_ss, dut.spi_sck, dut.spi_mosi, dut.spi_miso);
$display("spi_enable_vld: 0x%1x, spi_enable: 0x%1x",
dut.spi_enable_vld, dut.spi_enable);
$display("spi_tx_data_vld: 0x%1x, spi_tx_data: 0x%02x",
dut.spi_tx_data_vld, dut.spi_tx_data);
$display("spi_start: 0x%1x, spi_ready: 0x%1x, spi_rx_data: 0x%02x",
dut.spi_start, dut.spi_ready, dut.spi_rx_data);
$display("");
$display("");
$display("Internal state:");
$display("spi_clk_ctr_rst: 0x%1x, spi_clk_ctr_reg: 0x%02x",
dut.spi_clk_ctr_rst, dut.spi_clk_ctr_reg);
$display("");
$display("spi_bit_ctr_rst: 0x%1x, spi_bit_ctr_inc: 0x%1x, spi_bit_ctr_reg: 0x%02x",
dut.spi_bit_ctr_rst, dut.spi_bit_ctr_inc, dut.spi_bit_ctr_reg);
$display("");
$display("spi_ctrl_reg: 0x%02x, spi_ctrl_new: 0x%02x, spi_ctrl_we: 0x%1x",
dut.spi_ctrl_reg, dut.spi_ctrl_new, dut.spi_ctrl_we);
$display("");
$display("spi_tx_data_new: 0x%1x, spi_tx_data_nxt: 0x%1x, spi_tx_data_we: 0x%1x",
dut.spi_tx_data_new, dut.spi_tx_data_nxt, dut.spi_tx_data_we);
$display("spi_tx_data_reg: 0x%02x, spi_tx_data_new: 0x%02x",
dut.spi_tx_data_reg, dut.spi_tx_data_new);
$display("");
$display("spi_rx_data_nxt: 0x%1x, spi_rx_data_we: 0x%1x",
dut.spi_rx_data_nxt, dut.spi_rx_data_we);
$display("spi_rx_data_reg: 0x%02x, spi_rx_data_new: 0x%02x",
dut.spi_rx_data_reg, dut.spi_rx_data_new);
$display("spi_rx_data_reg0: 0x%1x, spi_rx_data_new0: 0x%1x",
dut.spi_rx_data_reg[0], dut.spi_rx_data_new[0]);
$display("spi_rx_data_reg1: 0x%1x, spi_rx_data_new1: 0x%1x",
dut.spi_rx_data_reg[1], dut.spi_rx_data_new[1]);
$display("spi_rx_data_reg2: 0x%1x, spi_rx_data_new2: 0x%1x",
dut.spi_rx_data_reg[2], dut.spi_rx_data_new[2]);
$display("spi_rx_data_reg3: 0x%1x, spi_rx_data_new3: 0x%1x",
dut.spi_rx_data_reg[3], dut.spi_rx_data_new[3]);
$display("spi_rx_data_reg4: 0x%1x, spi_rx_data_new4: 0x%1x",
dut.spi_rx_data_reg[4], dut.spi_rx_data_new[4]);
$display("spi_rx_data_reg5: 0x%1x, spi_rx_data_new5: 0x%1x",
dut.spi_rx_data_reg[5], dut.spi_rx_data_new[5]);
$display("spi_rx_data_reg6: 0x%1x, spi_rx_data_new6: 0x%1x",
dut.spi_rx_data_reg[6], dut.spi_rx_data_new[6]);
$display("spi_rx_data_reg7: 0x%1x, spi_rx_data_new7: 0x%1x",
dut.spi_rx_data_reg[7], dut.spi_rx_data_new[7]);
$display("");
end
endtask // dump_dut_state
//----------------------------------------------------------------
// reset_dut()
//
// Toggle reset to put the DUT into a well known state.
//----------------------------------------------------------------
task reset_dut;
begin
$display("--- Toggle reset.");
tb_reset_n = 0;
#(2 * CLK_PERIOD);
tb_reset_n = 1;
end
endtask // reset_dut
//----------------------------------------------------------------
// display_test_result()
//
// Display the accumulated test results.
//----------------------------------------------------------------
task display_test_result;
begin
if (error_ctr == 0)
begin
$display("--- All %02d test cases completed successfully", tc_ctr);
end
else
begin
$display("--- %02d tests completed - %02d test cases did not complete successfully.",
tc_ctr, error_ctr);
end
end
endtask // display_test_result
//----------------------------------------------------------------
// init_sim()
//
// Initialize all counters and testbed functionality as well
// as setting the DUT inputs to defined values.
//----------------------------------------------------------------
task init_sim;
begin
cycle_ctr = 0;
error_ctr = 0;
tc_ctr = 0;
monitor = 0;
tb_clk = 1'h0;
tb_reset_n = 1'h1;
tb_spi_enable = 1'h0;
tb_spi_enable_vld = 1'h0;
tb_spi_start = 1'h0;
tb_spi_tx_data = 8'h0;
tb_spi_tx_data_vld = 1'h0;
tb_miso_mux_ctrl = MISO_MOSI;
end
endtask // init_sim
//----------------------------------------------------------------
// enable_spi
//
// Enable the SPI-interface
//----------------------------------------------------------------
task enable_spi;
begin
if (verbose) begin
$display("enable_spi: Started");
end
tb_spi_enable = 1'h1;
tb_spi_enable_vld = 1'h1;
#(CLK_PERIOD);
tb_spi_enable_vld = 1'h0;
#(CLK_PERIOD);
if (verbose) begin
$display("enable_spi: Completed");
end
end
endtask // enable_spi
//----------------------------------------------------------------
// disable_spi
//
// Disable the SPI-interface
//----------------------------------------------------------------
task disable_spi;
begin
if (verbose) begin
$display("disable_spi: Started");
end
tb_spi_enable = 1'h0;
tb_spi_enable_vld = 1'h1;
#(CLK_PERIOD);
tb_spi_enable_vld = 1'h0;
#(CLK_PERIOD);
if (verbose) begin
$display("disable_spi: Completed");
end
end
endtask // disable_spi
//----------------------------------------------------------------
// xfer_byte
//
// Wait until the SPI-master is ready, then send input byte
// and return the received byte.
//----------------------------------------------------------------
task xfer_byte (input [7 : 0] to_mem, output [7 : 0] from_mem);
begin
if (verbose) begin
$display("xfer_byte: Trying to send 0x%02x to mem", to_mem);
end
tb_spi_tx_data = to_mem;
tb_spi_tx_data_vld = 1'h1;
#(CLK_PERIOD);
tb_spi_tx_data_vld = 1'h0;
#(CLK_PERIOD);
while (tb_spi_ready == 1'h0) begin
#(CLK_PERIOD);
end
#(CLK_PERIOD);
tb_spi_start = 1'h1;
#(CLK_PERIOD);
tb_spi_start = 1'h0;
#(CLK_PERIOD);
while (tb_spi_ready == 1'h0) begin
#(CLK_PERIOD);
end
#(CLK_PERIOD);
from_mem = tb_spi_rx_data;
#(CLK_PERIOD);
if (verbose) begin
$display("xfer_byte: Received 0x%02x from mem", from_mem);
end
end
endtask // xfer_byte
//----------------------------------------------------------------
// read_mem_range()
//
// Read out a specified memory range. Result is printed,
//----------------------------------------------------------------
task read_mem_range (input [23 : 0] address, input integer num_bytes);
begin : read_mem_range
reg [7 : 0] rx_byte;
integer i;
if (verbose) begin
$display("read_mem_range: Reading out %d bytes starting at address 0x%06x", num_bytes, address);
end
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send read command 0x03.
xfer_byte(8'h03, rx_byte);
// Send adress 0x000000.
xfer_byte(address[23 : 16], rx_byte);
xfer_byte(address[15 : 8], rx_byte);
xfer_byte(address[7 : 0], rx_byte);
// Read out num_bytes bytes.
for (i = 0 ; i < num_bytes ; i = i + 1) begin
xfer_byte(8'h00, rx_byte);
$display("--- tc_read_mem_range: Byte 0x%06x: 0x%02x", address + i, rx_byte);
end
disable_spi();
#(2 * CLK_PERIOD);
if (verbose) begin
$display("read_mem_range: Completed");
end
end
endtask // read_mem_range
//----------------------------------------------------------------
// read_status()
//----------------------------------------------------------------
task read_status ();
begin : read_status
reg [7 : 0] dummy;
reg [15 : 0] status;
enable_spi();
#(2 * CLK_PERIOD);
xfer_byte(8'h05, dummy);
xfer_byte(8'h00, status[15 : 8]);
xfer_byte(8'h00, status[7 : 0]);
#(2 * CLK_PERIOD);
disable_spi();
$display("--- read_status: 0x%04x", status);
end
endtask // read_status
//----------------------------------------------------------------
// tc_get_device_id()
//
// Test case that reads out the device ID.
//----------------------------------------------------------------
task tc_get_device_id;
begin : tc_get_id
reg [7 : 0] rx_byte;
tc_ctr = tc_ctr + 1;
monitor = 0;
$display("");
$display("--- tc_get_device_id: Read out device id from the memory.");
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send 0xab command.
$display("--- tc_get_device_id: Sending 0xab command.");
xfer_byte(8'hab, rx_byte);
#(CLK_PERIOD);
// Dummy bytes.
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got 0x%02x after dummy byte 1", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got 0x%02x after dummy byte 2", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got 0x%02x after dummy byte 3", rx_byte);
// Get the ID byte.
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got ID 0x%02x after dummy byte 4", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got ID 0x%02x after dummy byte 5", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got ID 0x%02x after dummy byte 6", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got ID 0x%02x after dummy byte 6", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_device_id: Got ID 0x%02x after dummy byte 6", rx_byte);
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_get_device_id: completed.");
$display("");
end
endtask // tc_get_device_id
//----------------------------------------------------------------
// tc_get_jedec_id()
//
// Test case that reads out the JEDEC ID.
//----------------------------------------------------------------
task tc_get_jedec_id;
begin : tc_get_id
reg [7 : 0] rx_byte;
tc_ctr = tc_ctr + 1;
monitor = 0;
verbose = 0;
$display("");
$display("--- tc_get_jedec_id: Read out JEDEC device id, type and capacity from the memory.");
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send 0x9f command.
$display("--- tc_get_jedec_id: Sending 0xab command.");
xfer_byte(8'h9f, rx_byte);
// Send dummy bytes and get response back.
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_jedec_id: Got manufacture ID 0x%02x", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_jedec_id: Got memory type 0x%02x", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_jedec_id: Got memory capacity 0x%02x", rx_byte);
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_get_jedec_id: completed.");
$display("");
verbose = 1;
end
endtask // tc_get_jedec_id
//----------------------------------------------------------------
// tc_get_unique_device_id()
//
// Test case that reads out the JEDEC ID.
// Expected: 0xdc02030405060708
//----------------------------------------------------------------
task tc_get_unique_device_id;
begin : tc_get_id
reg [7 : 0] rx_byte;
integer i;
tc_ctr = tc_ctr + 1;
monitor = 0;
verbose = 0;
$display("");
$display("--- tc_get_unique_device_id: Read out unique id from the memory");
$display("--- tc_get_unique_device_id: Expected result: 0xdc02030405060708");
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send 0x9f command.
$display("--- tc_get_unique_device_id: Sending 0x4b command.");
xfer_byte(8'h4b, rx_byte);
// Send four dummy bytes and get response back.
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
// Send eight bytes and get unique device id back.
$display("--- tc_get_unique_device_id: reading out the unique device UD");
for (i = 0 ; i < 8 ; i = i + 1) begin
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_unique_device_id: 0x%02x", rx_byte);
end
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_get_unique_device_id: completed.");
$display("");
verbose = 1;
end
endtask // tc_get_unique_device_id
//----------------------------------------------------------------
// tc_get_manufacturer_id()
//
// Test case that reads out the device ID.
//----------------------------------------------------------------
task tc_get_manufacturer_id;
begin : tc_get_id
reg [7 : 0] rx_byte;
tc_ctr = tc_ctr + 1;
monitor = 0;
$display("");
$display("--- tc_get_manufacturer_id: Read out device id from the memory.");
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send 0x90 command.
$display("--- tc_get_manufacturer_id: Sending 0xab command.");
xfer_byte(8'h90, rx_byte);
// Dummy bytes.
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_manufacturer_id: Got 0x%02x after dummy byte 1", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_manufacturer_id: Got 0x%02x after dummy byte 2", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_manufacturer_id: Got 0x%02x after dummy byte 3", rx_byte);
// Get the ID byte.
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_manufacturer_id: Got ID 0x%02x after dummy byte 4", rx_byte);
xfer_byte(8'h00, rx_byte);
$display("--- tc_get_manufacturer_id: Got ID 0x%02x after dummy byte 5", rx_byte);
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_get_manufacturer_id: completed.");
$display("");
end
endtask // tc_get_manufacturer_id
//----------------------------------------------------------------
// tc_read_mem()
//
// Test case that reads out the first 16 bytes of the memory.
//----------------------------------------------------------------
task tc_read_mem;
begin : tc_get_id
reg [7 : 0] rx_byte;
integer i;
tc_ctr = tc_ctr + 1;
monitor = 0;
verbose = 0;
$display("");
$display("--- tc_read_mem: Read out the first 16 bytes from the memory.");
#(2 * CLK_PERIOD);
enable_spi();
#(2 * CLK_PERIOD);
// Send read command 0x03.
$display("--- tc_read_mem: Sending 0x03 command.");
xfer_byte(8'h03, rx_byte);
// Send adress 0x000000.
$display("--- tc_read_mem: Sending 24 bit address 0x000000.");
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
// Read out 16 bytes.
$display("--- tc_read_mem: Reading out 16 bytes from the memory.");
for (i = 1 ; i < 17 ; i = i + 1) begin
xfer_byte(8'h00, rx_byte);
$display("--- tc_read_mem: Byte %d: 0x%02x", i, rx_byte);
end
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_read_mem: completed.");
$display("");
end
endtask // tc_read_mem
//----------------------------------------------------------------
// tc_rmr_mem()
//
// Test case that reads out the first 16 bytes of the memory,
// erase the same area, reads out the contents again, writes
// a known pattern and the reads it out again.
//----------------------------------------------------------------
task tc_rmr_mem;
begin : tc_get_id
reg [7 : 0] rx_byte;
integer i;
tc_ctr = tc_ctr + 1;
monitor = 0;
verbose = 0;
$display("");
$display("--- tc_rmr_mem: Read out the first 16 bytes from the memory.");
read_mem_range(24'h000000, 16);
$display("");
$display("--- tc_rmr_mem: Status before write enable:");
read_status();
// Set write enable mode.
enable_spi();
// #(2 * CLK_PERIOD);
xfer_byte(8'h06, rx_byte);
// #(2 * CLK_PERIOD);
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_rmr_mem: Status after write enable:");
read_status();
// Erase sector. Command 0x20 followed by 24 bit address.
enable_spi();
#(2 * CLK_PERIOD);
xfer_byte(8'h20, rx_byte);
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
xfer_byte(8'h00, rx_byte);
disable_spi();
#(4096 * CLK_PERIOD);
$display("--- tc_rmr_mem: Content of memory after erase.");
read_mem_range(24'h000000, 16);
disable_spi();
#(2 * CLK_PERIOD);
$display("--- tc_rmr_mem: completed.");
$display("");
end
endtask // tc_rmr_mem
//----------------------------------------------------------------
// tk1_spi_master_test
//----------------------------------------------------------------
initial
begin : tk1_spi_master_test
$display("");
$display(" -= Testbench for tk1_spi_master started =-");
$display(" =======================================");
$display("");
init_sim();
reset_dut();
disable_spi();
verbose = 1;
// tc_get_device_id();
tc_get_jedec_id();
// tc_get_manufacturer_id();
tc_get_unique_device_id();
tc_read_mem();
// tc_rmr_mem();
display_test_result();
$display("");
$display(" -= Testbench for tk1_spi_master completed =-");
$display(" =========================================");
$display("");
$finish;
end // tk1_spi_master_test
endmodule // tb_tk1_spi_master
//======================================================================
// EOF tb_tk1_spi_master.v
//======================================================================

View File

@ -11,7 +11,11 @@
#
#===================================================================
TOP_SRC=../rtl/tk1.v
SPI_SRC=../rtl/tk1_spi_master.v
TB_SPI_SRC =../tb/tb_tk1_spi_master.v
MEM_MODEL_SRC =../tb/W25Q80DL.v
TOP_SRC=../rtl/tk1.v $(SPI_SRC)
TB_TOP_SRC =../tb/tb_tk1.v ../tb/sb_rgba_drv.v
CC = iverilog
@ -21,11 +25,23 @@ LINT = verilator
LINT_FLAGS = +1364-2005ext+ --lint-only -Wall -Wno-fatal -Wno-DECLFILENAME
all: top.sim
all: MEM.TXT spi.sim top.sim
MEM.TXT:
../tools/mem_gen.py > MEM.TXT
spi.sim: $(TB_SPI_SRC) $(SPI_SRC) $(MEM_MODEL_SRC)
$(CC) $(CC_FLAGS) -o spi.sim $^
top.sim: $(TB_TOP_SRC) $(TOP_SRC)
$(CC) $(CC_FLAGS) -o top.sim $(TB_TOP_SRC) $(TOP_SRC) -DUDI_HEX=\"../tb/udi.hex\"
$(CC) $(CC_FLAGS) -o top.sim $^ -DUDI_HEX=\"../tb/udi.hex\"
sim-spi: spi.sim
./spi.sim
sim-top: top.sim
@ -33,18 +49,25 @@ sim-top: top.sim
lint-top: $(TOP_SRC)
$(LINT) $(LINT_FLAGS) $(TOP_SRC)
$(LINT) $(LINT_FLAGS) $^ ../tb/sb_rgba_drv.v -DUDI_HEX=\"../tb/udi.hex\"
lint-spi: $(SPI_SRC)
$(LINT) $(LINT_FLAGS) $^
clean:
rm -f spi.sim
rm -f top.sim
rm -f MEM.TXT
help:
@echo "Build system for simulation of TK1 core"
@echo ""
@echo "Supported targets:"
@echo "------------------"
@echo "spi.sim: Build SPI simulation target."
@echo "sim-spi: Run SPI simulation."
@echo "top.sim: Build top level simulation target."
@echo "sim-top: Run top level simulation."
@echo "lint-top: Lint top rtl source files."

View File

@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#=======================================================================
#
# mem_gen.py
# ------
# Program that generates hex memory file read by the memory model.
#
# Copyright (C) 2024 - Tillitis AB
# SPDX-License-Identifier: GPL-2.0-only
#
#=======================================================================
# print("// Memory data loaded into the module at init.")
for i in range(int(16793600 / 4)):
print("de")
print("ad")
print("be")
print("ef")

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

@ -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

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),