mirror of
https://github.com/tillitis/tillitis-key1.git
synced 2024-06-13 08:32:19 +00:00
Merge afa28ab1ad
into 0454e023cd
This commit is contained in:
commit
ded1e5f6a5
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -60,6 +60,6 @@ fp-info-cache
|
|||
*.net
|
||||
*.dsn
|
||||
*.ses
|
||||
|
||||
__pycache__
|
||||
application_fpga_par.json
|
||||
MEM.TXT
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, $^)
|
||||
|
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
|
|
@ -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
|
||||
|
|
327
hw/application_fpga/core/tk1/rtl/tk1_spi_master.v
Normal file
327
hw/application_fpga/core/tk1/rtl/tk1_spi_master.v
Normal 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
|
||||
//======================================================================
|
|
@ -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("");
|
||||
|
|
732
hw/application_fpga/core/tk1/tb/tb_tk1_spi_master.v
Normal file
732
hw/application_fpga/core/tk1/tb/tb_tk1_spi_master.v
Normal 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
|
||||
//======================================================================
|
|
@ -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."
|
||||
|
|
19
hw/application_fpga/core/tk1/tools/mem_gen.py
Executable file
19
hw/application_fpga/core/tk1/tools/mem_gen.py
Executable 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")
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue
Block a user