Skip to content

Commit

Permalink
Change implementation of SPI, add test bench, update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hpretl committed Oct 27, 2024
1 parent 344361e commit 9ffbf34
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 86 deletions.
40 changes: 4 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
![](../../workflows/gds/badge.svg) ![](../../workflows/docs/badge.svg) ![](../../workflows/test/badge.svg) ![](../../workflows/fpga/badge.svg)

# Tiny Tapeout Verilog Project Template
# Simple SPI Test

- [Read the documentation for project](docs/info.md)
(c) 2024 Harald Pretl, Institute for Integrated Circuits, Johannes Kepler University, Linz, Austria

## What is Tiny Tapeout?
This project implements a simple SPI where the loaded 16b data can be output in 8b chunks (high and low byte). In addition, a magic cookie detection is implemented (an output goes active on detection of 0xCAFE).

Tiny Tapeout is an educational project that aims to make it easier and cheaper than ever to get your digital and analog designs manufactured on a real chip.

To learn more and get started, visit https://tinytapeout.com.

## Set up your Verilog project

1. Add your Verilog files to the `src` folder.
2. Edit the [info.yaml](info.yaml) and update information about your project, paying special attention to the `source_files` and `top_module` properties. If you are upgrading an existing Tiny Tapeout project, check out our [online info.yaml migration tool](https://tinytapeout.github.io/tt-yaml-upgrade-tool/).
3. Edit [docs/info.md](docs/info.md) and add a description of your project.
4. Adapt the testbench to your design. See [test/README.md](test/README.md) for more information.

The GitHub action will automatically build the ASIC files using [OpenLane](https://www.zerotoasiccourse.com/terminology/openlane/).

## Enable GitHub actions to build the results page

- [Enabling GitHub Pages](https://tinytapeout.com/faq/#my-github-action-is-failing-on-the-pages-part)

## Resources

- [FAQ](https://tinytapeout.com/faq/)
- [Digital design lessons](https://tinytapeout.com/digital_design/)
- [Learn how semiconductors work](https://tinytapeout.com/siliwiz/)
- [Join the community](https://tinytapeout.com/discord)
- [Build your design locally](https://www.tinytapeout.com/guides/local-hardening/)

## What next?

- [Submit your design to the next shuttle](https://app.tinytapeout.com/).
- Edit [this README](README.md) and explain your design, how it works, and how to test it.
- Share your project on your social network of choice:
- LinkedIn [#tinytapeout](https://www.linkedin.com/search/results/content/?keywords=%23tinytapeout) [@TinyTapeout](https://www.linkedin.com/company/100708654/)
- Mastodon [#tinytapeout](https://chaos.social/tags/tinytapeout) [@matthewvenn](https://chaos.social/@matthewvenn)
- X (formerly Twitter) [#tinytapeout](https://twitter.com/hashtag/tinytapeout) [@tinytapeout](https://twitter.com/tinytapeout)
The input and output list documentation can be found in `info.yaml`.
14 changes: 7 additions & 7 deletions info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,24 @@ project:
# Don't forget to also update `PROJECT_SOURCES` in test/Makefile.
source_files:
- "tt_um_hpretl_spi.v"
- "chain1.v"
- "chain2.v"

# The pinout of your project. Leave unused pins blank. DO NOT delete or add any pins.
pinout:
# Inputs
ui[0]: "clk (SCLK"
ui[1]: "data_in (MOSI)"
ui[2]: "select output byte (0 = low, 1 = high)"
ui[3]: ""
ui[0]: "SPI clk (SCLK)"
ui[1]: "SPI data in (MOSI)"
ui[2]: "SPI load (CS)"
ui[3]: "select output byte (0 = low, 1 = high)"
ui[4]: ""
ui[5]: ""
ui[6]: ""
ui[7]: ""

# Outputs
uo[0]: "data_out (MISO)"
uo[0]: "SPI data out (MISO)"
uo[1]: "cookie detected (loaded 0xCAFE)"
uo[2]: "XOR of clk and data_in"
uo[2]: "XOR of SPI clk and SPI data in"
uo[3]: ""
uo[4]: ""
uo[5]: ""
Expand Down
50 changes: 30 additions & 20 deletions src/chain1.v → src/chain2.v
Original file line number Diff line number Diff line change
Expand Up @@ -11,50 +11,60 @@
IO description:
i_clk (like SPI SCLK)
i_dat (like SPI MOSI)
i_load (like SPI nCS)
o_dat (like SPI MISO)
i_clk (high running clock to sample the SPI input lines)
i_spi_clk (like SPI SCLK)
i_spi_dat (like SPI MOSI)
i_spi_load (like SPI nCS)
o_spi_dat (like SPI MISO)
o_det (magic cookie detected)
o_check (XOR of i_dat and i_load)
*/

`ifndef __CHAIN1__
`define __CHAIN1__
`ifndef __CHAIN2__
`define __CHAIN2__
`default_nettype none

module chain1 (
module chain2 (
input wire i_resetn,
input wire i_clk,
input wire i_dat,
input wire i_load,
output wire o_dat,
input wire i_spi_clk,
input wire i_spi_dat,
input wire i_spi_load,
output wire o_spi_dat,
output wire o_det,
output wire o_check,
output wire [15:0] o_data
);

reg [15:0] scan_r, data_r;
reg last_spi_clk_r;

// here we see if the stored content is matching a magic cookie
assign o_det = (data_r === 16'hcafe) ? 1'b1 : 1'b0;
// provide loaded data
assign o_data = data_r;
// shift out MSB register bit
assign o_dat = scan_r[15];
assign o_spi_dat = scan_r[15];

// here the scan chain, shift MSB first (as in SPI)
always @(posedge i_clk) begin
if (i_load === 1'b0)
scan_r <= {scan_r[14:0],i_dat};
end
always @(posedge i_clk or negedge i_resetn) begin
if (i_resetn === 1'b0) begin
scan_r <= 16'b0;
data_r <= 16'b0;
last_spi_clk_r <= 1'b0;
end else begin
if ((i_spi_load === 1'b0) && (i_spi_clk === 1'b1) && (last_spi_clk_r === 1'b0))
scan_r <= {scan_r[14:0],i_spi_dat};

if (i_spi_load === 1'b1)
data_r <= scan_r;

// and here we latch the result
always @(posedge i_load) begin
data_r <= scan_r;
last_spi_clk_r <= i_spi_clk;
end
end

// here a very simple boolean function to check basic functionality
assign o_check = i_dat ^ i_load;
assign o_check = i_spi_dat ^ i_spi_load;

endmodule // chain1
endmodule // chain2
`endif
16 changes: 9 additions & 7 deletions src/tt_um_hpretl_spi.v
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

`default_nettype none
`include "chain1.v"
`include "chain2.v"

module tt_um_hpretl_spi (
input wire [7:0] ui_in, // Dedicated inputs
Expand All @@ -20,13 +20,15 @@ module tt_um_hpretl_spi (
wire [15:0] out_w;

assign uio_oe = 8'b11111111; // using IO for output
assign uio_out = ui_in[2] ? out_w[15:8] : out_w[7:0];
assign uio_out = ui_in[3] ? out_w[15:8] : out_w[7:0];

chain1 dut(
chain2 dut(
.i_resetn(rst_n),
.i_clk(clk),
.i_dat(ui_in[0]),
.i_load(ui_in[1]),
.o_dat(uo_out[0]),
.i_spi_clk(ui_in[0]),
.i_spi_dat(ui_in[1]),
.i_spi_load(ui_in[2]),
.o_spi_dat(uo_out[0]),
.o_det(uo_out[1]),
.o_check(uo_out[2]),
.o_data(out_w)
Expand All @@ -36,6 +38,6 @@ module tt_um_hpretl_spi (
assign uo_out[7:3] = 5'b10000;

// List all unused inputs to prevent warnings
wire _unused = &{ena, rst_n, uio_in[7:0], ui_in[7:3], 1'b0};
wire _unused = &{ena, uio_in[7:0], ui_in[7:4], 1'b0};

endmodule // tt_um_hpretl_spi
2 changes: 1 addition & 1 deletion test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SIM ?= icarus
TOPLEVEL_LANG ?= verilog
SRC_DIR = $(PWD)/../src
PROJECT_SOURCES = project.v
PROJECT_SOURCES = tt_um_hpretl_spi.v

ifneq ($(GATES),yes)

Expand Down
2 changes: 1 addition & 1 deletion test/tb.v
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module tb ();
wire [7:0] uio_oe;

// Replace tt_um_example with your module name:
tt_um_example user_project (
tt_um_hpretl_spi user_project (
.ui_in (ui_in), // Dedicated inputs
.uo_out (uo_out), // Dedicated outputs
.uio_in (uio_in), // IOs: Input path
Expand Down
71 changes: 57 additions & 14 deletions test/test.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# SPDX-FileCopyrightText: © 2024 Tiny Tapeout
# SPDX-FileCopyrightText: © 2024 Tiny Tapeout and Harald Pretl, IIC@JKU
# SPDX-License-Identifier: Apache-2.0

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import ClockCycles


@cocotb.test()
async def test_project(dut):
dut._log.info("Start")

# Set the clock period to 10 us (100 KHz)
clock = Clock(dut.clk, 10, units="us")
# Set the clock period to 0.1 us (10 MHz)
clock = Clock(dut.clk, 0.1, units="us")
cocotb.start_soon(clock.start())

# Reset
Expand All @@ -20,21 +19,65 @@ async def test_project(dut):
dut.ui_in.value = 0
dut.uio_in.value = 0
dut.rst_n.value = 0
await ClockCycles(dut.clk, 10)
await ClockCycles(dut.clk, 3)
dut.rst_n.value = 1

dut._log.info("Test project behavior")

# Set the input values you want to test
dut.ui_in.value = 20
dut.uio_in.value = 30
# Check that cookie not detected
assert dut.uo_out.value[2] == 0

# Load magic cookie
load_word = "0xCAFE"
load_word_bin = format(int(load_word, 16), '016b')
load_word_bits = [int(bit) for bit in load_word_bin]

dut._log.info("Loading " + str(load_word_bin))

# Shift cookie in
for i in range(16):
#b0 is clk, b1 is dat, b2 is load, b3 is select
dut.ui_in.value = 1*0 + 2*load_word_bits[i] + 4*0 + 8*0

await ClockCycles(dut.clk, 3)

#b0 is clk, b1 is dat, b2 is load, b3 is select
dut.ui_in.value = 1*1 + 2*load_word_bits[i] + 4*0 + 8*0

await ClockCycles(dut.clk, 3)

assert (dut.uo_out.value & 2) == 0

# Wait for one clock cycle to see the output values
# serial register is loaded, now store it
#b0 is clk, b1 is dat, b2 is load, b3 is select
dut.ui_in.value = 1*0 + 2*0 + 4*0 + 8*0
await ClockCycles(dut.clk, 3)
dut.ui_in.value = 1*0 + 2*0 + 4*1 + 8*0
await ClockCycles(dut.clk, 3)

# check magic cookie detection
dut._log.info("Check magic cookie detection")
assert (dut.uo_out.value & 2) == 2

# check output parallel selection
dut._log.info("Check parallel output")
dut.ui_in.value = 1*0 + 2*0 + 4*0 + 8*0
await ClockCycles(dut.clk, 1)
assert dut.uio_out.value == 0xfe
dut.ui_in.value = 1*0 + 2*0 + 4*0 + 8*1
await ClockCycles(dut.clk, 1)
assert dut.uio_out.value == 0xca

# Shift 0 in
for i in range(16):
#b0 is clk, b1 is dat, b2 is load, b3 is select
dut.ui_in.value = 1*0 + 2*0 + 4*0 + 8*0

await ClockCycles(dut.clk, 3)

#b0 is clk, b1 is dat, b2 is load, b3 is select
dut.ui_in.value = 1*1 + 2*0 + 4*0 + 8*0

# The following assersion is just an example of how to check the output values.
# Change it to match the actual expected output of your module:
assert dut.uo_out.value == 50
await ClockCycles(dut.clk, 3)

# Keep testing the module by changing the input values, waiting for
# one or more clock cycles, and asserting the expected output values.
assert (dut.uo_out.value & 2) == 2

0 comments on commit 9ffbf34

Please sign in to comment.