diff --git a/README.md b/README.md index 68ba6ed..ddd0441 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/info.yaml b/info.yaml index 86a3745..8e15e52 100644 --- a/info.yaml +++ b/info.yaml @@ -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]: "" diff --git a/src/chain1.v b/src/chain2.v similarity index 51% rename from src/chain1.v rename to src/chain2.v index c6504f4..917f15e 100644 --- a/src/chain1.v +++ b/src/chain2.v @@ -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 diff --git a/src/tt_um_hpretl_spi.v b/src/tt_um_hpretl_spi.v index 2c53175..93762d4 100644 --- a/src/tt_um_hpretl_spi.v +++ b/src/tt_um_hpretl_spi.v @@ -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 @@ -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) @@ -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 diff --git a/test/Makefile b/test/Makefile index b6b9504..aa4c221 100644 --- a/test/Makefile +++ b/test/Makefile @@ -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) diff --git a/test/tb.v b/test/tb.v index 4938ad3..272946b 100644 --- a/test/tb.v +++ b/test/tb.v @@ -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 diff --git a/test/test.py b/test/test.py index fa7f92c..0a2e8a0 100644 --- a/test/test.py +++ b/test/test.py @@ -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 @@ -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