diff options
| author | Alejandro Soto <alejandro@34project.org> | 2024-03-06 02:38:24 -0600 |
|---|---|---|
| committer | Alejandro Soto <alejandro@34project.org> | 2024-03-06 02:38:24 -0600 |
| commit | 3038edc09a2eb15762f2e58533f429489107520b (patch) | |
| tree | f7a45e424d39e6fef0d59e329c1bf6ea206e2886 | |
| parent | 3b62399f92e9faa2602ac30865e5fc3c7c4e12b8 (diff) | |
rtl/wb2axip: add to version control
Diffstat (limited to '')
67 files changed, 56365 insertions, 1 deletions
@@ -1,5 +1,5 @@ cores := config debounce intc -subdirs := cache core dma_axi32 gfx perf picorv32 smp top +subdirs := cache core dma_axi32 gfx perf picorv32 smp top wb2axip define core/config $(this)/rtl_include_dirs := . diff --git a/rtl/wb2axip/.gitignore b/rtl/wb2axip/.gitignore new file mode 100644 index 0000000..e1cd503 --- /dev/null +++ b/rtl/wb2axip/.gitignore @@ -0,0 +1,2 @@ +*.ys +ivcheck diff --git a/rtl/wb2axip/Makefile b/rtl/wb2axip/Makefile new file mode 100644 index 0000000..db66615 --- /dev/null +++ b/rtl/wb2axip/Makefile @@ -0,0 +1,344 @@ +################################################################################ +## +## Filename: Makefile +## {{{ +## Project: WB2AXIPSP: bus bridges and other odds and ends +## +## Purpose: To describe how to build the Verilator libraries from the +## RTL, for the purposes of trying to discover if they work. +## Any actual testing will be done from the code within the bench/cpp +## directory. +## +## Targets: The default target, all, builds the target test, which includes +## the libraries necessary for Verilator testing. +## +## Creator: Dan Gisselquist, Ph.D. +## Gisselquist Technology, LLC +## +################################################################################ +## }}} +## Copyright (C) 2016-2023, Gisselquist Technology, LLC +## {{{ +## This file is part of the WB2AXIP project. +## +## The WB2AXIP project contains free software and gateware, licensed under the +## Apache License, Version 2.0 (the "License"). You may not use this project, +## or this file, except in compliance with the License. You may obtain a copy +## of the License at +## }}} +## http://www.apache.org/licenses/LICENSE-2.0 +## {{{ +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +## WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +## License for the specific language governing permissions and limitations +## under the License. +## +################################################################################ +## +## }}} +all: test +YYMMDD=`date +%Y%m%d` +CXX := g++ +IVCHECK := ivcheck +IVERILOG := iverilog +FBDIR := . +VDIRFB:= $(FBDIR)/obj_dir +VERILATOR := verilator +VFLAGS := -MMD -O3 -Wall -Wpedantic -cc +.DELETE_ON_ERROR: +.PHONY: test +test: testwb testaxi testaxil testapb testaxis + +.PHONY: testwb testaxi testaxil testapb testaxis + +.PHONY: axim2wbsp axim2wbsp wbm2axilite axilrd2wbsp axilwr2wbsp axlite2wbsp +.PHONY: axixbar axilxbar wbxbar axis2mm aximm2s axidma axim2wbsp aximrd2wbsp +.PHONY: aximwr2wbsp axixclk axiperf demoaxi demofull easyaxil sfifo skidbuffer +.PHONY: axilgpio +.PHONY: axilsafety axisafety wbsafety axivfifo axil2apb apbslave +.PHONY: axisrandom axisswitch axisbroadcast axispacker + +axim2wbsp: $(VDIRFB)/Vwbm2axisp__ALL.a $(IVCHECK)/axim2wbsp +axim2wbsp: $(VDIRFB)/Vaxim2wbsp__ALL.a +wbm2axilite: $(VDIRFB)/Vwbm2axilite__ALL.a $(IVCHECK)/wbm2axilite +axilrd2wbsp: $(VDIRFB)/Vaxilrd2wbsp__ALL.a $(IVCHECK)/axilrd2wbsp +axilwr2wbsp: $(VDIRFB)/Vaxilwr2wbsp__ALL.a $(IVCHECK)/axilwr2wbsp +axlite2wbsp: $(VDIRFB)/Vaxlite2wbsp__ALL.a $(IVCHECK)/axlite2wbsp +axiempty: $(VDIRFB)/Vaxiempty__ALL.a $(IVCHECK)/axiempty +axilempty: $(VDIRFB)/Vaxilempty__ALL.a $(IVCHECK)/axilempty +axilgpio: $(VDIRFB)/Vaxilgpio__ALL.a $(IVCHECK)/axilgpio +axivcamera: $(VDIRFB)/Vaxivcamera__ALL.a $(IVCHECK)/axivcamera +axivdisplay: $(VDIRFB)/Vaxivdisplay__ALL.a $(IVCHECK)/axivdisplay +axivfifo: $(VDIRFB)/Vaxivfifo__ALL.a $(IVCHECK)/axivfifo +axixbar: $(VDIRFB)/Vaxixbar__ALL.a $(IVCHECK)/axixbar +axilxbar: $(VDIRFB)/Vaxilxbar__ALL.a $(IVCHECK)/axilxbar +wbxbar: $(VDIRFB)/Vwbxbar__ALL.a $(IVCHECK)/wbxbar +axis2mm: $(VDIRFB)/Vaxis2mm__ALL.a $(IVCHECK)/axis2mm +aximm2s: $(VDIRFB)/Vaximm2s__ALL.a $(IVCHECK)/aximm2s +axidma: $(VDIRFB)/Vaxidma__ALL.a $(IVCHECK)/axidma +axim2wbsp: $(VDIRFB)/Vaxim2wbsp__ALL.a $(IVCHECK)/axim2wbsp +aximrd2wbsp: $(VDIRFB)/Vaximrd2wbsp__ALL.a $(IVCHECK)/aximrd2wbsp +aximwr2wbsp: $(VDIRFB)/Vaximwr2wbsp__ALL.a $(IVCHECK)/aximwr2wbsp +axiperf: $(VDIRFB)/Vaxiperf__ALL.a $(IVCHECK)/axiperf +axixclk: $(VDIRFB)/Vaxixclk__ALL.a $(IVCHECK)/axixclk +demoaxi: $(VDIRFB)/Vdemoaxi__ALL.a $(IVCHECK)/demoaxi +demofull: $(VDIRFB)/Vdemofull__ALL.a $(IVCHECK)/demofull +easyaxil: $(VDIRFB)/Veasyaxil__ALL.a $(IVCHECK)/easyaxil +sfifo: $(VDIRFB)/Vsfifo__ALL.a $(IVCHECK)/sfifo +skidbuffer: $(VDIRFB)/Vskidbuffer__ALL.a $(IVCHECK)/skidbuffer +axisafety: $(VDIRFB)/Vaxisafety__ALL.a $(IVCHECK)/axisafety +axilsafety: $(VDIRFB)/Vaxilsafety__ALL.a $(IVCHECK)/axilsafety +wbsafety: $(VDIRFB)/Vwbsafety__ALL.a $(IVCHECK)/wbsafety +axil2apb: $(VDIRFB)/Vaxil2apb__ALL.a $(IVCHECK)/axil2apb +apbslave: $(VDIRFB)/Vapbslave__ALL.a $(IVCHECK)/apbslave +axisbroadcast: $(VDIRFB)/Vaxisbroadcast__ALL.a $(IVCHECK)/axisbroadcast +axispacker: $(VDIRFB)/Vaxispacker__ALL.a $(IVCHECK)/axispacker +axisrandom: $(VDIRFB)/Vaxisrandom__ALL.a $(IVCHECK)/axisrandom +axisswitch: $(VDIRFB)/Vaxisswitch__ALL.a $(IVCHECK)/axisswitch + +testwb: wbsafety wbm2axilite wbxbar sfifo wbsafety wbxbar wbsafety +testaxi: axim2wbsp axixbar axixclk demofull axiempty +testaxi: aximrd2wbsp axim2wbsp aximwr2wbsp axisafety +testaxi: axis2mm aximm2s axidma axivfifo axivdisplay axivcamera +testaxil: axilrd2wbsp axilwr2wbsp axlite2wbsp axilxbar demoaxi easyaxil +testaxil: skidbuffer axiperf axilempty axilgpio +testapb: axil2apb apbslave +testaxis: axisbroadcast axisswitch axisrandom axispacker + +.PHONY: wbm2axisp +wbm2axisp: $(VDIRFB)/Vwbm2axisp__ALL.a +$(VDIRFB)/Vwbm2axisp__ALL.a: $(VDIRFB)/Vwbm2axisp.h $(VDIRFB)/Vwbm2axisp.cpp +$(VDIRFB)/Vwbm2axisp__ALL.a: $(VDIRFB)/Vwbm2axisp.mk +$(VDIRFB)/Vwbm2axisp.h $(VDIRFB)/Vwbm2axisp.cpp $(VDIRFB)/Vwbm2axisp.mk: wbm2axisp.v + +.PHONY: wbm2axilite +wbm2axilite: $(VDIRFB)/Vwbm2axilite__ALL.a +$(VDIRFB)/Vwbm2axilite__ALL.a: $(VDIRFB)/Vwbm2axilite.h $(VDIRFB)/Vwbm2axilite.cpp +$(VDIRFB)/Vwbm2axilite__ALL.a: $(VDIRFB)/Vwbm2axilite.mk +$(VDIRFB)/Vwbm2axilite.h $(VDIRFB)/Vwbm2axilite.cpp $(VDIRFB)/Vwbm2axilite.mk: wbm2axilite.v + +.PHONY: axilrd2wbsp +axilrd2wbsp: $(VDIRFB)/Vaxilrd2wbsp__ALL.a +$(VDIRFB)/Vaxilrd2wbsp__ALL.a: $(VDIRFB)/Vaxilrd2wbsp.h $(VDIRFB)/Vaxilrd2wbsp.cpp +$(VDIRFB)/Vaxilrd2wbsp__ALL.a: $(VDIRFB)/Vaxilrd2wbsp.mk +$(VDIRFB)/Vaxilrd2wbsp.h $(VDIRFB)/Vaxilrd2wbsp.cpp $(VDIRFB)/Vaxilrd2wbsp.mk: axilrd2wbsp.v + +.PHONY: axilwr2wbsp +axilwr2wbsp: $(VDIRFB)/Vaxilwr2wbsp__ALL.a +$(VDIRFB)/Vaxilwr2wbsp__ALL.a: $(VDIRFB)/Vaxilwr2wbsp.h $(VDIRFB)/Vaxilwr2wbsp.cpp +$(VDIRFB)/Vaxilwr2wbsp__ALL.a: $(VDIRFB)/Vaxilwr2wbsp.mk +$(VDIRFB)/Vaxilwr2wbsp.h $(VDIRFB)/Vaxilwr2wbsp.cpp $(VDIRFB)/Vaxilwr2wbsp.mk: axilwr2wbsp.v + +$(VDIRFB)/Vaxlite2wbsp__ALL.a: $(VDIRFB)/Vaxlite2wbsp.h $(VDIRFB)/Vaxlite2wbsp.cpp +$(VDIRFB)/Vaxlite2wbsp__ALL.a: $(VDIRFB)/Vaxlite2wbsp.mk +$(VDIRFB)/Vaxlite2wbsp.h $(VDIRFB)/Vaxlite2wbsp.cpp $(VDIRFB)/Vaxlite2wbsp.mk: axlite2wbsp.v + +$(VDIRFB)/Vaxiempty__ALL.a: $(VDIRFB)/Vaxiempty.h $(VDIRFB)/Vaxiempty.cpp +$(VDIRFB)/Vaxiempty__ALL.a: $(VDIRFB)/Vaxiempty.mk +$(VDIRFB)/Vaxiempty.h $(VDIRFB)/Vaxiempty.cpp $(VDIRFB)/Vaxiempty.mk: axiempty.v skidbuffer.v + +$(VDIRFB)/Vaxilempty__ALL.a: $(VDIRFB)/Vaxilempty.h $(VDIRFB)/Vaxilempty.cpp +$(VDIRFB)/Vaxilempty__ALL.a: $(VDIRFB)/Vaxilempty.mk +$(VDIRFB)/Vaxilempty.h $(VDIRFB)/Vaxilempty.cpp $(VDIRFB)/Vaxilempty.mk: axilempty.v skidbuffer.v + +$(VDIRFB)/Vaxilgpio__ALL.a: $(VDIRFB)/Vaxilgpio.h $(VDIRFB)/Vaxilgpio.cpp +$(VDIRFB)/Vaxilgpio__ALL.a: $(VDIRFB)/Vaxilgpio.mk +$(VDIRFB)/Vaxilgpio.h $(VDIRFB)/Vaxilgpio.cpp $(VDIRFB)/Vaxilgpio.mk: axilgpio.v skidbuffer.v + +$(VDIRFB)/Vaxim2wbsp__ALL.a: $(VDIRFB)/Vaxim2wbsp.h $(VDIRFB)/Vaxim2wbsp.cpp +$(VDIRFB)/Vaxim2wbsp__ALL.a: $(VDIRFB)/Vaxim2wbsp.mk +$(VDIRFB)/Vaxim2wbsp.h $(VDIRFB)/Vaxim2wbsp.cpp $(VDIRFB)/Vaxim2wbsp.mk: \ + axim2wbsp.v aximrd2wbsp.v aximwr2wbsp.v wbarbiter.v + +$(VDIRFB)/Vaxivfifo__ALL.a: $(VDIRFB)/Vaxivfifo.h $(VDIRFB)/Vaxivfifo.cpp +$(VDIRFB)/Vaxivfifo__ALL.a: $(VDIRFB)/Vaxivfifo.mk +$(VDIRFB)/Vaxivfifo.h $(VDIRFB)/Vaxivfifo.cpp $(VDIRFB)/Vaxivfifo.mk: \ + axivfifo.v skidbuffer.v sfifo.v + +$(VDIRFB)/Vaxivcamera__ALL.a: $(VDIRFB)/Vaxivcamera.h $(VDIRFB)/Vaxivcamera.cpp +$(VDIRFB)/Vaxivcamera__ALL.a: $(VDIRFB)/Vaxivcamera.mk +$(VDIRFB)/Vaxivcamera.h $(VDIRFB)/Vaxivcamera.cpp $(VDIRFB)/Vaxivcamera.mk: \ + axivcamera.v skidbuffer.v sfifo.v + +$(VDIRFB)/Vaxivdisplay__ALL.a: $(VDIRFB)/Vaxivdisplay.h $(VDIRFB)/Vaxivdisplay.cpp +$(VDIRFB)/Vaxivdisplay__ALL.a: $(VDIRFB)/Vaxivdisplay.mk +$(VDIRFB)/Vaxivdisplay.h $(VDIRFB)/Vaxivdisplay.cpp $(VDIRFB)/Vaxivdisplay.mk: \ + axivdisplay.v skidbuffer.v sfifo.v + +$(VDIRFB)/V%.cpp $(VDIRFB)/V%.h $(VDIRFB)/V%.mk: $(FBDIR)/%.v + $(VERILATOR) $(VFLAGS) $*.v + +$(VDIRFB)/V%__ALL.a: $(VDIRFB)/V%.mk + cd $(VDIRFB); make -f V$*.mk + +## +## Run Icarus Verilog (iverilog) on each of these cores for a lint check +## +$(IVCHECK)/axi2axilite: axi2axilite.v sfifo.v skidbuffer.v axi_addr.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axil2axis: axil2axis.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axildouble: axildouble.v sfifo.v skidbuffer.v addrdecode.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilrd2wbsp: axilrd2wbsp.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilsafety: axilsafety.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilsingle: axilsingle.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilwr2wbsp: axilwr2wbsp.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilxbar: axilxbar.v skidbuffer.v addrdecode.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axidma: axidma.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/aximm2s: aximm2s.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axim2wbsp: axim2wbsp.v sfifo.v skidbuffer.v aximrd2wbsp.v aximwr2wbsp.v axi_addr.v wbarbiter.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/aximrd2wbsp: aximrd2wbsp.v sfifo.v skidbuffer.v axi_addr.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/aximwr2wbsp: aximwr2wbsp.v sfifo.v skidbuffer.v axi_addr.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axiperf: axiperf.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axisafety: axisafety.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axis2mm: axis2mm.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axivcamera: axivcamera.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axivdisplay: axivdisplay.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axivfifo: axivfifo.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axixbar: axixbar.v skidbuffer.v addrdecode.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axixclk: axixclk.v afifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axlite2wbsp: axlite2wbsp.v axilrd2wbsp.v axilwr2wbsp.v wbarbiter.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axiempty: axiempty.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilempty: axilempty.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axilgpio: axilgpio.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/demoaxi: demoaxi.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/demofull: demofull.v axi_addr.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/easyaxil: easyaxil.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/sfifo: sfifo.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/skidbuffer: skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/wbm2axilite: wbm2axilite.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/wbsafety: wbsafety.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/wbxbar: wbxbar.v skidbuffer.v addrdecode.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axil2apb: axil2apb.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/apbslave: apbslave.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axisbroadcast: axisbroadcast.v sfifo.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axispacker: axispacker.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axisrandom: axisrandom.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +$(IVCHECK)/axisswitch: axisswitch.v skidbuffer.v + $(mk-ivcheck) + $(IVERILOG) -g2012 $^ -o $@ + +define mk-ivcheck + @bash -c "if [ ! -e $(IVCHECK) ]; then mkdir -p $(IVCHECK); fi" +endef + +.PHONY: clean +clean: + rm -rf $(VDIRFB)/*.mk + rm -rf $(VDIRFB)/*.cpp + rm -rf $(VDIRFB)/*.h + rm -rf $(VDIRFB)/ + rm -rf $(IVCHECK)/ diff --git a/rtl/wb2axip/README.md b/rtl/wb2axip/README.md new file mode 100644 index 0000000..6aa99a8 --- /dev/null +++ b/rtl/wb2axip/README.md @@ -0,0 +1,175 @@ +## Demo designs + +- [Demonstration AXI4-lite slave](demoaxi.v) +- [Demonstration APB slave](apbslave.v) +- [Demonstration AXI4(Full) slave](demofull.v) + -- [AXI Addr](axi_addr.v) is a helper core used for calculating the "next" address in a burst sequence. It's based upon [the algorithm discussed here](https://zipcpu.com/blog/2019/04/27/axi-addr.html). +- [A simplified AXI-lite slave](easyaxil.v) +- [A basic AXI-lite GPIO controller](axilgpio.v) + +- [Skidbuffer](skidbuffer.v) + +## Demo AXI-stream designs + +- [AXIS Broadcast](axisbroadcast.v): Accepts one stream input, duplicates that + stream to an arbitrary number of output streams +- [AXISPACKER](axispacker.v): Removes NULL bytes from an AXI Stream +- [AXISRANDOM](axisrandom.v): Generates a pseudorandom AXI-Stream output +- [AXISSWITCH](axisswitch.v): Switches a stream from among many input streams, + with an AXI-lite control input for switching between them. + +## Crossbars + +See [this post for a discussion of these +crossbars](https://zipcpu.com/blog/2019/07/17/crossbars.html). + +- [AXI4](axixbar.v) +- [AXI4-Lite](axilxbar.v) +- [Wishbone](wbxbar.v) + +These cores rely not only on the [skidbuffer](skidbuffer.v) listed above, but +also upon a separate [address decoder](addrdecode.v) that is common to all of +them. + +All three cores are supported by the (dev branch of) +[AutoFPGA](https://github.com/ZipCPU/autofpga). + +## Data movers/DMA engines + +- [AXIMM2S](aximm2s.v). Supports unaligned transfers, but only fully aligned + stream words. +- [AXIS2MM](axis2mm.v). Doesn't yet support unaligned transfers. It may + eventually, but it will also only ever support full word transfers + through the stream. +- [AXIDMA](axidma.v). Supports unaligned transfers. +- [AXISGDMA](axisgdma.v). A scatter-gather DMA implementation. Performs DMA + operations based upon an external, bus-fetched, table containing the details + of multiple DMA operations to be done. + -- [AXILFETCH](axilfetch.v)--A ZipCPU instruction fetch module required by the Scatter-Gather engine to fetch tables from memory. + -- [AXISGFSM](axisgfsm.v)--The FSM that reads instructions (i.e. table entries) from the fetch routine, and issues instructions to the [AXIDMA](axidma.v). +- [AXIVFIFO](axivfifo.v). A virtual FIFO, using an external AXI device for + memory backing--perhaps even an SDRAM. It doesn't really matter--it just + needs to be AXI. +- [AXIVCAMERA](axivcamera.v). Writes a video stream to a memory frame buffer. +- [AXIVDISPLAY](axivdisplay.v). Reads a frame buffer from memory to generate + a continuous AXI-stream video source output. + +- [Synchronous FIFO](sfifo.v) +- [Synchronous FIFO with threshold](sfifothresh.v) + +## Bus Bridges + +- [AXI to AXI-lite](axi2axilite.v). Supports 100% throughput even across burst + boundaries, unlike other (similar) bridges of this type you might come across. + +- [AXI-lite to AXI](axilite2axi.v). A "no-cost" bridge. + +- [AXI-lite to AXI stream](axil2axis.v) + +- [AXI-Lite to Wishbone](axlite2wbsp.v) + + -- [Read side](axilrd2wbsp.v) + + -- [Write side](axilwr2wbsp.v) + + -- A following (optional) [arbiter](wbarbiter.v) will connect read and write sides together into the same WB bus. Alternatively, each of the two sides can be submitted separately into a [WB Crossbar](wbxbar.v). + +- [AXI-Lite to APB](axil2apb.v) + +- [AXI4 Master to Wishbone](axim2wbsp.v) + + -- [Read side](aximrd2wbsp.v) + + -- [AXI4 Master to Wishbone](aximwr2wbsp.v) + +- [Wishbone classic to pipelined](wbc2pipeline.v) + +- [Wishbone pipelined to classic](wbp2classic.v) + +- [Wishbone master to AXI4-Litejk slave](wbm2axilite.v) + +- [Wishbone master to AXI4 slave](wbm2axisp.v) + + This core, together with its sister core, [migsdram](migsdram.v), form the + basis for my approach to accessing DDR3 SDRAM memory through an AXI + interface using Xilinx's MIG controller. Other than the lag in going + through the bridge, this solution works quite well--achieving full (nearly + 100%) bus throughput when loaded. + +- [AXI3 to AXI4](axi32axi.v). See the internal documentation of the + [AXI3 write deinterleaver](axi3reorder.v) for the details of the write + deinterleaving algorithm. + +- The AXI4 to AXI3 bridge is still missing. + This will be more difficult than its [AXI3 to AXI4 sister](axi32axi.v) above, + since it will need to break apart large AXI4 burst requests into smaller AXI3 + bursts, sort of like the [AXI4 to AXI4-lite bridge](axi2axilite.v) does. + +## AXI Simplifiers + +These follow from the discussion in [this article about how to simplify +a bus interconnect](https://zipcpu.com/zipcpu/2019/08/30/subbus.html) into +something allowing easier integration of multiple slaves into a design. They +work by aggregating the signaling logic across many slaves together. See the +headers for each of the cores for the specifics of the standard subsets +they support. + +- AXI Single, the companion to the [AXI Double](axidouble.v) core has not yet + been written. The design for it should be nearly identical. The big + difference between single and double AXI slaves is that single slaves can have + only one address, and that address must always be available for reading. + +- [AXI Double](axidouble.v). + +- [AXI-Lite Single](axilsingle.v) + +- [AXI-Lite Double](axildouble.v) + +## Firewalls + +The goal of these firewall(s) is to create a core that, when placed between the +bus master and slave, will guarantee that the slave responds appropriately to +the bus master---even in the presence of a broken slave. If the slave is +broken, the firewall will raise a flag that can then be used to trigger a +logic analyzer of some type so you can dig deeper into what's going on. + +As a bonus, the firewall may also have the capability of resetting the +downstream core upon any error, so that it might be reintegrated later into +the rest of the design for additional testing. + +- [AXI-lite](axilsafety.v) + +- [AXI4](axisafety.v) + +- [AXI-stream](axissafety.v) + +- [Wishbone](wbsafety.v) + +## Performance Measurement + +- [AXIPERF](axiperf.v) -- Useful for measuring Latency and Throughput in an +AXI bus. Has an AXI-lite interface, and an AXI4 monitor interface. Monitors +the AXI4 bus, and measures key performance statistics. Statistics may be read +and cleared via the AXI-lite interface. + +## Empty slaves + +These are used to simplify interconnect generation. When there are no slaves +connected to an interconnect, the interconnect generator can automatically +connect one of these slaves (depending on the protocol) and be guaranteed +that the protocol will still be followed. All requests, however, will return +bus errors. + +- [AXIEMPTY](axiempty.v) - the AXI4 empty slave + +- [AXILEMPTY](axilempty.v) - the AXI4-lite empty slave + +## Clock domain crossing bridges + +- [Wishbone cross-clock domains](wbxclk.v) + +- [AXI4 cross-clock domains](axixclk.v) + +- [APB cross-clock domains](apbxclk.v) + +- [Asynchronous FIFO](afifo.v) -- support function diff --git a/rtl/wb2axip/addrdecode.v b/rtl/wb2axip/addrdecode.v new file mode 100644 index 0000000..2f3cac6 --- /dev/null +++ b/rtl/wb2axip/addrdecode.v @@ -0,0 +1,461 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: addrdecode.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Supports bus crossbars by answering the question, which slave +// does the current address need to be routed to? Requests are +// pipelined using valid/!stall handshaking. For those familiar with +// AXI, READY=!STALL. The outgoing stream is identical to the incoming +// one, save for the (new) o_decode field. This is a bitmask containing +// one bit for each slave that the request might be routed to, and one +// extra bit to indicate no slaves matched. +// +// The keys to the operation of this module are found in the two +// parameters, SLAVE_ADDR and SLAVE_MASK. +// +// SLAVE_ADDR specifies the address of the slave in question. It's a large +// array, with one address (i.e. one set of AW bits) for each +// potential slave address region. +// +// SLAVE_MASK specifies which of the bits in SLAVE_ADDR need to match in +// order to route a request to a that slave. +// +// It's important to guarantee that no two slaves will ever map to the +// same address, and likewise any given slave may only map to a single +// address region. +// +// Incidentally, the algorithm forces all slaves to have an address +// aligned with their memory size. Hence a 2GB memory must have an +// address (range) of either 0-2GB, 2GB-4GB, 4GB-6GB, etc. However, a +// second slave having only 8kB of memory may be placed at 0-8kB, +// 8-16kB, 16-24kB, etc. For logic minimization purposes, it is often to +// the advantage of the bus compositor to minimize the number of mask +// bits, and hence 8kB slaves may be aliased to many places in memory. +// Bus composition and address assignment, however, are both outside of +// the scope of the operation of this module. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module addrdecode #( + // {{{ + parameter NS=8, + parameter AW = 32, DW=32+32/8+1+1, + // + // SLAVE_ADDR contains address assignments for each of the + // various slaves we are adjudicating between. + parameter [NS*AW-1:0] SLAVE_ADDR = { + { 3'b111, {(AW-3){1'b0}} }, + { 3'b110, {(AW-3){1'b0}} }, + { 3'b101, {(AW-3){1'b0}} }, + { 3'b100, {(AW-3){1'b0}} }, + { 3'b011, {(AW-3){1'b0}} }, + { 3'b010, {(AW-3){1'b0}} }, + { 4'b0010, {(AW-4){1'b0}} }, + { 4'b0000, {(AW-4){1'b0}} }}, + // + // SLAVE_MASK contains a mask of those address bits in + // SLAVE_ADDR which are relevant. It shall be true that if + // !SLAVE_MASK[k] then !SLAVE_ADDR[k], for any bits of k + parameter [NS*AW-1:0] SLAVE_MASK = (NS <= 1) ? 0 + : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, + {(2){ 4'b1111, {(AW-4){1'b0}} }} }, + // + // ACCESS_ALLOWED is a bit-wise mask indicating which slaves + // may get access to the bus. If ACCESS_ALLOWED[slave] is true, + // then a master can connect to the slave via this method. This + // parameter is primarily here to support AXI (or other similar + // buses) which may have separate accesses for both read and + // write. By using this, a read-only slave can be connected, + // which would also naturally create an error on any attempt to + // write to it. + parameter [NS-1:0] ACCESS_ALLOWED = -1, + // + // If OPT_REGISTERED is set, address decoding will take an extra + // clock, and will register the results of the decoding + // operation. + parameter [0:0] OPT_REGISTERED = 0, + // + // If OPT_LOWPOWER is set, then whenever the output is not + // valid, any respective data linse will also be forced to zero + // in an effort to minimize power. + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // + input wire i_valid, + output reg o_stall, + input wire [AW-1:0] i_addr, + input wire [DW-1:0] i_data, + // + output reg o_valid, + input wire i_stall, + output reg [NS:0] o_decode, + output reg [AW-1:0] o_addr, + output reg [DW-1:0] o_data + // }}} + ); + + // Local declarations + // {{{ + // + // OPT_NONESEL controls whether or not the address lines are fully + // proscribed, or whether or not a "no-slave identified" slave should + // be created. To avoid a "no-slave selected" output, slave zero must + // have no mask bits set (and therefore no address bits set), and it + // must also allow access. + localparam [0:0] OPT_NONESEL = (!ACCESS_ALLOWED[0]) + || (SLAVE_MASK[AW-1:0] != 0); + // + wire [NS:0] request; + reg [NS-1:0] prerequest; + integer iM; + // }}} + + // prerequest + // {{{ + always @(*) + for(iM=0; iM<NS; iM=iM+1) + prerequest[iM] = (((i_addr ^ SLAVE_ADDR[iM*AW +: AW]) + &SLAVE_MASK[iM*AW +: AW])==0) + &&(ACCESS_ALLOWED[iM]); + // }}} + + // request + // {{{ + generate if (OPT_NONESEL) + begin : NO_DEFAULT_REQUEST + // {{{ + reg [NS-1:0] r_request; + + // Need to create a slave to describe when nothing is selected + // + always @(*) + begin + for(iM=0; iM<NS; iM=iM+1) + r_request[iM] = i_valid && prerequest[iM]; + if (!OPT_NONESEL && (NS > 1 && |prerequest[NS-1:1])) + r_request[0] = 1'b0; + end + + assign request[NS-1:0] = r_request; + // }}} + end else if (NS == 1) + begin : SINGLE_SLAVE + // {{{ + assign request[0] = i_valid; + // }}} + end else begin : GENERAL_CASE + // {{{ + reg [NS-1:0] r_request; + + always @(*) + begin + for(iM=0; iM<NS; iM=iM+1) + r_request[iM] = i_valid && prerequest[iM]; + if (!OPT_NONESEL && (NS > 1 && |prerequest[NS-1:1])) + r_request[0] = 1'b0; + end + + assign request[NS-1:0] = r_request; + // }}} + end endgenerate + // }}} + + // request[NS] + // {{{ + generate if (OPT_NONESEL) + begin : GENERATE_NONSEL_SLAVE + reg r_request_NS, r_none_sel; + + always @(*) + begin + // Let's assume nothing's been selected, and then check + // to prove ourselves wrong. + // + // Note that none_sel will be considered an error + // condition in the follow-on processing. Therefore + // it's important to clear it if no request is pending. + r_none_sel = i_valid && (prerequest == 0); + // + // request[NS] indicates a request for a non-existent + // slave. A request that should (eventually) return a + // bus error + // + r_request_NS = r_none_sel; + end + + assign request[NS] = r_request_NS; + end else begin : NO_NONESEL_SLAVE + assign request[NS] = 1'b0; + end endgenerate + // }}} + + // o_valid, o_addr, o_data, o_decode, o_stall + // {{{ + generate if (OPT_REGISTERED) + begin : GEN_REG_OUTPUTS + + // o_valid + // {{{ + initial o_valid = 0; + always @(posedge i_clk) + if (i_reset) + o_valid <= 0; + else if (!o_stall) + o_valid <= i_valid; + // }}} + + // o_addr, o_data + // {{{ + initial o_addr = 0; + initial o_data = 0; + always @(posedge i_clk) + if (i_reset && OPT_LOWPOWER) + begin + o_addr <= 0; + o_data <= 0; + end else if ((!o_valid || !i_stall) + && (i_valid || !OPT_LOWPOWER)) + begin + o_addr <= i_addr; + o_data <= i_data; + end else if (OPT_LOWPOWER && !i_stall) + begin + o_addr <= 0; + o_data <= 0; + end + // }}} + + // o_decode + // {{{ + initial o_decode = 0; + always @(posedge i_clk) + if (i_reset) + o_decode <= 0; + else if ((!o_valid || !i_stall) + && (i_valid || !OPT_LOWPOWER)) + o_decode <= request; + else if (OPT_LOWPOWER && !i_stall) + o_decode <= 0; + // }}} + + // o_stall + // {{{ + always @(*) + o_stall = (o_valid && i_stall); + // }}} + end else begin : GEN_COMBINATORIAL_OUTPUTS + + always @(*) + begin + o_valid = i_valid; + o_stall = i_stall; + o_addr = i_addr; + o_data = i_data; + + o_decode = request; + end + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, +`ifdef VERILATOR + // Can't declare the clock as unused for formal, + // lest it not be recognized as *the* clock + i_clk, +`endif + i_reset }; + // verilator lint_on UNUSED + // }}} + end endgenerate + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1; + + reg [AW+DW-1:0] f_idata; + always @(*) + f_idata = { i_addr, i_data }; + +`ifdef ADDRDECODE + always @(posedge i_clk) + if (!f_past_valid) + assume(i_reset); +`else + always @(posedge i_clk) + if (!f_past_valid) + assert(i_reset); + +`endif // ADDRDECODE + always @(posedge i_clk) + if (OPT_REGISTERED && (!f_past_valid || $past(i_reset))) + begin + assert(!o_valid); + assert(o_decode == 0); + end else if ($past(o_valid && i_stall) && OPT_REGISTERED) + begin + assert($stable(o_addr)); + assert($stable(o_decode)); + assert($stable(o_data)); + end + + // If the output is ever valid, there must be at least one + // decoded output + always @(*) + assert(o_valid == (o_decode != 0)); + + always @(*) + for(iM=0; iM<NS; iM=iM+1) + if (o_decode[iM]) + begin + // The address must match + assert((((o_addr ^ SLAVE_ADDR[iM*AW +: AW]) + & SLAVE_MASK[iM*AW +: AW])==0) + && ACCESS_ALLOWED[iM]); + // + // And nothing else must match + assert(o_decode == (1<<iM)); + end + + always @(*) + for(iM=0; iM<NS; iM=iM+1) + if (!ACCESS_ALLOWED[iM]) + assert(!o_decode[iM]); + + // LOWPOWER check + // {{{ + generate if (OPT_LOWPOWER && OPT_REGISTERED) + begin + always @(*) + if (!o_valid) + begin + assert(o_addr == 0); + assert(o_decode == 0); + assert(o_data == 0); + end + end endgenerate + // }}} + + // + // The output decoded value may only ever have one value high, + // never more--i.e. $onehot0 + // {{{ +`ifdef VERIFIC + always @(*) + assert($onehot0(request)); +`else + reg onehot_request; + always @(*) + begin + onehot_request = 0; + for(iM=0; iM<NS+1; iM=iM+1) + if ((request ^ (1<<iM))==0) + onehot_request = 1; + end + + always @(*) + if (request != 0) + assert(onehot_request); +`endif + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Make sure all addresses are reachable + // + reg [NS:0] f_reached; + + always @(posedge i_clk) + cover(i_valid); + + always @(posedge i_clk) + cover(o_valid); + + always @(posedge i_clk) + cover(o_valid && !i_stall); + + initial f_reached = 0; + always @(posedge i_clk) + if (i_reset) + f_reached = 0; + else if (o_valid) + f_reached = f_reached | o_decode; + + generate if (!OPT_NONESEL && ACCESS_ALLOWED[0] + && SLAVE_MASK == 0 && NS == 1) + begin + + always @(*) + cover(f_reached[0]); + + always @(posedge i_clk) + if (f_past_valid && $stable(o_valid)) + assert($stable(o_decode)); + + end else begin + + always @(*) + cover(&f_reached); + + always @(posedge i_clk) + if (f_past_valid && $stable(o_valid)) + cover($changed(o_decode)); + + end endgenerate + // }}} +`endif // FORMAL +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/afifo.v b/rtl/wb2axip/afifo.v new file mode 100644 index 0000000..99af9cc --- /dev/null +++ b/rtl/wb2axip/afifo.v @@ -0,0 +1,801 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: afifo.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A basic asynchronous FIFO. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module afifo #( + // {{{ + // LGFIFO is the log based-two of the number of entries + // in the FIFO, log_2(fifo size) + parameter LGFIFO = 3, + // + // WIDTH is the number of data bits in each entry + parameter WIDTH = 16, + // + // NFF is the number of flip flops used to cross clock domains. + // 2 is a minimum. Some applications appreciate the better + parameter NFF = 2, + // + // This core can either write on the positive edge of the clock + // or the negative edge. Set WRITE_ON_POSEDGE (the default) + // to write on the positive edge of the clock. + parameter [0:0] WRITE_ON_POSEDGE = 1'b1, + // + // Many logic elements can read from memory asynchronously. + // This burdens any following logic. By setting + // OPT_REGISTER_READS, we force all reads to be synchronous and + // not burdened by any logic. You can spare a clock of latency + // by clearing this register. + parameter [0:0] OPT_REGISTER_READS = 1'b1 +`ifdef FORMAL + // F_OPT_DATA_STB + // {{{ + // In the formal proof, F_OPT_DATA_STB includes a series of + // assumptions associated with a data strobe I/O pin--things + // like a discontinuous clock--just to make sure the core still + // works in those circumstances + , parameter [0:0] F_OPT_DATA_STB = 1'b1 + // }}} +`endif + // }}} + ) ( + // {{{ + // + // The (incoming) write data interface + input wire i_wclk, i_wr_reset_n, i_wr, + input wire [WIDTH-1:0] i_wr_data, + output reg o_wr_full, + // + // The (incoming) write data interface + input wire i_rclk, i_rd_reset_n, i_rd, + output reg [WIDTH-1:0] o_rd_data, + output reg o_rd_empty +`ifdef FORMAL + , output reg [LGFIFO:0] f_fill +`endif + // }}} + ); + + // Register/net declarations + // {{{ + // MSB = most significant bit of the FIFO address vector. It's + // just short-hand for LGFIFO, and won't work any other way. + localparam MSB = LGFIFO; + // + reg [WIDTH-1:0] mem [(1<<LGFIFO)-1:0]; + reg [LGFIFO:0] rd_addr, wr_addr, + rd_wgray, wr_rgray; + wire [LGFIFO:0] next_rd_addr, next_wr_addr; + reg [LGFIFO:0] rgray, wgray; + (* ASYNC_REG = "TRUE" *) reg [(LGFIFO+1)*(NFF-1)-1:0] + rgray_cross, wgray_cross; + wire wclk; + reg [WIDTH-1:0] lcl_rd_data; + reg lcl_read, lcl_rd_empty; + // }}} + + // wclk - Write clock generation + // {{{ + generate if (WRITE_ON_POSEDGE) + begin : GEN_POSEDGE_WRITES + + assign wclk = i_wclk; + + end else begin : GEN_NEGEDGE_WRITES + + assign wclk = !i_wclk; + + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Write to and read from memory + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wr_addr, wgray + // {{{ + assign next_wr_addr = wr_addr + 1; + always @(posedge wclk or negedge i_wr_reset_n) + if (!i_wr_reset_n) + begin + wr_addr <= 0; + wgray <= 0; + end else if (i_wr && !o_wr_full) + begin + wr_addr <= next_wr_addr; + wgray <= next_wr_addr ^ (next_wr_addr >> 1); + end + // }}} + + // Write to memory + // {{{ + always @(posedge wclk) + if (i_wr && !o_wr_full) + mem[wr_addr[LGFIFO-1:0]] <= i_wr_data; + // }}} + + // rd_addr, rgray + // {{{ + assign next_rd_addr = rd_addr + 1; + always @(posedge i_rclk or negedge i_rd_reset_n) + if (!i_rd_reset_n) + begin + rd_addr <= 0; + rgray <= 0; + end else if (lcl_read && !lcl_rd_empty) + begin + rd_addr <= next_rd_addr; + rgray <= next_rd_addr ^ (next_rd_addr >> 1); + end + // }}} + + // Read from memory + // {{{ + always @(*) + lcl_rd_data = mem[rd_addr[LGFIFO-1:0]]; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cross clock domains + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // read pointer -> wr_rgray + // {{{ + always @(posedge wclk or negedge i_wr_reset_n) + if (!i_wr_reset_n) + { wr_rgray, rgray_cross } <= 0; + else + { wr_rgray, rgray_cross } <= { rgray_cross, rgray }; + // }}} + + // write pointer -> rd_wgray + // {{{ + always @(posedge i_rclk or negedge i_rd_reset_n) + if (!i_rd_reset_n) + { rd_wgray, wgray_cross } <= 0; + else + { rd_wgray, wgray_cross } <= { wgray_cross, wgray }; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Flag generation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + o_wr_full = (wr_rgray == { ~wgray[MSB:MSB-1], wgray[MSB-2:0] }); + + always @(*) + lcl_rd_empty = (rd_wgray == rgray); + + // o_rd_empty, o_rd_data + // {{{ + generate if (OPT_REGISTER_READS) + begin : GEN_REGISTERED_READ + // {{{ + always @(*) + lcl_read = (o_rd_empty || i_rd); + + always @(posedge i_rclk or negedge i_rd_reset_n) + if (!i_rd_reset_n) + o_rd_empty <= 1'b1; + else if (lcl_read) + o_rd_empty <= lcl_rd_empty; + + always @(posedge i_rclk) + if (lcl_read) + o_rd_data <= lcl_rd_data; + // }}} + end else begin : GEN_COMBINATORIAL_FLAGS + // {{{ + always @(*) + lcl_read = i_rd; + + always @(*) + o_rd_empty = lcl_rd_empty; + + always @(*) + o_rd_data = lcl_rd_data; + // }}} + end endgenerate + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Start out with some register/net/macro declarations, f_past_valid,etc + // {{{ +`ifdef AFIFO +`define ASSERT assert +`define ASSUME assume +`else +`define ASSERT assert +`define ASSUME assert +`endif + + (* gclk *) reg gbl_clk; + reg f_past_valid_gbl, f_past_valid_rd, + f_rd_in_reset, f_wr_in_reset; + reg [WIDTH-1:0] past_rd_data, past_wr_data; + reg past_wr_reset_n, past_rd_reset_n, + past_rd_empty, past_wclk, past_rclk, past_rd; + reg [(LGFIFO+1)*(NFF-1)-1:0] f_wcross, f_rcross; + reg [LGFIFO:0] f_rd_waddr, f_wr_raddr; + reg [LGFIFO:0] f_rdcross_fill [NFF-1:0]; + reg [LGFIFO:0] f_wrcross_fill [NFF-1:0]; + + + initial f_past_valid_gbl = 1'b0; + always @(posedge gbl_clk) + f_past_valid_gbl <= 1'b1; + + initial f_past_valid_rd = 1'b0; + always @(posedge i_rclk) + f_past_valid_rd <= 1'b1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Reset checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + initial f_wr_in_reset = 1'b1; + always @(posedge wclk or negedge i_wr_reset_n) + if (!i_wr_reset_n) + f_wr_in_reset <= 1'b1; + else + f_wr_in_reset <= 1'b0; + + initial f_rd_in_reset = 1'b1; + always @(posedge i_rclk or negedge i_rd_reset_n) + if (!i_rd_reset_n) + f_rd_in_reset <= 1'b1; + else + f_rd_in_reset <= 1'b0; + + // + // Resets are ... + // 1. Asserted always initially, and ... + always @(*) + if (!f_past_valid_gbl) + begin + `ASSUME(!i_wr_reset_n); + `ASSUME(!i_rd_reset_n); + end + + // 2. They only ever become active together + always @(*) + if (past_wr_reset_n && !i_wr_reset_n) + `ASSUME(!i_rd_reset_n); + + always @(*) + if (past_rd_reset_n && !i_rd_reset_n) + `ASSUME(!i_wr_reset_n); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Synchronous signal assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(posedge gbl_clk) + begin + past_wr_reset_n <= i_wr_reset_n; + past_rd_reset_n <= i_rd_reset_n; + + past_wclk <= wclk; + past_rclk <= i_rclk; + + past_rd <= i_rd; + past_rd_data <= lcl_rd_data; + past_wr_data <= i_wr_data; + + past_rd_empty<= lcl_rd_empty; + end + + // + // Read side may be assumed to be synchronous + always @(*) + if (f_past_valid_gbl && i_rd_reset_n && (past_rclk || !i_rclk)) + // i.e. if (!$rose(i_rclk)) + `ASSUME(i_rd == past_rd); + + always @(*) + if (f_past_valid_rd && !f_rd_in_reset && !lcl_rd_empty + &&(past_rclk || !i_rclk)) + begin + `ASSERT(lcl_rd_data == past_rd_data); + `ASSERT(lcl_rd_empty == past_rd_empty); + end + + + generate if (F_OPT_DATA_STB) + begin + + always @(posedge gbl_clk) + `ASSUME(!o_wr_full); + + always @(posedge gbl_clk) + if (!i_wr_reset_n) + `ASSUME(!i_wclk); + + always @(posedge gbl_clk) + `ASSUME(i_wr == i_wr_reset_n); + + always @(posedge gbl_clk) + if ($changed(i_wr_reset_n)) + `ASSUME($stable(wclk)); + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Fill checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + f_fill = wr_addr - rd_addr; + + always @(*) + if (!f_wr_in_reset) + `ASSERT(f_fill <= { 1'b1, {(MSB){1'b0}} }); + + always @(*) + if (wr_addr == rd_addr) + `ASSERT(lcl_rd_empty); + + always @(*) + if ((!f_wr_in_reset && !f_rd_in_reset) + && wr_addr == { ~rd_addr[MSB], rd_addr[MSB-1:0] }) + `ASSERT(o_wr_full); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Induction checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // f_wr_in_reset -- write logic is in its reset state + // {{{ + always @(*) + if (f_wr_in_reset) + begin + `ASSERT(wr_addr == 0); + `ASSERT(wgray_cross == 0); + + `ASSERT(rd_addr == 0); + `ASSERT(rgray_cross == 0); + `ASSERT(rd_wgray == 0); + + `ASSERT(lcl_rd_empty); + `ASSERT(!o_wr_full); + end + // }}} + + // f_rd_in_reset -- read logic is in its reset state + // {{{ + always @(*) + if (f_rd_in_reset) + begin + `ASSERT(rd_addr == 0); + `ASSERT(rgray_cross == 0); + `ASSERT(rd_wgray == 0); + + `ASSERT(lcl_rd_empty); + end + // }}} + + // f_wr_raddr -- a read address to match the gray values + // {{{ + always @(posedge wclk or negedge i_wr_reset_n) + if (!i_wr_reset_n) + { f_wr_raddr, f_rcross } <= 0; + else + { f_wr_raddr, f_rcross } <= { f_rcross, rd_addr }; + // }}} + + // f_rd_waddr -- a write address to match the gray values + // {{{ + always @(posedge i_rclk or negedge i_rd_reset_n) + if (!i_rd_reset_n) + { f_rd_waddr, f_wcross } <= 0; + else + { f_rd_waddr, f_wcross } <= { f_wcross, wr_addr }; + // }}} + + integer k; + + // wgray check + // {{{ + always @(*) + `ASSERT((wr_addr ^ (wr_addr >> 1)) == wgray); + // }}} + + // wgray_cross check + // {{{ + always @(*) + for(k=0; k<NFF-1; k=k+1) + `ASSERT((f_wcross[k*(LGFIFO+1) +: LGFIFO+1] + ^ (f_wcross[k*(LGFIFO+1)+: LGFIFO+1]>>1)) + == wgray_cross[k*(LGFIFO+1) +: LGFIFO+1]); + // }}} + + // rgray check + // {{{ + always @(*) + `ASSERT((rd_addr ^ (rd_addr >> 1)) == rgray); + // }}} + + // rgray_cross check + // {{{ + always @(*) + for(k=0; k<NFF-1; k=k+1) + `ASSERT((f_rcross[k*(LGFIFO+1) +: LGFIFO+1] + ^ (f_rcross[k*(LGFIFO+1) +: LGFIFO+1]>>1)) + == rgray_cross[k*(LGFIFO+1) +: LGFIFO+1]); + // }}} + + // wr_rgray + // {{{ + always @(*) + `ASSERT((f_wr_raddr ^ (f_wr_raddr >> 1)) == wr_rgray); + // }}} + + // rd_wgray + // {{{ + always @(*) + `ASSERT((f_rd_waddr ^ (f_rd_waddr >> 1)) == rd_wgray); + // }}} + + // f_rdcross_fill + // {{{ + always @(*) + for(k=0; k<NFF-1; k=k+1) + f_rdcross_fill[k] = f_wcross[k*(LGFIFO+1) +: LGFIFO+1] + - rd_addr; + + always @(*) + f_rdcross_fill[NFF-1] = f_rd_waddr - rd_addr; + + always @(*) + for(k=0; k<NFF; k=k+1) + `ASSERT(f_rdcross_fill[k] <= { 1'b1, {(LGFIFO){1'b0}} }); + + always @(*) + for(k=1; k<NFF; k=k+1) + `ASSERT(f_rdcross_fill[k] <= f_rdcross_fill[k-1]); + always @(*) + `ASSERT(f_rdcross_fill[0] <= f_fill); + // }}} + + // f_wrcross_fill + // {{{ + always @(*) + for(k=0; k<NFF-1; k=k+1) + f_wrcross_fill[k] = wr_addr - + f_rcross[k*(LGFIFO+1) +: LGFIFO+1]; + + always @(*) + f_wrcross_fill[NFF-1] = wr_addr - f_wr_raddr; + + always @(*) + for(k=0; k<NFF; k=k+1) + `ASSERT(f_wrcross_fill[k] <= { 1'b1, {(LGFIFO){1'b0}} }); + + always @(*) + for(k=1; k<NFF; k=k+1) + `ASSERT(f_wrcross_fill[k] >= f_wrcross_fill[k-1]); + always @(*) + `ASSERT(f_wrcross_fill[0] >= f_fill); + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Clock generation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Here's the challenge: if we use $past with any of our clocks, such + // as to determine stability or any such, the proof takes forever, and + // we need to guarantee a minimum number of transitions within the + // depth of the proof. If, on the other hand, we build our own $past + // primitives--we can then finish much faster and be successful on + // any depth of proof. + + // pre_xclk is what the clock will become on the next global clock edge. + // By using it here, we can check things @(*) instead of + // @(posedge gbl_clk). Further, we can check $rose(pre_xclk) (or $fell) + // and essentially check things @(*) but while using @(global_clk). + // In other words, we can transition on @(posedge gbl_clk), but stay + // in sync with the data--rather than being behind by a clock. + // now_xclk is what the clock is currently. + // + (* anyseq *) reg pre_wclk, pre_rclk; + reg now_wclk, now_rclk; + always @(posedge gbl_clk) + begin + now_wclk <= pre_wclk; + now_rclk <= pre_rclk; + end + + always @(*) + begin + assume(i_wclk == now_wclk); + assume(i_rclk == now_rclk); + end + + always @(posedge gbl_clk) + assume(i_rclk == $past(pre_rclk)); + + // Assume both clocks start idle + // {{{ + always @(*) + if (!f_past_valid_gbl) + begin + assume(!pre_wclk && !wclk); + assume(!pre_rclk && !i_rclk); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Formal contract check --- the twin write test + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Tracking register declarations + // {{{ + reg [WIDTH-1:0] f_first, f_next; + (* anyconst *) reg [LGFIFO:0] f_addr; + reg [LGFIFO:0] f_next_addr; + reg f_first_in_fifo, f_next_in_fifo; + reg [LGFIFO:0] f_to_first, f_to_next; + reg [1:0] f_state; + + always @(*) + f_next_addr = f_addr + 1; + // }}} + + // distance_to*, *_in_fifo + // {{{ + always @(*) + begin + f_to_first = f_addr - rd_addr; + + f_first_in_fifo = 1'b1; + if ((f_to_first >= f_fill)||(f_fill == 0)) + f_first_in_fifo = 1'b0; + + if (mem[f_addr] != f_first) + f_first_in_fifo = 1'b0; + + // + // Check the second item + // + + f_to_next = f_next_addr - rd_addr; + f_next_in_fifo = 1'b1; + if ((f_to_next >= f_fill)||(f_fill == 0)) + f_next_in_fifo = 1'b0; + + if (mem[f_next_addr] != f_next) + f_next_in_fifo = 1'b0; + end + // }}} + + // f_state -- generate our state variable + // {{{ + initial f_state = 0; + always @(posedge gbl_clk) + if (!i_wr_reset_n) + f_state <= 0; + else case(f_state) + 2'b00: if (($rose(pre_wclk))&& i_wr && !o_wr_full &&(wr_addr == f_addr)) + begin + f_state <= 2'b01; + f_first <= i_wr_data; + end + 2'b01: if ($rose(pre_rclk)&& lcl_read && rd_addr == f_addr) + f_state <= 2'b00; + else if ($rose(pre_wclk) && i_wr && !o_wr_full ) + begin + f_state <= 2'b10; + f_next <= i_wr_data; + end + 2'b10: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_addr) + f_state <= 2'b11; + 2'b11: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_next_addr) + f_state <= 2'b00; + endcase + // }}} + + // f_state invariants + // {{{ + always @(*) + if (i_wr_reset_n) case(f_state) + 2'b00: begin end + 2'b01: begin + `ASSERT(f_first_in_fifo); + `ASSERT(wr_addr == f_next_addr); + `ASSERT(f_fill >= 1); + end + 2'b10: begin + `ASSERT(f_first_in_fifo); + `ASSERT(f_next_in_fifo); + if (!lcl_rd_empty && (rd_addr == f_addr)) + `ASSERT(lcl_rd_data == f_first); + `ASSERT(f_fill >= 2); + end + 2'b11: begin + `ASSERT(rd_addr == f_next_addr); + `ASSERT(f_next_in_fifo); + `ASSERT(f_fill >= 1); + if (!lcl_rd_empty) + `ASSERT(lcl_rd_data == f_next); + end + endcase + // }}} + + generate if (OPT_REGISTER_READS) + begin + reg past_o_rd_empty; + + always @(posedge gbl_clk) + past_o_rd_empty <= o_rd_empty; + + always @(posedge gbl_clk) + if (f_past_valid_gbl && i_rd_reset_n) + begin + if ($past(!o_rd_empty && !i_rd && i_rd_reset_n)) + `ASSERT($stable(o_rd_data)); + end + + always @(posedge gbl_clk) + if (!f_rd_in_reset && i_rd_reset_n && i_rclk && !past_rclk) + begin + if (past_o_rd_empty) + `ASSERT(o_rd_data == past_rd_data); + if (past_rd) + `ASSERT(o_rd_data == past_rd_data); + end + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Prove that we can read and write the FIFO + // {{{ + always @(*) + if (i_wr_reset_n && i_rd_reset_n) + begin + cover(o_rd_empty); + cover(!o_rd_empty); + cover(f_state == 2'b01); + cover(f_state == 2'b10); + cover(f_state == 2'b11); + cover(&f_fill[MSB-1:0]); + + cover(i_rd); + cover(i_rd && !o_rd_empty); + end + // }}} + +`ifdef AFIFO + generate if (!F_OPT_DATA_STB) + begin : COVER_FULL + // {{{ + reg cvr_full; + + initial cvr_full = 1'b0; + always @(posedge gbl_clk) + if (!i_wr_reset_n) + cvr_full <= 1'b0; + else if (o_wr_full) + cvr_full <= 1'b1; + + + always @(*) + if (f_past_valid_gbl && i_wr_reset_n) + begin + cover(o_wr_full); + cover(o_rd_empty && cvr_full); + cover(o_rd_empty && f_fill == 0 && cvr_full); + end + // }}} + end else begin : COVER_NEARLY_FULL + // {{{ + reg cvr_nearly_full; + + initial cvr_nearly_full = 1'b0; + always @(posedge gbl_clk) + if (!i_wr_reset_n) + cvr_nearly_full <= 1'b0; + else if (f_fill == { 1'b0, {(LGFIFO){1'b1} }}) + cvr_nearly_full <= 1'b1; + + + always @(*) + if (f_past_valid_gbl && i_wr_reset_n) + begin + cover(f_fill == { 1'b0, {(LGFIFO){1'b1} }}); + cover(cvr_nearly_full && i_wr_reset_n); + cover(o_rd_empty && cvr_nearly_full); + cover(o_rd_empty && f_fill == 0 && cvr_nearly_full); + end + // }}} + end endgenerate +`endif + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/apbslave.v b/rtl/wb2axip/apbslave.v new file mode 100644 index 0000000..e3ba027 --- /dev/null +++ b/rtl/wb2axip/apbslave.v @@ -0,0 +1,229 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: apbslave.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Just a simple demonstration APB slave +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// }}} +`default_nettype none +// +module apbslave #( + // {{{ + parameter C_APB_ADDR_WIDTH = 12, + parameter C_APB_DATA_WIDTH = 32, + localparam AW = C_APB_ADDR_WIDTH, + localparam DW = C_APB_DATA_WIDTH, + localparam APBLSB = $clog2(C_APB_DATA_WIDTH)-3 + // }}} + ) ( + // {{{ + input wire PCLK, PRESETn, + input wire PSEL, + input wire PENABLE, + output reg PREADY, + input wire [AW-1:0] PADDR, + input wire PWRITE, + input wire [DW-1:0] PWDATA, + input wire [DW/8-1:0] PWSTRB, + input wire [2:0] PPROT, + output reg [DW-1:0] PRDATA, + output wire PSLVERR + // }}} + ); + + // Register declarations + // {{{ + // Just our demonstration "memory" here + reg [DW-1:0] mem [0:(1<<(AW-APBLSB))-1]; + integer ik; + // }}} + + // PREADY + // {{{ + initial PREADY = 1'b0; + always @(posedge PCLK) + if (!PRESETn) + PREADY <= 1'b0; + else if (PSEL && !PENABLE) + PREADY <= 1'b1; + else + PREADY <= 1'b0; + // }}} + + // mem writes + // {{{ + always @(posedge PCLK) + if (PRESETn && PSEL && !PENABLE && PWRITE) + begin + for(ik=0; ik<DW/8; ik=ik+1) + if (PWSTRB[ik]) + mem[PADDR[AW-1:APBLSB]][8*ik +: 8] <= PWDATA[8*ik +: 8]; + end + // }}} + + // PRDATA, memory reads + // {{{ + always @(posedge PCLK) + if (PSEL && !PENABLE && !PWRITE) + PRDATA <= mem[PADDR[AW-1:APBLSB]]; + // }}} + + // PSLVERR -- unused in this design, and so kept at zero + // {{{ + assign PSLVERR = 1'b0; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, PADDR[APBLSB-1:0], PPROT }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + + //////////////////////////////////////////////////////////////////////// + // + // Interface properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + fapb_slave #(.AW(C_APB_ADDR_WIDTH), .DW(C_APB_DATA_WIDTH), + .F_OPT_MAXSTALL(1) + ) fapb (PCLK, PRESETn, + PSEL, PENABLE, PREADY, PADDR, PWRITE, PWDATA, PWSTRB, + PPROT, PRDATA, PSLVERR); + + always @(*) + if (PRESETn && PSEL && PENABLE) + assert(PREADY); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + (* anyconst *) reg [AW-1:0] f_addr; + reg [DW-1:0] f_data; + +`ifndef FORMAL + initial for(ik=0; ik<(1<<AW); ik=ik+1) + mem[ik] = 0; +`endif + always @(*) + if (!PRESETn) + assume(mem[f_addr[AW-1:APBLSB]] == f_data); + + initial f_data = 0; + always @(posedge PCLK) + if (PRESETn && PSEL && !PENABLE && PWRITE && PADDR[AW-1:APBLSB] == f_addr[AW-1:APBLSB]) + begin + for(ik=0; ik<DW/8; ik=ik+1) + if (PWSTRB[ik]) + f_data[ik*8 +: 8] <= PWDATA[ik*8 +: 8]; + end + + always @(posedge PCLK) + if (PSEL && PENABLE && PREADY && !PWRITE && PADDR[AW-1:APBLSB] == f_addr[AW-1:APBLSB]) + assert(PRDATA == f_data); + + always @(*) + assert(f_data == mem[f_addr[AW-1:APBLSB]]); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + reg [2:0] cvr_reads, cvr_writes, cvr_seq; + + initial cvr_writes = 0; + always @(posedge PCLK) + if (!PRESETn) + cvr_writes <= 0; + else if (PSEL && PENABLE && PREADY && PWRITE && !cvr_writes[2]) + cvr_writes <= cvr_writes + 1; + + initial cvr_reads = 0; + always @(posedge PCLK) + if (!PRESETn) + cvr_reads <= 0; + else if (PSEL && PENABLE && PREADY && !PWRITE && !cvr_reads[2]) + cvr_reads <= cvr_reads + 1; + + always @(*) + cover(cvr_writes[2]); + + always @(*) + cover(cvr_reads[2]); + + initial cvr_seq = 0; + always @(posedge PCLK) + if (!PRESETn) + cvr_seq <= 0; + else if (PSEL && PENABLE && PREADY && !PWRITE && PADDR == f_addr) + begin + if (cvr_seq == 0 && PRDATA == 32'h12345678) + cvr_seq[0] <= 1'b1; + if (cvr_seq[0] && PRDATA == 32'h87654321) + cvr_seq[1] <= 1'b1; + if (cvr_seq[1] && PRDATA == 32'h0) + cvr_seq[2] <= 1'b1; + end + + always @(*) + if (PRESETn) + begin + cover(cvr_seq[0]); + cover(cvr_seq[1]); + cover(cvr_seq[2]); + end + + always @(*) + cover(PRESETn && !PSEL && !PENABLE && cvr_seq[2]); + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/apbxclk.v b/rtl/wb2axip/apbxclk.v new file mode 100644 index 0000000..831675b --- /dev/null +++ b/rtl/wb2axip/apbxclk.v @@ -0,0 +1,610 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: apbxclk.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// }}} +`default_nettype none +// +module apbxclk #( + // {{{ + parameter C_APB_ADDR_WIDTH = 12, + parameter C_APB_DATA_WIDTH = 32, + parameter [0:0] OPT_REGISTERED = 1'b0, + localparam AW = C_APB_ADDR_WIDTH, + localparam DW = C_APB_DATA_WIDTH + // }}} + ) ( + // {{{ + input wire S_APB_PCLK, S_PRESETn, + input wire S_APB_PSEL, + input wire S_APB_PENABLE, + output reg S_APB_PREADY, + input wire [AW-1:0] S_APB_PADDR, + input wire S_APB_PWRITE, + input wire [DW-1:0] S_APB_PWDATA, + input wire [DW/8-1:0] S_APB_PWSTRB, + input wire [2:0] S_APB_PPROT, + output wire [DW-1:0] S_APB_PRDATA, + output wire S_APB_PSLVERR, + // + input wire M_APB_PCLK, + output reg M_PRESETn, + output reg M_APB_PSEL, + output reg M_APB_PENABLE, + input wire M_APB_PREADY, + output wire [AW-1:0] M_APB_PADDR, + output wire M_APB_PWRITE, + output wire [DW-1:0] M_APB_PWDATA, + output wire [DW/8-1:0] M_APB_PWSTRB, + output wire [2:0] M_APB_PPROT, + input wire [DW-1:0] M_APB_PRDATA, + input wire M_APB_PSLVERR + // }}} + ); + + // Local declarations + // {{{ + reg reset_pipe, full_reset_pipe, full_reset; + + reg s_request, s_tfr_request; + reg m_request, m_request_pipe; + + reg m_ack; + reg s_ack, s_ack_pipe; + reg [DW-1:0] m_prdata; + reg m_pslverr; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Synchronize resets + // {{{ + always @(posedge M_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + { M_PRESETn, reset_pipe } <= 0; + else + { M_PRESETn, reset_pipe } <= { reset_pipe, 1'b1 }; + + always @(posedge S_APB_PCLK or negedge M_PRESETn) + if (!M_PRESETn) + { full_reset, full_reset_pipe } <= 0; + else + { full_reset, full_reset_pipe } <= { full_reset_pipe, 1'b1 }; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Move the request across clock domains + // {{{ + + // s_request + // {{{ + // Step 1: Register the request + // Always register anything before moving cross clock domains + always @(posedge S_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + s_request <= 0; + else if (S_APB_PREADY) + s_request <= 0; + else if (S_APB_PSEL && !S_APB_PENABLE) + s_request <= 1; + // }}} + + // s_tfr_request + // {{{ + // Step 2: Forward requests--but only when the downstream handshake + // is ready to accept them + always @(posedge S_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + s_tfr_request <= 0; + else if (S_APB_PREADY) + s_tfr_request <= 0; + else if ((S_APB_PSEL && !S_APB_PENABLE) || s_request) + s_tfr_request <= (full_reset && !s_ack); + // }}} + + // m_request, m_request_pipe -- 3. Capture the request in new clk domain + // {{{ + always @(posedge M_APB_PCLK or negedge M_PRESETn) + if (!M_PRESETn) + { m_request, m_request_pipe } <= 0; + else + { m_request, m_request_pipe } + <= { m_request_pipe, s_tfr_request }; + // }}} + + // M_APB_PSEL, M_APB_PENABLE -- 4. Forward the request downstream + // {{{ + always @(posedge M_APB_PCLK or negedge M_PRESETn) + if (!M_PRESETn) + begin + M_APB_PSEL <= 0; + M_APB_PENABLE <= 1'b0; + end else begin + M_APB_PSEL <= m_request && !m_ack + && (!M_APB_PSEL || !M_APB_PENABLE || !M_APB_PREADY); + M_APB_PENABLE <= M_APB_PSEL && (!M_APB_PSEL || !M_APB_PENABLE || !M_APB_PREADY); + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Move the response back across clock domains + // {{{ + + // m_ack + // {{{ + always @(posedge M_APB_PCLK or negedge M_PRESETn) + if (!M_PRESETn) + m_ack <= 0; + else if (m_request && !m_ack && M_APB_PSEL && M_APB_PREADY && M_APB_PENABLE) + m_ack <= 1'b1; + else if (!m_request) + m_ack <= 1'b0; + // }}} + + // s_ack, s_ack_pipe + // {{{ + always @(posedge S_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + { s_ack, s_ack_pipe } <= 0; + else + { s_ack, s_ack_pipe } <= { s_ack_pipe, m_ack }; + // }}} + + // S_APB_PREADY + // {{{ + always @(posedge S_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + S_APB_PREADY <= 0; + else + S_APB_PREADY <= !S_APB_PREADY && s_tfr_request && s_ack; + // }}} + + // m_prdata + // {{{ + always @(posedge M_APB_PCLK) + if (M_APB_PSEL && M_APB_PREADY) + m_prdata <= M_APB_PRDATA; + // }}} + + // m_pslverr + // {{{ + always @(posedge M_APB_PCLK or negedge M_PRESETn) + if (!M_PRESETn) + m_pslverr <= 0; + else if (M_APB_PSEL && M_APB_PREADY) + m_pslverr <= M_APB_PSLVERR; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Handle the ancillary (data bearing) signals + // {{{ + + generate if (OPT_REGISTERED) + begin : GEN_REGISTERS + // {{{ + // Register all results into the new domain when + // crossing clock domains + reg [AW-1:0] m_paddr; + reg m_pwrite; + reg [DW-1:0] m_pwdata; + reg [DW/8-1:0] m_pwstrb; + reg [2:0] m_pprot; + + reg [DW-1:0] s_prdata; + reg s_pslverr; + + always @(posedge M_APB_PCLK) + if (m_request && !m_ack) + begin + m_paddr <= S_APB_PADDR; + m_pwrite <= S_APB_PWRITE; + m_pwdata <= S_APB_PWDATA; + m_pwstrb <= S_APB_PWSTRB; + m_pprot <= S_APB_PPROT; + end + + assign M_APB_PADDR = m_paddr; + assign M_APB_PWRITE = m_pwrite; + assign M_APB_PWDATA = m_pwdata; + assign M_APB_PWSTRB = m_pwstrb; + assign M_APB_PPROT = m_pprot; + + always @(posedge S_APB_PCLK or negedge S_PRESETn) + if (!S_PRESETn) + s_pslverr <= 1'b0; + else if (s_tfr_request && s_ack + && (!S_APB_PREADY || !S_APB_PENABLE)) + s_pslverr <= m_pslverr; + else + s_pslverr <= 0; + + always @(posedge S_APB_PCLK) + if (s_request && s_ack) + begin + s_prdata <= m_prdata; + end else begin + s_prdata <= 0; + end + + assign S_APB_PRDATA = s_prdata; + assign S_APB_PSLVERR = s_pslverr; + // }}} + end else begin : NO_REGISTERING + // {{{ + // Results will be stable whenever PSEL is true. There's + // really no *need* to register them + + assign M_APB_PADDR = S_APB_PADDR; + assign M_APB_PWRITE = S_APB_PWRITE; + assign M_APB_PWDATA = S_APB_PWDATA; + assign M_APB_PWSTRB = S_APB_PWSTRB; + assign M_APB_PPROT = S_APB_PPROT; + + assign S_APB_PRDATA = m_prdata; + assign S_APB_PSLVERR = m_pslverr && S_APB_PREADY; + // }}} + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + (* gclk *) reg gbl_clk; + + //////////////////////////////////////////////////////////////////////// + // + // Interface properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + fapb_slave #( + // {{{ + .AW(C_APB_ADDR_WIDTH), .DW(C_APB_DATA_WIDTH), + .F_OPT_MAXSTALL(0), .F_OPT_ASYNC_RESET(1'b1), + .F_OPT_SLVERR(1'b1) + // }}} + ) fslv ( + // {{{ + .PCLK(S_APB_PCLK), .PRESETn(S_PRESETn), + .PSEL(S_APB_PSEL), + .PENABLE(S_APB_PENABLE), + .PREADY(S_APB_PREADY), + .PADDR(S_APB_PADDR), + .PWRITE(S_APB_PWRITE), .PWDATA(S_APB_PWDATA), + .PWSTRB(S_APB_PWSTRB), + .PPROT(S_APB_PPROT), + .PRDATA(S_APB_PRDATA), .PSLVERR(S_APB_PSLVERR) + // }}} + ); + + fapb_master #( + // {{{ + .AW(C_APB_ADDR_WIDTH), .DW(C_APB_DATA_WIDTH), + .F_OPT_MAXSTALL(0), .F_OPT_ASYNC_RESET(1'b1), + .F_OPT_SLVERR(1'b1) + // }}} + ) fmas ( + // {{{ + .PCLK(M_APB_PCLK), .PRESETn(M_PRESETn), + .PSEL(M_APB_PSEL), .PENABLE(M_APB_PENABLE), + .PREADY(M_APB_PREADY), .PADDR(M_APB_PADDR), + .PWRITE(M_APB_PWRITE), .PWDATA(M_APB_PWDATA), + .PWSTRB(M_APB_PWSTRB), + .PPROT(M_APB_PPROT), + .PRDATA(M_APB_PRDATA), .PSLVERR(M_APB_PSLVERR) + // }}} + ); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Clock stability + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg f_past_valid_gbl; + + initial f_past_valid_gbl <= 1'b0; + always @(posedge gbl_clk) + f_past_valid_gbl <= 1'b1; + + always @(*) + if (!f_past_valid_gbl) + assume(!S_PRESETn); + + always @(posedge gbl_clk) + if (!$rose(S_APB_PCLK)) + begin + assume(!$rose(S_PRESETn)); + assume($stable(S_APB_PSEL)); + assume($stable(S_APB_PENABLE)); + assume($stable(S_APB_PADDR)); + assume($stable(S_APB_PWRITE)); + assume($stable(S_APB_PWDATA)); + assume($stable(S_APB_PWSTRB)); + assume($stable(S_APB_PPROT)); + // + if (f_past_valid_gbl && S_PRESETn) + begin + assert($stable(S_APB_PREADY)); + if (OPT_REGISTERED) + begin + assert($stable(S_APB_PRDATA)); + assert($stable(S_APB_PSLVERR)); + end else if ($past(S_APB_PREADY) && S_APB_PREADY) + begin + assert($stable(S_APB_PRDATA)); + assert($stable(S_APB_PSLVERR)); + end + end + end + + always @(posedge gbl_clk) + if (!$rose(M_APB_PCLK)) + begin + if (f_past_valid_gbl) + begin + assert(!$rose(M_PRESETn)); + end + + if (f_past_valid_gbl && M_PRESETn) + begin + assert($stable(M_APB_PSEL)); + assert($stable(M_APB_PENABLE)); + if (OPT_REGISTERED) + begin + assert($stable(M_APB_PADDR)); + assert($stable(M_APB_PWRITE)); + assert($stable(M_APB_PWDATA)); + assert($stable(M_APB_PWSTRB)); + assert($stable(M_APB_PPROT)); + end else if ($past(M_APB_PSEL) && M_APB_PSEL) + begin + assert($stable(M_APB_PADDR)); + assert($stable(M_APB_PWRITE)); + assert($stable(M_APB_PWDATA)); + assert($stable(M_APB_PWSTRB)); + assert($stable(M_APB_PPROT)); + end + end + // + assume($stable(M_APB_PREADY)); + assume($stable(M_APB_PRDATA)); + assume($stable(M_APB_PSLVERR)); + end + + always @(posedge gbl_clk) + if ($past(S_APB_PSEL && !S_APB_PREADY) && $past(S_PRESETn) && S_PRESETn) + begin + assume($stable(S_APB_PSEL)); + assume($stable(S_APB_PADDR)); + assume($stable(S_APB_PWRITE)); + assume($stable(S_APB_PWDATA)); + assume($stable(S_APB_PWSTRB)); + assume($stable(S_APB_PPROT)); + end + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Induction invariants + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + case({ S_PRESETn, reset_pipe, M_PRESETn, full_reset_pipe, full_reset }) + 5'b0_00_00: begin end + 5'b1_00_00: begin end + 5'b1_10_00: begin end + 5'b1_11_00: begin end + 5'b1_11_10: begin end + 5'b1_11_11: begin end + default: assert(0); + endcase + + always @(*) + casez({ s_request, s_tfr_request, m_request_pipe, m_request, m_ack, s_ack_pipe, s_ack }) + 7'b00_00_000: begin end + 7'b10_00_000: begin end + 7'b11_00_000: begin end + 7'b11_10_000: begin end + 7'b11_11_000: begin end + 7'b11_11_100: begin end + 7'b11_11_110: begin end + 7'b11_11_111: begin end + 7'b?0_11_111: begin end + 7'b?0_01_111: begin end + 7'b?0_00_111: begin end + 7'b?0_00_011: begin end + 7'b?0_00_001: begin end + 7'b?0_00_000: begin end + default: assert(0); + endcase + + always @(posedge S_APB_PCLK) + if (s_request && S_PRESETn) + assert(S_APB_PSEL && S_APB_PENABLE); + + always @(posedge gbl_clk) + if (!s_tfr_request && S_PRESETn) + assert(!M_APB_PSEL); + + always @(posedge gbl_clk) + if (M_APB_PSEL) + assert(m_request && !m_ack); + + always @(posedge gbl_clk) + if (S_PRESETn && (m_ack || s_ack_pipe || s_ack || S_APB_PREADY)) + assert(!M_APB_PSEL); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + (* anyconst *) reg fnvr_check; + (* anyconst *) reg [3+DW/8+DW:0] fnvr_write; + (* anyconst *) reg [AW-1:0] fnvr_addr; + (* anyconst *) reg [1+DW-1:0] fnvr_return; + + always @(posedge gbl_clk) + if (S_APB_PSEL && fnvr_check) + begin + assume({ S_APB_PPROT, S_APB_PWSTRB, S_APB_PWDATA } != fnvr_write); + assume(S_APB_PADDR != fnvr_addr); + end + + always @(posedge gbl_clk) + if (M_APB_PSEL && fnvr_check && M_PRESETn) + begin + assert({ M_APB_PPROT, M_APB_PWSTRB, M_APB_PWDATA } != fnvr_write); + assert(M_APB_PADDR != fnvr_addr); + end + + always @(posedge gbl_clk) + if (M_APB_PSEL && M_PRESETn) + begin + assert(M_APB_PADDR == S_APB_PADDR); + assert(M_APB_PWRITE == S_APB_PWRITE); + assert(M_APB_PWDATA == S_APB_PWDATA); + assert(M_APB_PWSTRB == S_APB_PWSTRB); + end + + always @(posedge gbl_clk) + if (fnvr_check && M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY) + assume({ M_APB_PSLVERR, M_APB_PRDATA } != fnvr_return); + + always @(posedge gbl_clk) + if (fnvr_check && m_ack) + assert({ m_pslverr, m_prdata } != fnvr_return); + + always @(posedge gbl_clk) + if (fnvr_check && S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY) + assert({ S_APB_PSLVERR, S_APB_PRDATA } != fnvr_return); + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + reg [2:0] cvr_reads, cvr_writes; + + initial cvr_writes = 0; + always @(posedge S_APB_PCLK) + if (!S_PRESETn) + cvr_writes <= 0; + else if (S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY && S_APB_PWRITE + && !cvr_writes[2]) + cvr_writes <= cvr_writes + 1; + + initial cvr_reads = 0; + always @(posedge S_APB_PCLK) + if (!S_PRESETn) + cvr_reads <= 0; + else if (S_APB_PSEL && S_APB_PENABLE && S_APB_PREADY && !S_APB_PWRITE + && !cvr_reads[2]) + cvr_reads <= cvr_reads + 1; + + always @(*) + if (S_PRESETn) + begin + cover(cvr_writes >= 2); + cover(cvr_reads >= 2); + + cover(cvr_writes >= 3); + cover(cvr_reads >= 3); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // "Careless" assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_NCLK = 5; + reg [F_NCLK-1:0] gbl_sclk, gbl_mclk; + + always @(posedge gbl_clk) + gbl_sclk <= { gbl_sclk[F_NCLK-2:0], S_APB_PCLK }; + + always @(posedge gbl_clk) + gbl_mclk <= { gbl_mclk[F_NCLK-2:0], M_APB_PCLK }; + + always @(posedge gbl_clk) + if (gbl_sclk == 0) + assume(S_APB_PCLK); + else if (&gbl_sclk) + assume(!S_APB_PCLK); + + always @(posedge gbl_clk) + if (gbl_mclk == 0) + assume(M_APB_PCLK); + else if (&gbl_mclk) + assume(!M_APB_PCLK); + + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axi2axi3.v b/rtl/wb2axip/axi2axi3.v new file mode 100644 index 0000000..3682949 --- /dev/null +++ b/rtl/wb2axip/axi2axi3.v @@ -0,0 +1,897 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi2axi3.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Bridge from an AXI4 slave to an AXI3 master +// +// The goal here is to not lose bus resolution, capacity or capability, +// while bridging from AXI4 to AXI3. The biggest problem with such +// a bridge is that we'll need to break large requests (AxLEN>15) up +// into smaller packets. After that, everything should work as normal +// with only minor modifications for AxCACHE. +// +// Opportunity: +// The cost of this core is very much determined by the number of ID's +// supported. It should be possible, with little extra cost or effort, +// to reduce the ID space herein. This should be a topic for future +// exploration. +// +// Status: +// This core is an unverified first draft. It has past neither formal +// nor simulation testing. Therefore, it is almost certain to have bugs +// within it. Use it at your own risk. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +// +module axi2axi3 #( + // {{{ + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The AXI4 incoming/slave interface + input reg S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input reg [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input reg [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input reg [7:0] S_AXI_AWLEN, + input reg [2:0] S_AXI_AWSIZE, + input reg [1:0] S_AXI_AWBURST, + input reg S_AXI_AWLOCK, + input reg [3:0] S_AXI_AWCACHE, + input reg [2:0] S_AXI_AWPROT, + input reg [3:0] S_AXI_AWQOS, + // + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [7:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire S_AXI_RLAST, + output wire [1:0] S_AXI_RRESP, + // + // + // The AXI3 Master (outgoing) interface + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [3:0] M_AXI_AWLEN, + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, + output wire [1:0] M_AXI_AWLOCK, + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_WID, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [3:0] M_AXI_ARLEN, + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire [1:0] M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP + // }}} + ); + + // + localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3; + localparam [0:0] OPT_LOWPOWER = 1'b0; + localparam LGWFIFO = 4; + localparam NID = (1<<C_AXI_ID_WIDTH); + parameter LGFIFO = 8; + + genvar ID; + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // WRITE SIDE + // {{{ + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + // + // Write address channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [7:0] r_awlen; + reg [3:0] next_wlen; + wire awskd_valid; + reg awskd_ready; + wire [C_AXI_ID_WIDTH-1:0] awskd_id; + wire [C_AXI_ADDR_WIDTH-1:0] awskd_addr; + wire [7:0] awskd_len; + wire [2:0] awskd_size; + wire [1:0] awskd_burst; + wire awskd_lock; + wire [3:0] awskd_cache; + wire [2:0] awskd_prot; + wire [3:0] awskd_qos; + reg axi_awvalid; + reg [C_AXI_ID_WIDTH-1:0] axi_awid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; + reg [3:0] axi_awlen; + reg [2:0] axi_awsize; + reg [1:0] axi_awburst; + reg [1:0] axi_awlock; + reg [3:0] axi_awcache; + reg [2:0] axi_awprot; + reg [3:0] axi_awqos; + + skidbuffer #( + .DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + 8 + 3 + 2 + 1+4+3+4), + .OPT_OUTREG(1'b0) + ) awskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, + S_AXI_AWSIZE, S_AXI_AWBURST, S_AXI_AWLOCK, + S_AXI_AWCACHE, S_AXI_AWPROT, S_AXI_AWQOS }), + .o_valid(awskd_valid), .i_ready(awskd_ready), + .o_data({ awskd_id, awskd_addr, awskd_len, + awskd_size, awskd_burst, awskd_lock, + awskd_cache, awskd_prot, awskd_qos }) + ); + + always @(*) + begin + awskd_ready = 1; + if (r_awlen > 0) + awskd_ready = 0; + if (M_AXI_AWVALID && M_AXI_AWREADY) + awskd_ready = 0; + if (|wfifo_fill[LGWFIFO:LGWFIFO-1]) + awskd_ready = 0; + end + + initial axi_awvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_awvalid <= 0; + else if (awskd_valid || r_awlen > 0) + axi_awvalid <= 1; + else if (M_AXI_AWREADY) + axi_awvalid <= 0; + + initial r_awlen = 0; + initial axi_awlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_awlen <= 0; + axi_awlen <= 0; + end else if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + if (r_awlen > 15) + begin + axi_awlen <= 4'd15; + r_awlen <= r_awlen - 8'd16; + end else if (r_awlen > 0) + begin + axi_awlen[3:0] <= r_awlen[3:0] - 1; + r_awlen <= 0; + end else if (awskd_valid) + begin + if (awskd_len > 15) + begin + r_awlen <= awskd_len + 1'b1 - 8'd16; + axi_awlen <= 4'd15; + end else begin + r_awlen <= 0; + axi_awlen <= awskd_len[3:0]; + end + end else begin + r_awlen <= 0; + axi_awlen <= 0; + end + end + + + always @(posedge S_AXI_ACLK) + if (awskd_valid && awskd_ready) + axi_awaddr <= awskd_addr; + else if (M_AXI_AWVALID && M_AXI_AWREADY) + begin + // Verilator lint_off WIDTH + axi_awaddr <= axi_awaddr + ((M_AXI_AWLEN + 1) << M_AXI_AWSIZE); + // Verilator lint_on WIDTH + + case(M_AXI_AWSIZE) + 3'b000: begin end + 3'b001: axi_awaddr[ 0] <= 0; + 3'b010: axi_awaddr[1:0] <= 0; + 3'b011: axi_awaddr[2:0] <= 0; + 3'b100: axi_awaddr[3:0] <= 0; + 3'b101: axi_awaddr[4:0] <= 0; + 3'b110: axi_awaddr[5:0] <= 0; + 3'b111: axi_awaddr[6:0] <= 0; + endcase + end + + + initial M_AXI_AWSIZE = ADDRLSB[2:0]; + always @(posedge S_AXI_ACLK) + begin + if (awskd_valid && awskd_ready) + begin + axi_awid <= awskd_id; + axi_awsize <= awskd_size; + axi_awburst <= awskd_burst; + axi_awlock[0]<=awskd_lock; + axi_awcache <= awskd_cache; + axi_awprot <= awskd_prot; + axi_awqos <= awskd_qos; + end + + if (C_AXI_DATA_WIDTH < 16) + axi_awsize <= 3'b000; + else if (C_AXI_DATA_WIDTH < 32) + axi_awsize[2:1] <= 2'b00; + else if (C_AXI_DATA_WIDTH < 128) + axi_awsize[2] <= 1'b0; + + axi_awlock[1] <= 1'b0; + end + + assign M_AXI_AWVALID = axi_awvalid; + assign M_AXI_AWID = axi_awid; + assign M_AXI_AWADDR = axi_awaddr; + assign M_AXI_AWLEN = axi_awlen; + assign M_AXI_AWSIZE = axi_awsize; + assign M_AXI_AWBURST = axi_awburst; + assign M_AXI_AWLOCK = axi_awlock; + assign M_AXI_AWCACHE = axi_awcache; + assign M_AXI_AWPROT = axi_awprot; + assign M_AXI_AWQOS = axi_awqos; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write data channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + wire wskd_valid; + reg wskd_ready, write_idle; + wire [C_AXI_DATA_WIDTH-1:0] wskd_data; + wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; + wire wskd_last; + reg [4:0] r_wlen; + wire [C_AXI_ID_WIDTH-1:0] wfifo_id, raw_wfifo_id; + wire [3:0] wfifo_wlen, raw_wfifo_wlen; + reg next_wlast, r_wlast; + wire raw_wfifo_last; + wire wfifo_empty, wfifo_full; + wire [LGWFIFO:0] wfifo_fill; + reg axi_wvalid; + reg [C_AXI_DATA_WIDTH-1:0] axi_wdata; + reg [C_AXI_DATA_WIDTH/8-1:0] axi_wstrb; + reg [C_AXI_ID_WIDTH-1:0] axi_wid; + reg axi_wlast; + + skidbuffer #( + .DW(C_AXI_DATA_WIDTH + (C_AXI_DATA_WIDTH/8) + 1), + .OPT_OUTREG(1'b0) + ) wskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB, S_AXI_WLAST }), + .o_valid(wskd_valid), .i_ready(wskd_ready), + .o_data({ wskd_data, wskd_strb, wskd_last }) + ); + + always @(*) + wskd_ready = !M_AXI_WVALID || M_AXI_WREADY; + + initial axi_wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_wvalid <= 0; + else if (wskd_valid) + axi_wvalid <= 1; + else if (M_AXI_WREADY) + axi_wvalid <= 0; + + initial write_idle = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + write_idle <= 1; + else if (M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST && !wskd_valid) + write_idle <= 1; + else if (wskd_valid && wskd_ready) + write_idle <= 0; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + axi_wdata <= wskd_data; + axi_wstrb <= wskd_strb; + + if (OPT_LOWPOWER && !wskd_valid) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + end + end + + always @(*) + begin + if (r_awlen[7:4] != 0) + next_wlen = 4'd15; + else if (r_awlen[3:0] != 0) + next_wlen = r_awlen[3:0]; + else if (awskd_len[7:4] != 0) + next_wlen = 4'd15; + else + next_wlen = awskd_len[3:0]; + + if (r_awlen != 0) + next_wlast = (r_awlen[7:4] == 0); + else + next_wlast = (awskd_len[7:4] == 0); + end + + sfifo #(.BW(C_AXI_ID_WIDTH+4+1), .LGFLEN(LGWFIFO)) + wfifo(S_AXI_ACLK, !S_AXI_ARESETN, + (!M_AXI_AWVALID || M_AXI_AWREADY)&&(awskd_valid||(r_awlen > 0)) + && !write_idle, + // The length of the next burst + { ((r_awlen != 0) ? M_AXI_AWID : awskd_id), + next_wlen, next_wlast }, + wfifo_full, wfifo_fill, + // + (!M_AXI_WVALID || M_AXI_WREADY), + { raw_wfifo_id, raw_wfifo_wlen, raw_wfifo_last }, + wfifo_empty); + + assign wfifo_id = (wfifo_empty) ? awskd_id : raw_wfifo_id; + assign wfifo_wlen = (wfifo_empty) ? next_wlen : raw_wfifo_wlen; + + initial r_wlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + r_wlen <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (r_wlen > 1) + r_wlen <= r_wlen - 1; + else if (!wfifo_empty) + r_wlen <= raw_wfifo_wlen + 1; + else if (awskd_valid && awskd_ready) + r_wlen <= next_wlen + 1; + end + + initial r_wlast = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + r_wlast <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (r_wlen > 0) + r_wlast <= (r_wlen <= 1); + else + r_wlast <= raw_wfifo_last; + end + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + begin + axi_wid <= 0; + axi_wlast <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + axi_wid <= wfifo_id; + axi_wlast <= r_wlast; + + if (OPT_LOWPOWER && !wskd_valid) + begin + axi_wid <= 0; + axi_wlast <= 0; + end + end + + assign M_AXI_WVALID = axi_wvalid; + assign M_AXI_WDATA = axi_wdata; + assign M_AXI_WSTRB = axi_wstrb; + assign M_AXI_WID = axi_wid; + assign M_AXI_WLAST = axi_wlast; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write return channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + wire bskd_valid; + reg bskd_ready; + wire [C_AXI_ID_WIDTH-1:0] bskd_id; + wire [1:0] bskd_resp; + reg [1:0] bburst_err [0:NID-1]; + reg [NID-1:0] wbfifo_valid; + reg [NID-1:0] wbfifo_last; + reg axi_bvalid; + reg [C_AXI_ID_WIDTH-1:0] axi_bid; + reg [1:0] axi_bresp; + + skidbuffer #(.DW(C_AXI_ID_WIDTH + 2), .OPT_OUTREG(1'b0) + ) bskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(M_AXI_BVALID), .o_ready(M_AXI_BREADY), + .i_data({ M_AXI_BID, M_AXI_BRESP }), + .o_valid(bskd_valid), .i_ready(bskd_ready), + .o_data({ bskd_id, bskd_resp }) + ); + + generate for(ID=0; ID < NID; ID = ID + 1) + begin : WRITE_BURST_TRACKING + wire wbfifo_empty, wbfifo_full; + wire [LGFIFO:0] wbfifo_fill; + + initial wbfifo_valid[ID] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + wbfifo_valid[ID] <= 0; + else if (!wbfifo_valid[ID] && !wbfifo_empty) + wbfifo_valid[ID] <= 1; + else if (bskd_valid && bskd_ready && bskd_id==ID) + wbfifo_valid[ID] <= !wbfifo_empty; + + sfifo #(.BW(1), .LGFLEN(LGFIFO)) + wbfifo(S_AXI_ACLK, !S_AXI_ARESETN, + (M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST) + && (M_AXI_WID == ID), + (r_wlen == 0) ? 1'b1 : 1'b0, + wbfifo_full, wbfifo_fill, + // + (!wbfifo_valid[ID] || (M_AXI_BVALID && M_AXI_BREADY + && M_AXI_BID == ID)), + wbfifo_last[ID], wbfifo_empty + ); + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, wbfifo_full, wbfifo_fill }; + // Verilator lint_on UNUSED + + end endgenerate + + always @(*) + bskd_ready = (!S_AXI_BVALID || S_AXI_BREADY); + + initial axi_bvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_bvalid <= 1'b0; + else if (bskd_valid && bskd_ready) + axi_bvalid <= wbfifo_last[bskd_id]; + else if (M_AXI_BREADY) + axi_bvalid <= 1'b0; + + generate for(ID=0; ID < NID; ID = ID + 1) + begin : WRITE_ERR_BY_ID + + initial bburst_err[ID] = 2'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bburst_err[ID] <= 2'b0; + else if (S_AXI_BVALID && S_AXI_BREADY && S_AXI_BID == ID + && wbfifo_last[ID]) + bburst_err[ID] <= 2'b0; + else if (bskd_valid && bskd_id == ID) + bburst_err[ID] <= bburst_err[ID] | bskd_resp; + + end endgenerate + + initial axi_bid = 0; + initial axi_bresp = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_bid <= 0; + axi_bresp <= 0; + end else if (!S_AXI_BVALID || S_AXI_BREADY) + begin + axi_bid <= bskd_id; + axi_bresp <= bskd_resp | bburst_err[bskd_id]; + + if (OPT_LOWPOWER && !bskd_valid) + begin + axi_bid <= 0; + axi_bresp <= 0; + end + end + + assign S_AXI_BVALID = axi_bvalid; + assign S_AXI_BID = axi_bid; + assign S_AXI_BRESP = axi_bresp; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // READ SIDE + // {{{ + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////// + // + // Read address channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [7:0] r_arlen; + wire arskd_valid; + reg arskd_ready; + wire [C_AXI_ID_WIDTH-1:0] arskd_id; + wire [C_AXI_ADDR_WIDTH-1:0] arskd_addr; + wire [7:0] arskd_len; + wire [2:0] arskd_size; + wire [1:0] arskd_burst; + wire arskd_lock; + wire [3:0] arskd_cache; + wire [2:0] arskd_prot; + wire [3:0] arskd_qos; + reg axi_arvalid; + reg [C_AXI_ID_WIDTH-1:0] axi_arid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [3:0] axi_arlen; + reg [2:0] axi_arsize; + reg [1:0] axi_arburst; + reg [1:0] axi_arlock; + reg [3:0] axi_arcache; + reg [2:0] axi_arprot; + reg [3:0] axi_arqos; + + skidbuffer #( + .DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + 8 + 3 + 2 + 1+4+3+4), + .OPT_OUTREG(1'b0) + ) arskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data({ S_AXI_ARID, S_AXI_ARADDR, S_AXI_ARLEN, + S_AXI_ARSIZE, S_AXI_ARBURST, S_AXI_ARLOCK, + S_AXI_ARCACHE, S_AXI_ARPROT, S_AXI_ARQOS }), + .o_valid(arskd_valid), .i_ready(arskd_ready), + .o_data({ arskd_id, arskd_addr, arskd_len, + arskd_size, arskd_burst, arskd_lock, + arskd_cache, arskd_prot, arskd_qos }) + ); + + always @(*) + begin + arskd_ready = 1; + if (r_arlen > 0) + arskd_ready = 0; + if (M_AXI_ARVALID && M_AXI_ARREADY) + arskd_ready = 0; + end + + initial axi_arvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arvalid <= 1'b0; + else if (r_arlen > 0) + axi_arvalid <= 1'b1; + else if (arskd_ready) + axi_arvalid <= 1'b1; + else if (M_AXI_ARREADY) + axi_arvalid <= 1'b0; + + initial r_arlen = 0; + initial M_AXI_ARLEN = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_arlen <= 0; + axi_arlen <= 0; + end else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + if (r_arlen[7:4] != 0) + begin + axi_arlen <= 4'd15; + r_arlen <= r_arlen - 16; + end else if (r_arlen[3:0] != 0) + begin + axi_arlen <= r_arlen[3:0] - 1; + r_arlen <= 0; + end else if (arskd_valid) + begin + if (arskd_len[7:4] != 0) + begin + r_arlen <= arskd_len + 1 - 16; + axi_arlen <= 4'd15; + end else begin + r_arlen <= 0; + axi_arlen <= arskd_len[3:0]; + end + end else begin + r_arlen <= 0; + axi_arlen <= 0; + end + end + + + always @(posedge S_AXI_ACLK) + if (arskd_valid && arskd_ready) + axi_araddr <= arskd_addr; + else if (M_AXI_ARVALID && M_AXI_ARREADY) + begin + // Verilator lint_off WIDTH + axi_araddr <= axi_araddr + ((M_AXI_ARLEN + 1) << M_AXI_ARSIZE); + // Verilator lint_on WIDTH + + case(M_AXI_AWSIZE) + 3'b000: begin end + 3'b001: axi_araddr[ 0] <= 0; + 3'b010: axi_araddr[1:0] <= 0; + 3'b011: axi_araddr[2:0] <= 0; + 3'b100: axi_araddr[3:0] <= 0; + 3'b101: axi_araddr[4:0] <= 0; + 3'b110: axi_araddr[5:0] <= 0; + 3'b111: axi_araddr[6:0] <= 0; + endcase + end + + initial axi_arsize = ADDRLSB[2:0]; + always @(posedge S_AXI_ACLK) + begin + if (arskd_valid && arskd_ready) + begin + axi_arid <= arskd_id; + axi_arsize <= arskd_size; + axi_arburst <= arskd_burst; + axi_arlock[0] <= arskd_lock; + axi_arcache <= arskd_cache; + axi_arprot <= arskd_prot; + axi_arqos <= arskd_qos; + end + + // Propagate constants, to help the optimizer out out a touch + axi_arlock[1] <= 1'b0; + + if (C_AXI_DATA_WIDTH < 16) + axi_arsize <= 3'b000; + else if (C_AXI_DATA_WIDTH < 32) + axi_arsize[2:1] <= 2'b00; + else if (C_AXI_DATA_WIDTH < 128) + axi_arsize[2] <= 1'b0; + end + + generate for(ID=0; ID < NID; ID = ID + 1) + begin : READ_BURST_TRACKING + wire rbfifo_empty, rbfifo_full; + wire [LGFIFO:0] rbfifo_fill; + + initial rbfifo_valid[ID] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rbfifo_valid[ID] <= 0; + else if (!rbfifo_valid[ID] && !rbfifo_empty) + rbfifo_valid[ID] <= 1; + else if (rskd_valid && rskd_ready && rskd_last && rskd_id==ID) + rbfifo_valid[ID] <= !rbfifo_empty; + + sfifo #(.BW(1), .LGFLEN(LGFIFO)) + rbfifo(S_AXI_ACLK, !S_AXI_ARESETN, + (!M_AXI_ARVALID || M_AXI_ARREADY) + && (((r_arlen>0)&&(M_AXI_ARID==ID)) + || (arskd_valid && arskd_id == ID)), + ((arskd_valid && arskd_ready && arskd_len <= 15) + ||(r_arlen <= 15)) ? 1'b1 : 1'b0, + rbfifo_full, rbfifo_fill, + // + (!rbfifo_valid[ID] || (M_AXI_RVALID && M_AXI_RREADY + && M_AXI_RID == ID && M_AXI_RLAST)), + rid_last[ID], rbfifo_empty + ); + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, rbfifo_full, rbfifo_fill }; + // Verilator lint_on UNUSED + end endgenerate + + assign M_AXI_ARVALID= axi_arvalid; + assign M_AXI_ARID = axi_arid; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARLEN = axi_arlen; + assign M_AXI_ARSIZE = axi_arsize; + assign M_AXI_ARBURST= axi_arburst; + assign M_AXI_ARLOCK = axi_arlock; + assign M_AXI_ARCACHE= axi_arcache; + assign M_AXI_ARPROT = axi_arprot; + assign M_AXI_ARQOS = axi_arqos; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read data channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + wire rskd_valid; + reg rskd_ready; + wire [C_AXI_ID_WIDTH-1:0] rskd_id; + wire [C_AXI_DATA_WIDTH-1:0] rskd_data; + wire rskd_last; + wire [1:0] rskd_resp; + reg [1:0] rburst_err [0:NID-1]; + reg [NID-1:0] rbfifo_valid; + reg [NID-1:0] rid_last; + reg axi_rvalid; + reg [C_AXI_ID_WIDTH-1:0] axi_rid; + reg [C_AXI_DATA_WIDTH-1:0] axi_rdata; + reg axi_rlast; + reg [1:0] axi_rresp; + + skidbuffer #( + .DW(C_AXI_DATA_WIDTH + C_AXI_ID_WIDTH + 3), + .OPT_OUTREG(1'b0) + ) rskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(M_AXI_RVALID), .o_ready(M_AXI_RREADY), + .i_data({ M_AXI_RID, M_AXI_RDATA, M_AXI_RLAST, + M_AXI_RRESP }), + .o_valid(rskd_valid), .i_ready(rskd_ready), + .o_data({ rskd_id, rskd_data, rskd_last, rskd_resp }) + ); + + always @(*) + rskd_ready = !S_AXI_RVALID || S_AXI_RREADY; + + initial axi_rvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_rvalid <= 1'b0; + else if (rskd_valid && rskd_ready) + axi_rvalid <= 1'b1; + else if (S_AXI_RREADY) + axi_rvalid <= 1'b0; + + generate for(ID=0; ID < NID; ID = ID + 1) + begin : READ_ERR_BY_ID + + initial rburst_err[ID] = 2'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rburst_err[ID] <= 2'b0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST + && S_AXI_RID == ID) + rburst_err[ID] <= 2'b0; + else if (rskd_valid && rskd_id == ID) + rburst_err[ID] <= rburst_err[ID] | rskd_resp; + + end endgenerate + + always @(posedge S_AXI_ACLK) + if (!S_AXI_RVALID || S_AXI_RREADY) + begin + axi_rid <= rskd_id; + axi_rdata <= rskd_data; + axi_rresp <= rskd_resp | rburst_err[rskd_id]; + axi_rlast <= rid_last[rskd_id] && rskd_last; + end + + assign S_AXI_RVALID= axi_rvalid; + assign S_AXI_RID = axi_rid; + assign S_AXI_RDATA = axi_rdata; + assign S_AXI_RRESP = axi_rresp; + assign S_AXI_RLAST = axi_rlast; + // }}} + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, wfifo_fill[LGWFIFO-2:0], wskd_last, + wfifo_wlen, wfifo_full }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL +// +// This design has not been formally verified. +// +`endif +endmodule diff --git a/rtl/wb2axip/axi2axilite.v b/rtl/wb2axip/axi2axilite.v new file mode 100644 index 0000000..4b30aec --- /dev/null +++ b/rtl/wb2axip/axi2axilite.v @@ -0,0 +1,1173 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi2axilite.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Convert from AXI to AXI-lite with no performance loss. +// +// Performance: The goal of this converter is to convert from AXI to AXI-lite +// while still maintaining the one-clock per transaction speed +// of AXI. It currently achieves this goal. The design needs very little +// configuration to be useful, but you might wish to resize the FIFOs +// within depending upon the length of your slave's data path. The current +// FIFO length, LGFIFO=4, is sufficient to maintain full speed. If the +// slave, however, can maintain full speed but requires a longer +// processing cycle, then you may need longer FIFOs. +// +// The AXI specification does require an additional 2 clocks per +// transaction when using this core, so your latency will go up. +// +// Related: There's a related axidouble.v core in the same repository as +// well. That can be used to convert the AXI protocol to something +// simpler (even simpler than AXI-lite), but it can also do so for multiple +// downstream slaves at the same time. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axi2axilite #( + // {{{ + parameter integer C_AXI_ID_WIDTH = 2, + parameter integer C_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 6, + parameter [0:0] OPT_WRITES = 1, + parameter [0:0] OPT_READS = 1, + parameter [0:0] OPT_LOWPOWER = 0, + // Log (based two) of the maximum number of outstanding AXI + // (not AXI-lite) transactions. If you multiply 2^LGFIFO * 256, + // you'll get the maximum number of outstanding AXI-lite + // transactions + parameter LGFIFO = 4 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI4 slave interface + // {{{ + // Write address channel + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [7:0] S_AXI_AWLEN, + input wire [2:0] S_AXI_AWSIZE, + input wire [1:0] S_AXI_AWBURST, + input wire S_AXI_AWLOCK, + input wire [3:0] S_AXI_AWCACHE, + input wire [2:0] S_AXI_AWPROT, + input wire [3:0] S_AXI_AWQOS, + // }}} + // Write data channel + // {{{ + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [(C_AXI_DATA_WIDTH/8)-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // }}} + // Write return channel + // {{{ + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // }}} + // Read address channel + // {{{ + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [7:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + // }}} + // Read data channel + // {{{ + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP, + output wire S_AXI_RLAST, + // }}} + // }}} + // AXI-lite master interface + // {{{ + // AXI-lite Write interface + // {{{ + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [2 : 0] M_AXI_AWPROT, + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [(C_AXI_DATA_WIDTH/8)-1:0] M_AXI_WSTRB, + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + input wire [1 : 0] M_AXI_BRESP, + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + // }}} + // AXI-lite read interface + // {{{ + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [2:0] M_AXI_ARPROT, + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA, + input wire [1 : 0] M_AXI_RRESP + // }}} + // }}} + // }}} + ); + + // Local parameters, register, and net declarations + // {{{ + localparam [1:0] SLVERR = 2'b10; + // localparam [1:0] OKAY = 2'b00, + // EXOKAY = 2'b01, + // DECERR = 2'b10; + localparam AW = C_AXI_ADDR_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + localparam IW = C_AXI_ID_WIDTH; + // }}} + // Register declarations + // {{{ + // + // Write registers + reg m_axi_awvalid, s_axi_wready; + reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; + reg [7:0] axi_awlen, axi_blen; + reg [1:0] axi_awburst; + reg [2:0] axi_awsize; + wire [C_AXI_ADDR_WIDTH-1:0] next_write_addr; + wire [4:0] wfifo_count; + wire wfifo_full; + wire wfifo_empty; + wire [7:0] wfifo_bcount; + wire [IW-1:0] wfifo_bid; + reg [8:0] bcounts; + reg [C_AXI_ID_WIDTH-1:0] axi_bid, bid; + reg [1:0] axi_bresp; + reg s_axi_bvalid; + wire read_from_wrfifo; + // + // Read register + reg m_axi_arvalid; + wire [4:0] rfifo_count; + wire rfifo_full; + wire rfifo_empty; + wire [7:0] rfifo_rcount; + reg s_axi_rvalid; + reg [1:0] s_axi_rresp; + reg [8:0] rcounts; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [7:0] axi_arlen, axi_rlen; + reg [1:0] axi_arburst; + reg [2:0] axi_arsize; + wire [C_AXI_ADDR_WIDTH-1:0] next_read_addr; + reg [C_AXI_ID_WIDTH-1:0] s_axi_rid; + wire [C_AXI_ID_WIDTH-1:0] rfifo_rid; + reg [C_AXI_DATA_WIDTH-1:0] s_axi_rdata; + reg s_axi_rlast; + reg [IW-1:0] rid; + wire read_from_rdfifo; + + // + // S_AXI_AW* skid buffer + wire skids_awvalid, skids_awready; + wire [IW-1:0] skids_awid; + wire [AW-1:0] skids_awaddr; + wire [7:0] skids_awlen; + wire [2:0] skids_awsize; + wire [1:0] skids_awburst; + // + // S_AXI_W* skid buffer + wire skids_wvalid, skids_wready, skids_wlast; + wire [DW-1:0] skids_wdata; + wire [DW/8-1:0] skids_wstrb; + // + // S_AXI_B* skid buffer isn't needed + // + // M_AXI_AW* skid buffer isn't needed + // + // M_AXI_W* skid buffer + wire skidm_wvalid, skidm_wready; + wire [DW-1:0] skidm_wdata; + wire [DW/8-1:0] skidm_wstrb; + // + // M_AXI_B* skid buffer + wire skidm_bvalid, skidm_bready; + wire [1:0] skidm_bresp; + // + // + // + // S_AXI_AR* skid buffer + wire skids_arvalid, skids_arready; + wire [IW-1:0] skids_arid; + wire [AW-1:0] skids_araddr; + wire [7:0] skids_arlen; + wire [2:0] skids_arsize; + wire [1:0] skids_arburst; + // + // S_AXI_R* skid buffer isn't needed + // + // M_AXI_AR* skid buffer isn't needed + // M_AXI_R* skid buffer + wire skidm_rvalid, skidm_rready; + wire [DW-1:0] skidm_rdata; + wire [1:0] skidm_rresp; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_WRITES) + begin : IMPLEMENT_WRITES + // {{{ + // The write address channel's skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2), .OPT_LOWPOWER(0), .OPT_OUTREG(0) + // }}} + ) awskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_AWVALID, S_AXI_AWREADY, + { S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, S_AXI_AWSIZE, + S_AXI_AWBURST }, + skids_awvalid, skids_awready, + { skids_awid, skids_awaddr, skids_awlen, skids_awsize, + skids_awburst } + // }}} + ); + // }}} + // + // The write data channel's skid buffer (S_AXI_W*) + // {{{ + skidbuffer #( + // {{{ + .DW(DW+DW/8+1), .OPT_LOWPOWER(0), .OPT_OUTREG(0) + // }}} + ) wskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_WVALID, S_AXI_WREADY, + { S_AXI_WDATA, S_AXI_WSTRB, S_AXI_WLAST }, + skids_wvalid, skids_wready, + { skids_wdata, skids_wstrb, skids_wlast } + // }}} + ); + // }}} + // + // The downstream AXI-lite write data (M_AXI_W*) skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(DW+DW/8), .OPT_LOWPOWER(0), .OPT_OUTREG(1) + // }}} + ) mwskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + skidm_wvalid, skidm_wready, { skidm_wdata, skidm_wstrb }, + M_AXI_WVALID,M_AXI_WREADY,{ M_AXI_WDATA, M_AXI_WSTRB } + // }}} + ); + // }}} + // + // The downstream AXI-lite response (M_AXI_B*) skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(2), .OPT_LOWPOWER(0), .OPT_OUTREG(0) + // }}} + ) bskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + M_AXI_BVALID, M_AXI_BREADY, { M_AXI_BRESP }, + skidm_bvalid, skidm_bready, { skidm_bresp } + // }}} + ); + // }}} + + // m_axi_awvalid + // {{{ + initial m_axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_awvalid <= 0; + else if (skids_awvalid & skids_awready) + m_axi_awvalid <= 1; + else if (M_AXI_AWREADY && axi_awlen == 0) + m_axi_awvalid <= 0; + + assign M_AXI_AWVALID = m_axi_awvalid; + // }}} + + // skids_awready + // {{{ + assign skids_awready = (!M_AXI_AWVALID + || ((axi_awlen == 0)&&M_AXI_AWREADY)) + && !wfifo_full + &&(!s_axi_wready || (skids_wvalid && skids_wlast && skids_wready)); + // }}} + + // Address processing + // {{{ + always @(posedge S_AXI_ACLK) + if (skids_awvalid && skids_awready) + begin + axi_awaddr <= skids_awaddr; + axi_blen <= skids_awlen; + axi_awburst<= skids_awburst; + axi_awsize <= skids_awsize; + end else if (M_AXI_AWVALID && M_AXI_AWREADY) + axi_awaddr <= next_write_addr; + // }}} + + // axi_awlen + // {{{ + initial axi_awlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_awlen <= 0; + else if (skids_awvalid && skids_awready) + axi_awlen <= skids_awlen; + else if (M_AXI_AWVALID && M_AXI_AWREADY && axi_awlen > 0) + axi_awlen <= axi_awlen - 1; + // }}} + + // axi_addr + // {{{ + axi_addr #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) + // }}} + ) calcwraddr( + // {{{ + axi_awaddr, axi_awsize, axi_awburst, + axi_blen, next_write_addr + // }}} + ); + // }}} + + // s_axi_wready + // {{{ + // We really don't need to do anything special to the write + // channel. + initial s_axi_wready = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_wready <= 0; + else if (skids_awvalid && skids_awready) + s_axi_wready <= 1; + else if (skids_wvalid && skids_wready && skids_wlast) + s_axi_wready <= 0; + // }}} + + // skidm*, and read_from_wrfifo + // {{{ + assign skidm_wdata = skids_wdata; + assign skidm_wstrb = skids_wstrb; + assign skidm_wvalid = skids_wvalid && s_axi_wready; + assign skids_wready = s_axi_wready && skidm_wready; + + assign read_from_wrfifo = (bcounts <= 1)&&(!wfifo_empty) + &&(skidm_bvalid && skidm_bready); + // }}} + + // BFIFO + // {{{ + sfifo #( + .BW(C_AXI_ID_WIDTH+8), .LGFLEN(LGFIFO) + ) bidlnfifo( + S_AXI_ACLK, !S_AXI_ARESETN, + skids_awvalid && skids_awready, + { skids_awid, skids_awlen }, + wfifo_full, wfifo_count, + read_from_wrfifo, + { wfifo_bid, wfifo_bcount }, wfifo_empty); + // }}} + + // bcounts + // {{{ + // Return counts + initial bcounts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bcounts <= 0; + else if (read_from_wrfifo) + begin + bcounts <= wfifo_bcount + bcounts; + end else if (skidm_bvalid && skidm_bready) + bcounts <= bcounts - 1; + // }}} + + // bid + // {{{ + always @(posedge S_AXI_ACLK) + if (read_from_wrfifo) + bid <= wfifo_bid; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_BVALID || S_AXI_BREADY) + axi_bid <= (read_from_wrfifo && bcounts==0) ? wfifo_bid : bid; + // }}} + + // s_axi_bvalid + // {{{ + initial s_axi_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_bvalid <= 0; + else if (skidm_bvalid && skidm_bready) + s_axi_bvalid <= (bcounts == 1) + ||((bcounts == 0) && (!wfifo_empty) && (wfifo_bcount == 0)); + else if (S_AXI_BREADY) + s_axi_bvalid <= 0; + // }}} + + // axi_bresp + // {{{ + initial axi_bresp = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_bresp <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY) + begin + if (skidm_bvalid && skidm_bready) + axi_bresp <= skidm_bresp; + else + axi_bresp <= 0; + end else if (skidm_bvalid && skidm_bready) + begin + // Let SLVERR take priority over DECERR + casez({ S_AXI_BRESP, skidm_bresp }) + 4'b??0?: axi_bresp <= S_AXI_BRESP; + 4'b0?1?: axi_bresp <= skidm_bresp; + 4'b1?10: axi_bresp <= SLVERR; + 4'b1011: axi_bresp <= SLVERR; + 4'b1111: axi_bresp <= skidm_bresp; + endcase + end + // /}}} + + // M_AXI_AW* + // {{{ + assign M_AXI_AWVALID= m_axi_awvalid; + assign M_AXI_AWADDR = axi_awaddr; + assign M_AXI_AWPROT = 0; + // }}} + + // skidm_bready, S_AXI_B* + // {{{ + assign skidm_bready = ((bcounts > 0)||(!wfifo_empty))&&(!S_AXI_BVALID | S_AXI_BREADY); + assign S_AXI_BID = axi_bid; + assign S_AXI_BRESP = axi_bresp; + assign S_AXI_BVALID = s_axi_bvalid; + // }}} + // }}} + end else begin : NO_WRITE_SUPPORT + // {{{ + assign S_AXI_AWREADY = 0; + assign S_AXI_WREADY = 0; + assign S_AXI_BID = 0; + assign S_AXI_BRESP = 2'b11; + assign S_AXI_BVALID = 0; + assign S_AXI_BID = 0; + + // + assign M_AXI_AWVALID = 0; + assign M_AXI_AWADDR = 0; + assign M_AXI_AWPROT = 0; + // + assign M_AXI_WVALID = 0; + assign M_AXI_WDATA = 0; + assign M_AXI_WSTRB = 0; + // + assign M_AXI_BREADY = 0; + + // + // S_AXI_AW* skid buffer + assign skids_awvalid = 0; + assign skids_awready = 0; + assign skids_awid = 0; + assign skids_awaddr = 0; + assign skids_awlen = 0; + assign skids_awsize = 0; + assign skids_awburst = 0; + // + // S_AXI_W* skid buffer + assign skids_wvalid = S_AXI_WVALID; + assign skids_wready = S_AXI_WREADY; + assign skids_wdata = S_AXI_WDATA; + assign skids_wstrb = S_AXI_WSTRB; + assign skids_wlast = S_AXI_WLAST; + // + // S_AXI_B* skid buffer isn't needed + // + // M_AXI_AW* skid buffer isn't needed + // + // M_AXI_W* skid buffer + assign skidm_wvalid = M_AXI_WVALID; + assign skidm_wready = M_AXI_WREADY; + assign skidm_wdata = M_AXI_WDATA; + assign skidm_wstrb = M_AXI_WSTRB; + // + // M_AXI_B* skid buffer + assign skidm_bvalid = M_AXI_BVALID; + assign skidm_bready = M_AXI_BREADY; + assign skidm_bresp = M_AXI_BRESP; + // + // + always @(*) + begin + s_axi_wready = 0; + + axi_awlen = 0; + bcounts = 0; + bid = 0; + axi_bresp = 0; + axi_bid = 0; + + end + + assign wfifo_full = 0; + assign wfifo_empty = 1; + assign wfifo_count = 0; + assign read_from_wrfifo = 0; + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_write_signals; + assign unused_write_signals = &{ 1'b0, M_AXI_AWREADY, + M_AXI_WREADY, S_AXI_BREADY }; + // Verilator lint_on UNUSED + // }}} + + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_READS) + begin : IMPLEMENT_READS + // {{{ + // S_AXI_AR* skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2), .OPT_LOWPOWER(0), .OPT_OUTREG(0) + // }}} + ) arskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_ARVALID, S_AXI_ARREADY, + { S_AXI_ARID, S_AXI_ARADDR, S_AXI_ARLEN, S_AXI_ARSIZE, + S_AXI_ARBURST }, + skids_arvalid, skids_arready, + { skids_arid, skids_araddr, skids_arlen, skids_arsize, + skids_arburst } + // }}} + ); + // }}} + // M_AXI_R* skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(DW+2), .OPT_LOWPOWER(0), .OPT_OUTREG(0) + // }}} + ) rskid( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + M_AXI_RVALID, M_AXI_RREADY,{ M_AXI_RDATA, M_AXI_RRESP }, + skidm_rvalid,skidm_rready,{ skidm_rdata, skidm_rresp } + // }}} + ); + // }}} + // m_axi_arvalid + // {{{ + initial m_axi_arvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_arvalid <= 0; + else if (skids_arvalid && skids_arready) + m_axi_arvalid <= 1; + else if (M_AXI_ARREADY && axi_arlen == 0) + m_axi_arvalid <= 0; + // }}} + + // Read address processing + // {{{ + always @(posedge S_AXI_ACLK) + if (skids_arvalid && skids_arready) + begin + axi_araddr <= skids_araddr; + axi_arburst <= skids_arburst; + axi_arsize <= skids_arsize; + axi_rlen <= skids_arlen; + end else if (M_AXI_ARREADY) + begin + axi_araddr <= next_read_addr; + if (OPT_LOWPOWER && axi_arlen == 0) + axi_araddr <= 0; + end + + axi_addr #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) + // }}} + ) calcrdaddr( + // {{{ + axi_araddr, axi_arsize, axi_arburst, + axi_rlen, next_read_addr + // }}} + ); + // }}} + + // axi_arlen, Read length processing + // {{{ + initial axi_arlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arlen <= 0; + else if (skids_arvalid && skids_arready) + axi_arlen <= skids_arlen; + else if (M_AXI_ARVALID && M_AXI_ARREADY && axi_arlen > 0) + axi_arlen <= axi_arlen - 1; + // }}} + + assign skids_arready = (!M_AXI_ARVALID || + ((axi_arlen == 0) && M_AXI_ARREADY)) + && !rfifo_full; + + assign read_from_rdfifo = skidm_rvalid && skidm_rready + && (rcounts <= 1) && !rfifo_empty; + + // Read ID FIFO + // {{{ + sfifo #( + // {{{ + .BW(C_AXI_ID_WIDTH+8), .LGFLEN(LGFIFO) + // }}} + ) ridlnfifo( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, + skids_arvalid && skids_arready, + { skids_arid, skids_arlen }, + rfifo_full, rfifo_count, + read_from_rdfifo, + { rfifo_rid, rfifo_rcount }, rfifo_empty + // }}} + ); + // }}} + + assign skidm_rready = (!S_AXI_RVALID || S_AXI_RREADY); + + // s_axi_rvalid + // {{{ + initial s_axi_rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_rvalid <= 0; + else if (skidm_rvalid && skidm_rready) + s_axi_rvalid <= 1; + else if (S_AXI_RREADY) + s_axi_rvalid <= 0; + // }}} + + // s_axi_rresp, s_axi_rdata + // {{{ + always @(posedge S_AXI_ACLK) + if (skidm_rvalid && skidm_rready) + begin + s_axi_rresp <= skidm_rresp; + s_axi_rdata <= skidm_rdata; + end else if (S_AXI_RREADY) + begin + s_axi_rresp <= 0; + s_axi_rdata <= 0; + end + // }}} + + // rcounts, Return counts + // {{{ + initial rcounts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rcounts <= 0; + else if (read_from_rdfifo) + rcounts <= rfifo_rcount + rcounts; + else if (skidm_rvalid && skidm_rready) + rcounts <= rcounts - 1; + // }}} + + // rid + // {{{ + initial rid = 0; + always @(posedge S_AXI_ACLK) + if (read_from_rdfifo) + rid <= rfifo_rid; + // }}} + + // s_axi_rlast + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_RVALID || S_AXI_RREADY) + begin + // if (rcounts == 1) s_axi_rlast <= 1; else + if (read_from_rdfifo) + s_axi_rlast <= (rfifo_rcount == 0); + else + s_axi_rlast <= 0; + + if (rcounts == 1) + s_axi_rlast <= 1; + end + // }}} + + // s_axi_rid + // {{{ + initial s_axi_rid = 0; + always @(posedge S_AXI_ACLK) + if ((S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + ||(!S_AXI_RVALID && rcounts == 0)) + s_axi_rid <= (read_from_rdfifo)&&(rcounts == 0)?rfifo_rid : rid; + // }}} + + // M_AXI_AR* + // {{{ + assign M_AXI_ARVALID= m_axi_arvalid; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARPROT = 0; + // }}} + // S_AXI_R* + // {{{ + assign S_AXI_RVALID = s_axi_rvalid; + assign S_AXI_RDATA = s_axi_rdata; + assign S_AXI_RRESP = s_axi_rresp; + assign S_AXI_RLAST = s_axi_rlast; + assign S_AXI_RID = s_axi_rid; + // }}} + // }}} + end else begin : NO_READ_SUPPORT // if (!OPT_READS) + // {{{ + assign M_AXI_ARVALID= 0; + assign M_AXI_ARADDR = 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_RREADY = 0; + // + assign S_AXI_ARREADY= 0; + assign S_AXI_RVALID = 0; + assign S_AXI_RDATA = 0; + assign S_AXI_RRESP = 0; + assign S_AXI_RLAST = 0; + assign S_AXI_RID = 0; + + // + assign skids_arvalid = S_AXI_ARVALID; + assign skids_arready = S_AXI_ARREADY; + assign skids_arid = S_AXI_ARID; + assign skids_araddr = S_AXI_ARADDR; + assign skids_arlen = S_AXI_ARLEN; + assign skids_arsize = S_AXI_ARSIZE; + assign skids_arburst = S_AXI_ARBURST; + // + assign skidm_rvalid = M_AXI_RVALID; + assign skidm_rready = M_AXI_RREADY; + assign skidm_rdata = M_AXI_RDATA; + assign skidm_rresp = M_AXI_RRESP; + // + // + always @(*) + begin + axi_arlen = 0; + + rcounts = 0; + rid = 0; + + end + assign rfifo_empty = 1; + assign rfifo_full = 0; + assign rfifo_count = 0; + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_read_signals; + assign unused_read_signals = &{ 1'b0, M_AXI_ARREADY, + S_AXI_RREADY }; + // Verilator lint_on UNUSED + // }}} + + // }}} + end endgenerate + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire [35-1:0] unused; + assign unused = { + S_AXI_AWLOCK, S_AXI_AWCACHE, S_AXI_AWPROT, S_AXI_AWQOS, + skids_wlast, wfifo_count, + S_AXI_ARLOCK, S_AXI_ARCACHE, S_AXI_ARPROT, S_AXI_ARQOS, + rfifo_count }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +// The following are a subset of the formal properties used to verify this core +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = LGFIFO+1+8; + + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // AXI channel properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxi_slave #(.C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(DW), + .C_AXI_ADDR_WIDTH(AW), + // + ) + faxi(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address + .i_axi_awready(skids_awready), + .i_axi_awid( skids_awid), + .i_axi_awaddr( skids_awaddr), + .i_axi_awlen( skids_awlen), + .i_axi_awsize( skids_awsize), + .i_axi_awburst(skids_awburst), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( 0), + .i_axi_awqos( 0), + .i_axi_awvalid(skids_awvalid), + // Write data + .i_axi_wready( skids_wready), + .i_axi_wdata( skids_wdata), + .i_axi_wstrb( skids_wstrb), + .i_axi_wlast( skids_wlast), + .i_axi_wvalid( skids_wvalid), + // Write return response + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + .i_axi_bvalid( S_AXI_BVALID), + .i_axi_bready( S_AXI_BREADY), + // Read address + .i_axi_arready(skids_arready), + .i_axi_arid( skids_arid), + .i_axi_araddr( skids_araddr), + .i_axi_arlen( skids_arlen), + .i_axi_arsize( skids_arsize), + .i_axi_arburst(skids_arburst), + .i_axi_arlock( 0), + .i_axi_arcache(0), + .i_axi_arprot( 0), + .i_axi_arqos( 0), + .i_axi_arvalid(skids_arvalid), + // Read response + .i_axi_rid( S_AXI_RID), + .i_axi_rresp( S_AXI_RRESP), + .i_axi_rvalid( S_AXI_RVALID), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rlast( S_AXI_RLAST), + .i_axi_rready( S_AXI_RREADY), + // + // Formal property data + .f_axi_awr_nbursts( faxi_awr_nbursts), + .f_axi_wr_pending( faxi_wr_pending), + .f_axi_rd_nbursts( faxi_rd_nbursts), + .f_axi_rd_outstanding(faxi_rd_outstanding), + // + // ... + ); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxil_master #(.C_AXI_DATA_WIDTH(DW), .C_AXI_ADDR_WIDTH(AW), + .F_OPT_NO_RESET(1), + .F_AXI_MAXWAIT(5), + .F_AXI_MAXDELAY(4), + .F_AXI_MAXRSTALL(0), + .F_OPT_WRITE_ONLY(OPT_WRITES && !OPT_READS), + .F_OPT_READ_ONLY(!OPT_WRITES && OPT_READS), + .F_LGDEPTH(F_AXIL_LGDEPTH)) + faxil(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address channel + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awprot( M_AXI_AWPROT), + // Write data + .i_axi_wready( skidm_wready), + .i_axi_wdata( skidm_wdata), + .i_axi_wstrb( skidm_wstrb), + .i_axi_wvalid( skidm_wvalid), + // Write response + .i_axi_bresp( skidm_bresp), + .i_axi_bvalid( skidm_bvalid), + .i_axi_bready( skidm_bready), + // Read address + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arprot( M_AXI_ARPROT), + // Read data return + .i_axi_rvalid( skidm_rvalid), + .i_axi_rready( skidm_rready), + .i_axi_rdata( skidm_rdata), + .i_axi_rresp( skidm_rresp), + // + // Formal check variables + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding)); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assume that the two write channels stay within an appropriate + // distance of each other. This is to make certain that the property + // file features are not violated, although not necessary true for + // actual operation + // + always @(*) + assert(s_axi_wready == (OPT_WRITES && faxi_wr_pending > 0)); + + //////////////////////////////////////////////////////////////////////// + // + // Write induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // These are extra properties necessary to pass write induction + // + + always @(*) + if ((bcounts == 0)&&(!read_from_wrfifo)) + assert(!skidm_bvalid || !skidm_bready); + + always @(*) + if (axi_awlen > 0) + begin + assert(m_axi_awvalid); + if (axi_awlen > 1) + begin + assert(!skids_awready); + end else if (wfifo_full) + begin + assert(!skids_awready); + end else if (M_AXI_AWVALID && !M_AXI_AWREADY) + assert(!skids_awready); + end + + + always @(*) + assert(axi_bresp != EXOKAY); + + reg [F_LGDEPTH-1:0] f_wfifo_bursts, f_wfifo_bursts_minus_one, + f_wfifo_within, + f_wfiid_bursts, f_wfiid_bursts_minus_one; + reg [IW-1:0] f_awid; + always @(posedge S_AXI_ACLK) + if (skids_awvalid && skids_awready) + f_awid = skids_awid; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + if (!S_AXI_RVALID && rcounts > 0) + assert(rid == S_AXI_RID); + + always @(*) + if (S_AXI_RVALID && !S_AXI_RLAST) + assert(rid == S_AXI_RID); + + always @(*) + if ((rcounts == 0)&&(!read_from_rdfifo)) + assert(!skidm_rvalid || !skidm_rready); + + always @(*) + if (axi_arlen > 0) + begin + assert(m_axi_arvalid); + assert(!skids_arready); + end + + // + // ... + // + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Select only write or only read operation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (!OPT_WRITES) + begin + always @(*) + begin + assume(!skids_awvalid); + assume(!skids_wvalid); + assert(M_AXI_AWVALID == 0); + assert(faxil_awr_outstanding == 0); + assert(faxil_wr_outstanding == 0); + assert(!skidm_bvalid); + assert(!S_AXI_BVALID); + end + end endgenerate + + generate if (!OPT_READS) + begin + + always @(*) + begin + assume(!S_AXI_ARVALID); + assert(M_AXI_ARVALID == 0); + assert(faxil_rd_outstanding == 0); + end + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover statements, to show performance + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_WRITES) + begin + // {{{ + reg [3:0] cvr_write_count, cvr_write_count_simple; + + initial cvr_write_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_write_count_simple <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN == 0) + cvr_write_count_simple <= cvr_write_count_simple + 1; + + initial cvr_write_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_write_count <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN > 2) + cvr_write_count <= cvr_write_count + 1; + + always @(*) + cover(cvr_write_count_simple > 6 && /* ... */ !S_AXI_BVALID); + always @(*) + cover(cvr_write_count > 2 && /* ... */ !S_AXI_BVALID); + // }}} + end endgenerate + + generate if (OPT_READS) + begin + // {{{ + reg [3:0] cvr_read_count, cvr_read_count_simple; + + initial cvr_read_count_simple = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_count_simple <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN == 0) + cvr_read_count_simple <= cvr_read_count_simple + 1; + + initial cvr_read_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_count <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN > 2) + cvr_read_count <= cvr_read_count + 1; + + always @(*) + cover(cvr_read_count_simple > 6 && /* ... */ !S_AXI_RVALID); + always @(*) + cover(cvr_read_count > 2 && /* ... */ !S_AXI_RVALID); + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // ... + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // }}} +`undef BMC_ASSERT +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axi2axilsub.v b/rtl/wb2axip/axi2axilsub.v new file mode 100644 index 0000000..694dc58 --- /dev/null +++ b/rtl/wb2axip/axi2axilsub.v @@ -0,0 +1,2157 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi2axilsub.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Convert from AXI to AXI-lite with no performance loss, and to +// a potentially smaller bus width. +// +// Performance: The goal of this converter is to convert from AXI to an AXI-lite +// bus of a smaller width, while still maintaining the one-clock +// per transaction speed of AXI. It currently achieves this goal. The +// design needs very little configuration to be useful, but you might +// wish to resize the FIFOs within depending upon the length of your +// slave's data path. The current FIFO length, LGFIFO=4, is sufficient to +// maintain full speed. If the slave, however, can maintain full speed but +// requires a longer processing cycle, then you may need longer FIFOs. +// +// The AXI specification does require an additional 2 clocks per +// transaction when using this core, so your latency will go up. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This digital logic component is the proprietary property of Gisselquist +// Technology, LLC. It may only be distributed and/or re-distributed by the +// express permission of Gisselquist Technology. +// +// Permission has been granted to the Patreon sponsors of the ZipCPU blog +// to use this logic component as they see fit, but not to redistribute it +// beyond their individual personal or commercial use. +// +// This component is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY +// or FITNESS FOR A PARTICULAR PURPOSE. +// +// Please feel free to contact me should you have any questions, or even if you +// just want to ask about what you find within here. +// +// Yours, +// +// Dan Gisselquist +// Owner +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// +`ifdef FORMAL +`ifdef BMC +`define BMC_ASSERT assert +`else +`define BMC_ASSERT assume +`endif +`endif +// +// }}} +module axi2axilsub #( + // {{{ + parameter integer C_AXI_ID_WIDTH = 2, + parameter integer C_S_AXI_DATA_WIDTH = 64, + parameter integer C_M_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 6, + parameter [0:0] OPT_LOWPOWER = 1, + parameter [0:0] OPT_WRITES = 1, + parameter [0:0] OPT_READS = 1, + parameter SLVSZ = $clog2(C_S_AXI_DATA_WIDTH/8), + parameter MSTSZ = $clog2(C_M_AXI_DATA_WIDTH/8), + // Log (based two) of the maximum number of outstanding AXI + // (not AXI-lite) transactions. If you multiply 2^LGFIFO * 256, + // you'll get the maximum number of outstanding AXI-lite + // transactions + parameter LGFIFO = 4 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI4 slave interface + // {{{ + // Write address channel + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [7:0] S_AXI_AWLEN, + input wire [2:0] S_AXI_AWSIZE, + input wire [1:0] S_AXI_AWBURST, + input wire S_AXI_AWLOCK, + input wire [3:0] S_AXI_AWCACHE, + input wire [2:0] S_AXI_AWPROT, + input wire [3:0] S_AXI_AWQOS, + // }}} + // Write data channel + // {{{ + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [(C_S_AXI_DATA_WIDTH/8)-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // }}} + // Write return channel + // {{{ + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // }}} + // Read address channel + // {{{ + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [7:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + // }}} + // Read data channel + // {{{ + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_S_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP, + output wire S_AXI_RLAST, + // }}} + // }}} + // AXI-lite master interface + // {{{ + // AXI-lite Write interface + // {{{ + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [2 : 0] M_AXI_AWPROT, + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_M_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [(C_M_AXI_DATA_WIDTH/8)-1:0] M_AXI_WSTRB, + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + input wire [1 : 0] M_AXI_BRESP, + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + // }}} + // AXI-lite read interface + // {{{ + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [2:0] M_AXI_ARPROT, + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_M_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA, + input wire [1 : 0] M_AXI_RRESP + // }}} + // }}} + // }}} + ); + + // Local parameters, register, and net declarations + // {{{ + // Verilator lint_off UNUSED + localparam [1:0] OKAY = 2'b00, + EXOKAY = 2'b01, + SLVERR = 2'b10; + // localparam [1:0] DECERR = 2'b10; + + // Verilator lint_on UNUSED + localparam AW = C_AXI_ADDR_WIDTH; + localparam SLVDW = C_S_AXI_DATA_WIDTH; + localparam MSTDW = C_M_AXI_DATA_WIDTH; + localparam IW = C_AXI_ID_WIDTH; + // localparam LSB = $clog2(C_AXI_DATA_WIDTH)-3; + // }}} + // Register declarations + // {{{ + // + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_WRITES && SLVDW == MSTDW) + begin : IMPLEMENT_AXI2AXILITE_WRITES + // {{{ + + // (Unused) signal declarations + // {{{ + // Verilator lint_off UNUSED + wire ign_arready, ign_rvalid, + ign_rlast, + ign_arvalid, ign_rready; + wire [IW-1:0] ign_rid; + wire [SLVDW-1:0] ign_rdata; + wire [1:0] ign_rresp; + wire [AW-1:0] ign_araddr; + wire [2:0] ign_arprot; + // Verilator lint_on UNUSED + // }}} + + axi2axilite #( + // {{{ + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(SLVDW), + .C_AXI_ADDR_WIDTH(AW), + .OPT_WRITES(1), + .OPT_READS(0), + .LGFIFO(LGFIFO) + // }}} + ) axilwrite ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + // AXI4 slave interface + // {{{ + // Write address channel + // {{{ + .S_AXI_AWVALID(S_AXI_AWVALID), + .S_AXI_AWREADY(S_AXI_AWREADY), + .S_AXI_AWID( S_AXI_AWID), + .S_AXI_AWADDR( S_AXI_AWADDR), + .S_AXI_AWLEN( S_AXI_AWLEN), + .S_AXI_AWSIZE( S_AXI_AWSIZE), + .S_AXI_AWBURST(S_AXI_AWBURST), + .S_AXI_AWLOCK( S_AXI_AWLOCK), + .S_AXI_AWCACHE(S_AXI_AWCACHE), + .S_AXI_AWPROT( S_AXI_AWPROT), + .S_AXI_AWQOS( S_AXI_AWQOS), + // }}} + // Write data channel + // {{{ + .S_AXI_WVALID(S_AXI_WVALID), + .S_AXI_WREADY(S_AXI_WREADY), + .S_AXI_WDATA( S_AXI_WDATA), + .S_AXI_WSTRB( S_AXI_WSTRB), + .S_AXI_WLAST( S_AXI_WLAST), + // }}} + // Write return channel + // {{{ + .S_AXI_BVALID(S_AXI_BVALID), + .S_AXI_BREADY(S_AXI_BREADY), + .S_AXI_BID( S_AXI_BID), + .S_AXI_BRESP( S_AXI_BRESP), + // }}} + // Read address channel + // {{{ + .S_AXI_ARVALID(1'b0), + .S_AXI_ARREADY(ign_arready), + .S_AXI_ARID( {(IW){1'b0}}), + .S_AXI_ARADDR( {(AW){1'b0}}), + .S_AXI_ARLEN( 8'h0), + .S_AXI_ARSIZE( 3'h0), + .S_AXI_ARBURST(2'h0), + .S_AXI_ARLOCK( 1'b0), + .S_AXI_ARCACHE(4'h0), + .S_AXI_ARPROT( 3'h0), + .S_AXI_ARQOS( 4'h0), + // }}} + // Read data channel + // {{{ + .S_AXI_RVALID(ign_rvalid), + .S_AXI_RREADY(1'b1), + .S_AXI_RID( ign_rid), + .S_AXI_RDATA( ign_rdata), + .S_AXI_RRESP( ign_rresp), + .S_AXI_RLAST( ign_rlast), + // }}} + // }}} + // AXI-lite master interface + // {{{ + // AXI-lite Write interface + // {{{ + .M_AXI_AWVALID(M_AXI_AWVALID), + .M_AXI_AWREADY(M_AXI_AWREADY), + .M_AXI_AWADDR(M_AXI_AWADDR), + .M_AXI_AWPROT(M_AXI_AWPROT), + // + .M_AXI_WVALID(M_AXI_WVALID), + .M_AXI_WREADY(M_AXI_WREADY), + .M_AXI_WDATA(M_AXI_WDATA), + .M_AXI_WSTRB(M_AXI_WSTRB), + // + .M_AXI_BVALID(M_AXI_BVALID), + .M_AXI_BREADY(M_AXI_BREADY), + .M_AXI_BRESP(M_AXI_BRESP), + // }}} + // AXI-lite read interface + // {{{ + .M_AXI_ARVALID(ign_arvalid), + .M_AXI_ARREADY(1'b1), + .M_AXI_ARADDR(ign_araddr), + .M_AXI_ARPROT(ign_arprot), + // + .M_AXI_RVALID(1'b0), + .M_AXI_RREADY(ign_rready), + .M_AXI_RDATA({(MSTDW){1'b0}}), + .M_AXI_RRESP(2'b00) + // }}} + // }}} + // }}} + ); + + // }}} + end else begin : WDN + if (OPT_WRITES) + begin : IMPLEMENT_WRITES + // {{{ + + // Register declarations + // {{{ + localparam BIDFIFOBW = IW+1+(SLVSZ-MSTSZ+1); + + // + // S_AXI_AW* skid buffer + wire skids_awvalid; + wire skids_awready; + wire [IW-1:0] skids_awid; + wire [AW-1:0] skids_awaddr; + wire [7:0] skids_awlen; + wire [2:0] skids_awsize; + wire [1:0] skids_awburst; + wire [2:0] skids_awprot; + // + // S_AXI_W* skid buffer + wire skids_wvalid, skids_wready; + wire [SLVDW-1:0] skids_wdata; + wire [SLVDW/8-1:0] skids_wstrb; + + wire slv_awready, slv_wready; + reg [SLVDW-1:0] slv_wdata; + reg [SLVDW/8-1:0] slv_wstrb; + + reg [IW-1:0] slv_awid; + reg [AW-1:0] slv_awaddr; + wire [AW-1:0] slv_next_awaddr; + reg [2:0] slv_awsize; + reg [1:0] slv_awburst; + reg [7:0] slv_awlen; + reg [8:0] slv_wlen; + reg slv_awlast; + + // Write registers + reg m_awvalid; + reg bfifo_write; + wire wfifo_wlast; + reg [IW+1+SLVSZ-MSTSZ:0] bfifo_wdata; + wire [LGFIFO:0] wfifo_count; + wire wfifo_full; + wire wfifo_empty; + wire [SLVSZ-MSTSZ:0] wfifo_subcount; + wire [IW-1:0] wfifo_bid; + reg [SLVSZ-MSTSZ:0] bcounts; + reg [IW-1:0] s_axi_bid, bid; + reg blast; + reg [1:0] s_axi_bresp, bresp; + reg s_axi_bvalid; + reg b_return_stall; + wire read_from_wrfifo; + // + // S_AXI_B* skid buffer isn't needed + // + // M_AXI_AW* skid buffer isn't needed + // + // M_AXI_W* skid buffer isn't needed + // + // M_AXI_B* skid buffer + wire skidm_bvalid, skidm_bready; + wire [1:0] skidm_bresp; + + reg m_wvalid; + reg [AW-1:0] mst_awaddr; + reg [2:0] mst_awprot; + reg [SLVSZ-MSTSZ:0] mst_awbeats, + mst_wbeats, next_slv_beats; + // }}} + //////////////////////////////////////////////////////////////// + // + // Incoming write address / data handling + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + //////////////////////////////////////// + // + // Clock #1: The skid buffer + // {{{ + + // The write address channel's skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2+3), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0) + // }}} + ) awskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, + S_AXI_AWSIZE, S_AXI_AWBURST, S_AXI_AWPROT }), + .o_valid(skids_awvalid), .i_ready(skids_awready), + .o_data({ skids_awid, skids_awaddr, skids_awlen, + skids_awsize, skids_awburst, skids_awprot }) + // }}} + ); + // }}} + // + // The write data channel's skid buffer (S_AXI_W*) + // {{{ + skidbuffer #( + // {{{ + .DW(SLVDW+SLVDW/8), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0) + // }}} + ) wskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(skids_wvalid), .i_ready(skids_wready), + .o_data({ skids_wdata, skids_wstrb }) + // }}} + ); + // }}} + + assign skids_awready = skids_wvalid && skids_wready + && slv_awlast; + + assign skids_wready = slv_wready; + // }}} + //////////////////////////////////////// + // + // Clock 2: slv_* clock + // {{{ + + // When are we ready for a next SLAVE beat? + assign slv_awready = (skids_wvalid && skids_wready); + + // slv_aw* + // {{{ + // slv_awaddr is the address of the value *CURRENTLY* in the + // buffer, *NOT* the next address. + initial slv_awlast = 1; + always @(posedge S_AXI_ACLK) + if (skids_awvalid && skids_awready) + begin + slv_awid <= skids_awid; + slv_awaddr <= skids_awaddr; + slv_awlen <= skids_awlen; + slv_awsize <= skids_awsize; + slv_awburst <= skids_awburst; + end else if (slv_awready && slv_wlen > 0) + begin + // Step forward a full beat -- but only when we have + // the data to do so + slv_awaddr <= slv_next_awaddr; + end + // }}} + + // slv_awlast + // {{{ + initial slv_awlast = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + slv_awlast <= 1; + else if (skids_awvalid && skids_awready) + slv_awlast <= (skids_awlen == 0); + else if (skids_wvalid && skids_wready) + slv_awlast <= (slv_wlen <= 2); + +`ifdef FORMAL + always @(*) + assert(slv_awlast == (slv_wlen <= 1)); +`endif + // }}} + + // slv_wlen = Number of beats remaining + // {{{ + // ... in the slave's data space, to include the one we've + // just ingested. Therefore, this is a 1-up counter. If + // slv_wlen == 0, then we are idle. + initial slv_wlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + slv_wlen <= 0; + else if (skids_awvalid && skids_awready) + begin + slv_wlen <= skids_awlen + 1; + end else if (skids_wvalid && skids_wready) // (slv_awready && slv_wlen > 0) + begin + slv_wlen <= slv_wlen - 1; +`ifdef FORMAL + assert(slv_wlen > 0); +`endif + end else if (slv_wlen == 1 && slv_wready) + slv_wlen <= 0; + // }}} + + // next_slv_beats + // {{{ + always @(*) + begin + // Verilator lint_off WIDTH + next_slv_beats = (1<<(skids_awsize-MSTSZ[2:0])) + - (skids_awaddr[SLVSZ-1:0] >> skids_awsize); + if (skids_awsize <= MSTSZ[2:0]) + next_slv_beats = 1; + // Verilator lint_on WIDTH + end + // }}} + + // slv_next_awaddr + // {{{ + axi_addr #( + // {{{ + .AW(AW), + .DW(SLVDW) + // }}} + ) get_next_slave_addr ( + // {{{ + .i_last_addr(slv_awaddr), + .i_size(slv_awsize), + .i_burst(slv_awburst), + .i_len(slv_awlen), + .o_next_addr(slv_next_awaddr) + // }}} + ); + // }}} + + assign slv_wready = (mst_awbeats <= (M_AXI_AWREADY ? 1:0)) + && (mst_wbeats <= (M_AXI_WREADY ? 1:0)) + && (!bfifo_write || !wfifo_full); + + // slv_wstrb, slv_wdata + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + { slv_wstrb, slv_wdata } <= 0; + else if (skids_awready) + begin + slv_wstrb <= skids_wstrb >> (skids_awaddr[SLVSZ-1:MSTSZ]*MSTDW/8); + slv_wdata <= skids_wdata >> (skids_awaddr[SLVSZ-1:MSTSZ]*MSTDW); + if (OPT_LOWPOWER && !skids_awvalid) + begin + slv_wstrb <= 0; + slv_wdata <= 0; + end + end else if (skids_wready) + begin + slv_wstrb <= skids_wstrb >> (slv_next_awaddr[SLVSZ-1:MSTSZ]*MSTDW/8); + slv_wdata <= skids_wdata >> (slv_next_awaddr[SLVSZ-1:MSTSZ]*MSTDW); + if (OPT_LOWPOWER && !skids_wvalid) + begin + slv_wstrb <= 0; + slv_wdata <= 0; + end + end else if (M_AXI_WVALID && M_AXI_WREADY) + begin + slv_wstrb <= slv_wstrb >> (MSTDW/8); + slv_wdata <= slv_wdata >> (MSTDW); + end + // }}} + + // }}} + //////////////////////////////////////// + // + // Clock 2 (continued): mst_* = M_AXI_* + // {{{ + + // mst_awaddr, mst_awprot, mst_awbeats + // {{{ + initial mst_awaddr = 0; + initial mst_awprot = 0; + always @(posedge S_AXI_ACLK) + begin + if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + if (skids_awvalid && skids_awready) + begin + mst_awaddr <= skids_awaddr; + mst_awprot <= skids_awprot; + mst_awbeats<= next_slv_beats; + end else if (skids_wvalid && skids_wready) + begin + mst_awaddr <= slv_next_awaddr; + mst_awbeats<= 1; + if (slv_awsize >= MSTSZ[2:0]) + mst_awbeats <= (1<<(slv_awsize - MSTSZ[2:0])); + end else begin + if (mst_awbeats > 1) + begin + mst_awaddr <= mst_awaddr + (1<<MSTSZ); + mst_awaddr[MSTSZ-1:0] <= 0; + end else if (OPT_LOWPOWER) + begin + mst_awaddr <= 0; + end + + if (mst_awbeats > 0) + mst_awbeats <= mst_awbeats - 1; + end + end + + if (!S_AXI_ARESETN) + begin + // {{{ + mst_awbeats <= 0; + if (OPT_LOWPOWER) + begin + mst_awaddr <= 0; + mst_awprot <= 0; + end + // }}} + end + end +`ifdef FORMAL + always @(*) + if(S_AXI_ARESETN && OPT_LOWPOWER && mst_awbeats == 0) + begin + assert(mst_awaddr == 0); + end + + always @(*) + if (S_AXI_ARESETN) + assert(m_axi_awvalid == (mst_awbeats > 0)); +`endif + // }}} + + // m_awvalid + // {{{ + initial m_axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_awvalid <= 0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + if (skids_wvalid && skids_wready) + m_awvalid <= 1'b1; + else if (mst_awbeats == 1) + m_awvalid <= 1'b0; + end + // }}} + + assign M_AXI_AWVALID = m_awvalid; + assign M_AXI_AWADDR = mst_awaddr; + assign M_AXI_AWPROT = mst_awprot; + + // M_AXI_WVALID, mst_wbeats + // {{{ + initial m_wvalid = 0; + initial mst_wbeats = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + m_wvalid <= 0; + mst_wbeats <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (skids_wvalid && skids_wready) + begin + m_wvalid <= 1'b1; + if (skids_awvalid && skids_awready) + mst_wbeats <= next_slv_beats; + else if (slv_awsize <= MSTSZ[2:0]) + mst_wbeats <= 1; + else + mst_wbeats <= (1<<(slv_awsize - MSTSZ[2:0])); + end else begin + if (mst_wbeats <= 1) + m_wvalid <= 1'b0; + if (mst_wbeats > 0) + mst_wbeats <= mst_wbeats - 1; + end + end +`ifdef FORMAL + always @(*) + if (S_AXI_ARESETN) + assert(M_AXI_WVALID == (mst_wbeats > 0)); +`endif + // }}} + + // M_AXI_W* + // {{{ + assign M_AXI_WVALID = m_wvalid; + assign M_AXI_WDATA = slv_wdata[MSTDW-1:0]; + assign M_AXI_WSTRB = slv_wstrb[MSTDW/8-1:0]; + // }}} + // }}} + //////////////////////////////////////// + // + // Clock 3: The B FIFO + // {{{ + + // bfifo_write + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bfifo_write <= 0; + else if (!bfifo_write || !wfifo_full) + bfifo_write <= skids_wvalid && skids_wready; + // }}} + + // bfifo_wdata + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bfifo_wdata <= 0; + else if (skids_awvalid && skids_awready) + begin + bfifo_wdata[SLVSZ-MSTSZ:0] <= next_slv_beats; + bfifo_wdata[SLVSZ-MSTSZ+1] <= (skids_awlen == 0); + bfifo_wdata[SLVSZ-MSTSZ+2 +: IW] <= skids_awid; +`ifdef FORMAL + assert(skids_wvalid && skids_wready); +`endif + end else if (skids_wvalid && skids_wready) + begin + bfifo_wdata[SLVSZ-MSTSZ+2 +: IW] <= slv_awid; + bfifo_wdata[SLVSZ-MSTSZ+1] <= (slv_wlen <= 2) + ? 1'b1 : 1'b0; + + if (slv_awsize <= MSTSZ[2:0]) + bfifo_wdata[SLVSZ-MSTSZ:0] <= 1; + else + bfifo_wdata[SLVSZ-MSTSZ:0] + <= (1<<(slv_awsize - MSTSZ[2:0])); + end + // }}} + + // BFIFO + // {{{ + sfifo #( + // {{{ + .BW(BIDFIFOBW), .LGFLEN(LGFIFO) + // }}} + ) bidlnfifo( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(bfifo_write), .i_data(bfifo_wdata), + .o_full(wfifo_full), .o_fill(wfifo_count), + .i_rd(read_from_wrfifo), + .o_data({ wfifo_bid, wfifo_wlast,wfifo_subcount }), + .o_empty(wfifo_empty) + // }}} + ); + // }}} + + // read_from_wrfifo + // {{{ + assign read_from_wrfifo = (bcounts <= 1)&&(!wfifo_empty) + &&(skidm_bvalid && skidm_bready); + // }}} + // }}} + //////////////////////////////////////// + // + // Return clock 1: the skid buffer + // {{{ + + // + // The downstream AXI-lite response (M_AXI_B*) skid buffer + skidbuffer #( + // {{{ + .DW(2), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0) + // }}} + ) bskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(M_AXI_BVALID), .o_ready(M_AXI_BREADY), + .i_data({ M_AXI_BRESP }), + .o_valid(skidm_bvalid), .i_ready(skidm_bready), + .o_data({ skidm_bresp }) + // }}} + ); + + assign skidm_bready = ((bcounts > 0)||(!wfifo_empty)) + && !b_return_stall; + // }}} + //////////////////////////////////////// + // + // Return staging: Counting returns + // {{{ + + // bcounts + // {{{ + // Return counts + initial bcounts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bcounts <= 0; + else if (skidm_bvalid && skidm_bready) + begin + if (read_from_wrfifo) + begin + /* + if (bcounts == 0 && wfifo_subcount <= 1 + && wfifo_wlast) + // Go straight to S_AXI_BVALID, no + // more bursts to count here + bcounts <= 0; + else + */ + bcounts <= wfifo_subcount + bcounts - 1; + end else + bcounts <= bcounts - 1; + end + // }}} + + // blast + // {{{ + always @(posedge S_AXI_ACLK) + if (read_from_wrfifo) + blast <= wfifo_wlast; + // }}} + + // bid + // {{{ + always @(posedge S_AXI_ACLK) + if (read_from_wrfifo) + bid <= wfifo_bid; + // }}} + + // bresp + // {{{ + initial bresp = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + bresp <= OKAY; + else if (!S_AXI_BVALID || S_AXI_BREADY) + bresp <= OKAY; + else if (skidm_bvalid && skidm_bready) + begin + // Let SLVERR take priority over DECERR + casez({ bresp, skidm_bresp }) + 4'b??0?: bresp <= bresp; + 4'b0?1?: bresp <= skidm_bresp; + 4'b1?10: bresp <= SLVERR; + 4'b1011: bresp <= SLVERR; + 4'b1111: bresp <= skidm_bresp; + endcase + + if (blast) + bresp <= OKAY; + end + // }}} + // }}} + //////////////////////////////////////// + // + // Return clock 2: S_AXI_* returns + // {{{ + + // s_axi_bid + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_BVALID || S_AXI_BREADY) + begin + if (bcounts > 0 && blast) + s_axi_bid <= bid; + else if (read_from_wrfifo) + s_axi_bid <= wfifo_bid; + else + s_axi_bid <= bid; + end + // }}} + + // s_axi_bvalid, b_return_stall + // {{{ + always @(*) + begin + // Force simulation evaluation on reset + if (!S_AXI_ARESETN) + b_return_stall = 0; + + // Default: stalled if the output is stalled + b_return_stall = S_AXI_BVALID && !S_AXI_BREADY; + + if (bcounts > 1) + b_return_stall = 0; + else if (bcounts == 1 && !blast) + b_return_stall = 0; + else if (bcounts == 0 + && (wfifo_subcount > 1 || !wfifo_wlast)) + b_return_stall = 0; + end + + initial s_axi_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_bvalid <= 0; + else if (!S_AXI_BVALID || S_AXI_BREADY) + begin + s_axi_bvalid <= 0; + if (skidm_bvalid && skidm_bready) + s_axi_bvalid <= ((bcounts == 1)&& blast) + ||((bcounts == 0) && (!wfifo_empty) + && (wfifo_subcount <= 1)&& wfifo_wlast); + end + // }}} + + // s_axi_bresp + // {{{ + initial s_axi_bresp = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_bresp <= OKAY; + else if (!S_AXI_BVALID || S_AXI_BREADY) + begin + if (skidm_bvalid && skidm_bready) + begin + // Let SLVERR take priority over DECERR + casez({ bresp, skidm_bresp[1] }) + 3'b??0: s_axi_bresp <= s_axi_bresp; + 3'b0?1: s_axi_bresp <= skidm_bresp; + 3'b101: s_axi_bresp <= SLVERR; + 3'b111: s_axi_bresp <= skidm_bresp; + endcase + end else + s_axi_bresp <= bresp; + end + // }}} + + // S_AXI_B* assignments + // {{{ + assign S_AXI_BID = s_axi_bid; + assign S_AXI_BRESP = s_axi_bresp; + assign S_AXI_BVALID = s_axi_bvalid; + // }}} + // }}} + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_write; + assign unused_write = &{ 1'b0, wfifo_count, S_AXI_WLAST }; + // Verilator lint_on UNUSED + // }}} + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + // + // Formal properties, write half + // {{{ + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// +`ifdef FORMAL + // These are only a subset of the formal properties used to + // verify this design. While I will warrant that the full + // property set will work, I don't really expect the below + // properties to be sufficient or for that matter even + // syntactically correct. + // + // Register declarations + // {{{ + localparam F_LGDEPTH = LGFIFO+1+8; + + wire [F_LGDEPTH-1:0] faxi_awr_nbursts; + wire [9-1:0] faxi_wr_pending; + wire [F_LGDEPTH-1:0] faxi_rd_nbursts; + wire [F_LGDEPTH-1:0] faxi_rd_outstanding; + + // + // ... + // + + localparam F_AXIL_LGDEPTH = F_LGDEPTH; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + // }}} + //////////////////////////////////////////////////////////////// + // + // AXI channel properties + // {{{ + //////////////////////////////////////////////////////////////// + // + // + faxi_slave #( + // {{{ + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(SLVDW), + .C_AXI_ADDR_WIDTH(AW), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .OPT_EXCLUSIVE(0) + // ... + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address + // {{{ + .i_axi_awvalid(skids_awvalid), + .i_axi_awready(skids_awready), + .i_axi_awid( skids_awid), + .i_axi_awaddr( skids_awaddr), + .i_axi_awlen( skids_awlen), + .i_axi_awsize( skids_awsize), + .i_axi_awburst(skids_awburst), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( skids_awprot), + .i_axi_awqos( 0), + // }}} + // Write data + // {{{ + .i_axi_wvalid( skids_wvalid), + .i_axi_wready( skids_wready), + .i_axi_wdata( skids_wdata), + .i_axi_wstrb( skids_wstrb), + .i_axi_wlast( S_AXI_WLAST), + // }}} + // Write return response + // {{{ + .i_axi_bvalid( S_AXI_BVALID), + .i_axi_bready( S_AXI_BREADY), + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + // }}} + // Read address + // {{{ + .i_axi_arvalid(1'b0), + .i_axi_arready(1'b0), + .i_axi_arid( skids_awid), + .i_axi_araddr( skids_awaddr), + .i_axi_arlen( skids_awlen), + .i_axi_arsize( skids_awsize), + .i_axi_arburst(skids_awburst), + .i_axi_arlock( 0), + .i_axi_arcache(0), + .i_axi_arprot( 0), + .i_axi_arqos( 0), + // }}} + // Read response + // {{{ + .i_axi_rvalid( 1'b0), + .i_axi_rready( 1'b0), + .i_axi_rid( S_AXI_RID), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rlast( S_AXI_RLAST), + .i_axi_rresp( S_AXI_RRESP), + // }}} + // Formal property data + // {{{ + .f_axi_awr_nbursts( faxi_awr_nbursts), + .f_axi_wr_pending( faxi_wr_pending), + .f_axi_rd_nbursts( faxi_rd_nbursts), + .f_axi_rd_outstanding(faxi_rd_outstanding) + // + // ... + // + // }}} + // }}} + ); + + always @(*) + begin + // ... + assert(faxi_rd_nbursts == 0); + end + + // }}} + //////////////////////////////////////////////////////////////// + // + // AXI-lite properties + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + faxil_master #( + // {{{ + .C_AXI_DATA_WIDTH(MSTDW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_NO_RESET(1), + .F_AXI_MAXWAIT(5), + .F_AXI_MAXDELAY(4), + .F_AXI_MAXRSTALL(0), + .F_OPT_WRITE_ONLY(1), + .F_OPT_READ_ONLY(1'b0), + .F_LGDEPTH(F_AXIL_LGDEPTH) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address channel + // {{{ + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awprot( M_AXI_AWPROT), + // }}} + // Write data + // {{{ + .i_axi_wvalid( M_AXI_WVALID), + .i_axi_wready( M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + // }}} + // Write response + // {{{ + .i_axi_bvalid( skidm_bvalid), + .i_axi_bready( skidm_bready), + .i_axi_bresp( skidm_bresp), + // }}} + // Read address + // {{{ + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arprot( M_AXI_ARPROT), + // }}} + // Read data return + // {{{ + .i_axi_rvalid( 1'b0), + .i_axi_rready( 1'b1), + .i_axi_rdata( {(C_M_AXI_DATA_WIDTH){1'b0}}), + .i_axi_rresp( 2'b00), + // }}} + // Formal check variables + // {{{ + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + // }}} + ); + + always @(*) + if (S_AXI_ARESETN) + begin + assert(faxil_rd_outstanding == 0); + + assert(faxil_awr_outstanding + + ((mst_awbeats > 0) ? mst_awbeats : 0) + == f_wfifo_beats + + (bfifo_write ? bfifo_wdata[SLVSZ-MSTSZ:0] : 0) + + bcounts); + + assert(faxil_wr_outstanding + + ((mst_wbeats > 0) ? mst_wbeats : 0) + == f_wfifo_beats + + (bfifo_write ? bfifo_wdata[SLVSZ-MSTSZ:0] : 0) + + bcounts); + end + + + always @(*) + assert(faxil_awr_outstanding <= { 1'b1, {(LGFIFO+8){1'b0}} } + bcounts); + always @(*) + assert(faxil_wr_outstanding <= { 1'b1, {(LGFIFO+8){1'b0}} } + bcounts); + + // }}} + //////////////////////////////////////////////////////////////// + // + // BFIFO property checking + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // + // ... + // + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN) + begin + assert(faxi_awr_nbursts == f_bfifo_packets + + (slv_awvalid ? 1:0)); + assert(f_bfifo_packets <= wfifo_count); + + // ... + end + + // + // ... + // + + // }}} +`endif + // }}} + // }}} + end else begin : NO_WRITE_SUPPORT + // {{{ + assign S_AXI_AWREADY = 0; + assign S_AXI_WREADY = 0; + assign S_AXI_BID = 0; + assign S_AXI_BRESP = 2'b11; + assign S_AXI_BVALID = 0; + assign S_AXI_BID = 0; + + // + assign M_AXI_AWVALID = 0; + assign M_AXI_AWADDR = 0; + assign M_AXI_AWPROT = 0; + // + assign M_AXI_WVALID = 0; + assign M_AXI_WDATA = 0; + assign M_AXI_WSTRB = 0; + // + assign M_AXI_BREADY = 0; + // }}} + end end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_READS && SLVDW == MSTDW) + begin : IMPLEMENT_AXI2AXILITE_READS + // {{{ + + // (Unused) signal declarations + // {{{ + // Verilator lint_off UNUSED + wire ign_awready, ign_wready, + ign_bvalid; + wire [IW-1:0] ign_bid; + wire [1:0] ign_bresp; + + wire ign_awvalid, ign_wvalid, + ign_bready; + wire [AW-1:0] ign_awaddr; + wire [2:0] ign_awprot; + wire [MSTDW-1:0] ign_wdata; + wire [MSTDW/8-1:0] ign_wstrb; + // Verilator lint_on UNUSED + // }}} + + axi2axilite #( + // {{{ + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(SLVDW), + .C_AXI_ADDR_WIDTH(AW), + .OPT_WRITES(0), + .OPT_READS(1), + .LGFIFO(LGFIFO) + // }}} + ) axilread ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + // AXI4 slave interface + // {{{ + // Write address channel + // {{{ + .S_AXI_AWVALID(1'b0), + .S_AXI_AWREADY(ign_awready), + .S_AXI_AWID( {(IW){1'b0}} ), + .S_AXI_AWADDR( {(AW){1'b0}} ), + .S_AXI_AWLEN( 8'h0), + .S_AXI_AWSIZE( 3'h0), + .S_AXI_AWBURST(2'h0), + .S_AXI_AWLOCK( 1'b0), + .S_AXI_AWCACHE(4'h0), + .S_AXI_AWPROT( 3'h0), + .S_AXI_AWQOS( 4'h0), + // }}} + // Write data channel + // {{{ + .S_AXI_WVALID(1'b0), + .S_AXI_WREADY(ign_wready), + .S_AXI_WDATA( {(SLVDW){1'b0}}), + .S_AXI_WSTRB( {(SLVDW/8){1'b0}}), + .S_AXI_WLAST( 1'b0), + // }}} + // Write return channel + // {{{ + .S_AXI_BVALID(ign_bvalid), + .S_AXI_BREADY(1'b1), + .S_AXI_BID( ign_bid), + .S_AXI_BRESP( ign_bresp), + // }}} + // Read address channel + // {{{ + .S_AXI_ARVALID(S_AXI_ARVALID), + .S_AXI_ARREADY(S_AXI_ARREADY), + .S_AXI_ARID( S_AXI_ARID), + .S_AXI_ARADDR( S_AXI_ARADDR), + .S_AXI_ARLEN( S_AXI_ARLEN), + .S_AXI_ARSIZE( S_AXI_ARSIZE), + .S_AXI_ARBURST(S_AXI_ARBURST), + .S_AXI_ARLOCK( S_AXI_ARLOCK), + .S_AXI_ARCACHE(S_AXI_ARCACHE), + .S_AXI_ARPROT( S_AXI_ARPROT), + .S_AXI_ARQOS( S_AXI_ARQOS), + // }}} + // Read data channel + // {{{ + .S_AXI_RVALID(S_AXI_RVALID), + .S_AXI_RREADY(S_AXI_RREADY), + .S_AXI_RID( S_AXI_RID), + .S_AXI_RDATA( S_AXI_RDATA), + .S_AXI_RRESP( S_AXI_RRESP), + .S_AXI_RLAST( S_AXI_RLAST), + // }}} + // }}} + // AXI-lite master interface + // {{{ + // AXI-lite Write interface + // {{{ + .M_AXI_AWVALID(ign_awvalid), + .M_AXI_AWREADY(1'b1), + .M_AXI_AWADDR(ign_awaddr), + .M_AXI_AWPROT(ign_awprot), + // + .M_AXI_WVALID(ign_wvalid), + .M_AXI_WREADY(1'b1), + .M_AXI_WDATA(ign_wdata), + .M_AXI_WSTRB(ign_wstrb), + // + .M_AXI_BVALID(1'b0), + .M_AXI_BREADY(ign_bready), + .M_AXI_BRESP(2'b00), + // }}} + // AXI-lite read interface + // {{{ + .M_AXI_ARVALID(M_AXI_ARVALID), + .M_AXI_ARREADY(M_AXI_ARREADY), + .M_AXI_ARADDR(M_AXI_ARADDR), + .M_AXI_ARPROT(M_AXI_ARPROT), + // + .M_AXI_RVALID(M_AXI_RVALID), + .M_AXI_RREADY(M_AXI_RREADY), + .M_AXI_RDATA(M_AXI_RDATA), + .M_AXI_RRESP(M_AXI_RRESP) + // }}} + // }}} + // }}} + ); + + // }}} + end else begin : RDN + if (OPT_READS) + begin : IMPLEMENT_READS + // {{{ + + // Declarations + // {{{ + wire slv_arvalid, slv_arready; + reg [IW-1:0] slv_arid, mst_arid; + reg [AW-1:0] slv_araddr, mst_araddr; + wire [AW-1:0] slv_next_araddr; + reg [7:0] slv_arlen; + reg slv_arlast, mst_arlast, + mst_arsublast; + reg [2:0] slv_arprot, mst_arprot; + reg [2:0] slv_arsize; + reg [1:0] slv_arburst; + reg [SLVSZ-MSTSZ:0] slv_arbeats, mst_arbeats; + reg [8:0] slv_rlen; + + wire [SLVSZ-MSTSZ-1:0] rfifo_addr; + wire rfifo_end_of_beat, + rfifo_rlast; + wire [4:0] rfifo_count; + // + reg m_arvalid; + wire rfifo_full; + wire rfifo_empty; + reg s_axi_rvalid; + reg [1:0] s_axi_rresp; + reg [IW-1:0] s_axi_rid; + wire [IW-1:0] rfifo_rid; + reg [SLVDW-1:0] s_axi_rdata, next_rdata; + reg s_axi_rlast; + reg [IW-1:0] rid; + wire read_from_rdfifo; + + // + // + // S_AXI_AR* skid buffer + wire skids_arvalid, skids_arready; + wire [IW-1:0] skids_arid; + wire [AW-1:0] skids_araddr; + wire [7:0] skids_arlen; + wire [2:0] skids_arsize, skids_arprot; + wire [1:0] skids_arburst; + // + // S_AXI_R* skid buffer isn't needed + // + // M_AXI_AR* skid buffer isn't needed + // M_AXI_R* skid buffer + wire skidm_rvalid, skidm_rready; + wire [MSTDW-1:0] skidm_rdata; + wire [1:0] skidm_rresp; + // }}} + + // S_AXI_AR* skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2+3), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0) + // }}} + ) arskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data({ S_AXI_ARID, S_AXI_ARADDR, S_AXI_ARLEN, + S_AXI_ARSIZE, S_AXI_ARBURST, S_AXI_ARPROT }), + .o_valid(skids_arvalid), .i_ready(skids_arready), + .o_data({ skids_arid, skids_araddr, skids_arlen, + skids_arsize, skids_arburst, skids_arprot }) + // }}} + ); + // }}} + // M_AXI_R* skid buffer + // {{{ + skidbuffer #( + // {{{ + .DW(MSTDW+2), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0) + // }}} + ) rskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(M_AXI_RVALID), .o_ready(M_AXI_RREADY), + .i_data({ M_AXI_RDATA, M_AXI_RRESP }), + .o_valid(skidm_rvalid), .i_ready(skidm_rready), + .o_data({ skidm_rdata, skidm_rresp }) + // }}} + ); + // }}} + + assign skids_arready = (slv_rlen <= (slv_arready ? 1:0)) + &&(mst_arbeats <=(M_AXI_ARREADY ? 1:0)); + + // slv_* + // {{{ + always @(posedge S_AXI_ACLK) + begin + if (OPT_LOWPOWER && (slv_rlen <= (slv_arready ? 1:0))) + begin + // {{{ + slv_arid <= 0; + slv_araddr <= 0; + slv_arlen <= 0; + slv_arsize <= 0; + slv_arburst <= 0; + slv_arprot <= 0; + + slv_rlen <= 0; + // }}} + end + + if (skids_arvalid && skids_arready) + begin + // {{{ + slv_arid <= skids_arid; + slv_araddr <= skids_araddr; + slv_arlen <= skids_arlen; + slv_arsize <= skids_arsize; + slv_arburst <= skids_arburst; + slv_arlast <= (skids_arlen == 0); + slv_arprot <= skids_arprot; + + slv_rlen <= skids_arlen+1; + // }}} + end else if (slv_arready && slv_rlen > 0) + begin + // {{{ + slv_araddr <= (!OPT_LOWPOWER || slv_rlen > 1) + ? slv_next_araddr : 0; + slv_rlen <= slv_rlen - 1; + slv_arlast <= (slv_rlen <= 2); + // }}} + end + + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + // {{{ + slv_arid <= 0; + slv_araddr <= 0; + slv_arlen <= 0; + slv_arsize <= 0; + slv_arburst <= 0; + slv_arprot <= 0; + + slv_arlast <= 1; + // }}} + end + + if (!S_AXI_ARESETN) + slv_rlen <= 0; + end + + assign slv_arvalid = (slv_rlen > 0); +`ifdef FORMAL + always @(*) + if (S_AXI_ARESETN) + begin + assert(slv_arlast == (slv_rlen <= 1)); + assert(slv_rlen <= (slv_arlen + 1)); + end +`endif + // }}} + + // slv_next_araddr + // {{{ + axi_addr #( + // {{{ + .AW(AW), + .DW(SLVDW) + // }}} + ) get_next_slave_addr ( + // {{{ + .i_last_addr(slv_araddr), + .i_size(slv_arsize), + .i_burst(slv_arburst), + .i_len(slv_arlen), + .o_next_addr(slv_next_araddr) + // }}} + ); + // }}} + + // slv_arbeats + // {{{ + always @(*) + if (slv_rlen > 0) + begin + // Master beats to turn this slave beat into + if (slv_arsize >= MSTSZ[2:0]) + slv_arbeats = (1<<(slv_arsize-MSTSZ[2:0])) + - (slv_araddr[MSTSZ-1:0] >> slv_arsize); + else + slv_arbeats = 1; + end else + slv_arbeats = 0; + // }}} + + // mst_araddr, mst_arprot, mst_arbeats + // {{{ + always @(posedge S_AXI_ACLK) + begin + if (slv_arready) + begin + // {{{ + mst_arid <= slv_arid; + mst_araddr <= slv_araddr; + mst_arprot <= slv_arprot; + + // Beats to turn this beat into + mst_arbeats <= slv_arbeats; + mst_arlast <= slv_arlast; + mst_arsublast <= (slv_arbeats <= 1); + + if (OPT_LOWPOWER && slv_rlen == 0) + begin + mst_arid <= 0; + mst_araddr <= 0; + mst_arprot <= 0; + end + // }}} + end else if ((mst_arbeats > 0) + &&(M_AXI_ARVALID && M_AXI_ARREADY)) + begin + // {{{ + mst_araddr <= mst_araddr + (1<<MSTSZ); + mst_araddr[MSTSZ-1:0] <= 0; + mst_arbeats <= mst_arbeats - 1; + + mst_arsublast <= (mst_arbeats <= 2); + // }}} + end + + if (!S_AXI_ARESETN) + begin + // {{{ + mst_arbeats <= 0; + if (OPT_LOWPOWER) + begin + mst_arid <= 0; + mst_araddr <= 0; + mst_arprot <= 0; + end + // }}} + end + end +`ifdef FORMAL + always @(*) + if(OPT_LOWPOWER && S_AXI_ARESETN && mst_arbeats == 0) + begin + assert(mst_arid == 0); + assert(mst_araddr == 0); + assert(mst_arprot == 0); + end +`endif + // }}} + + // m_arvalid + // {{{ + initial m_arvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_arvalid <= 0; + else if (slv_arvalid && slv_arready) + m_arvalid <= 1; + else if (M_AXI_ARVALID && M_AXI_ARREADY) + m_arvalid <= (mst_arbeats > 1); +`ifdef FORMAL + always @(*) + if (S_AXI_ARESETN) + assert(M_AXI_ARVALID == (mst_arbeats > 0)); +`endif + // }}} + + assign slv_arready = (mst_arbeats <= (M_AXI_ARREADY ? 1:0)); + assign read_from_rdfifo = skidm_rvalid && skidm_rready + &&(!S_AXI_RVALID || S_AXI_RREADY); + + // Read ID FIFO + // {{{ + sfifo #( + // {{{ + .BW(IW+2+SLVSZ-MSTSZ), + .LGFLEN(LGFIFO) + // }}} + ) ridlnfifo( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + // + .i_wr(M_AXI_ARVALID && M_AXI_ARREADY), + .i_data({ mst_arid, mst_arlast, mst_arsublast, + M_AXI_ARADDR[SLVSZ-1:MSTSZ] }), + .o_full(rfifo_full), .o_fill(rfifo_count), + // + .i_rd(read_from_rdfifo), + .o_data({ rfifo_rid, rfifo_rlast, rfifo_end_of_beat, + rfifo_addr }), + .o_empty(rfifo_empty) + // }}} + ); + // }}} + + assign skidm_rready = (!S_AXI_RVALID || S_AXI_RREADY) + && !rfifo_empty; + + // s_axi_rvalid + // {{{ + initial s_axi_rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_rvalid <= 0; + else if (skidm_rvalid && skidm_rready && rfifo_end_of_beat) + s_axi_rvalid <= 1'b1; + else if (S_AXI_RREADY) + s_axi_rvalid <= 0; + // }}} + + // s_axi_rdata + // {{{ + always @(*) + begin + next_rdata = s_axi_rdata; + if (S_AXI_RVALID) + next_rdata = 0; + if (skidm_rvalid) + next_rdata = next_rdata | ({ {(SLVDW-MSTDW){1'b0}}, skidm_rdata } << (rfifo_addr * MSTDW)); + end + + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + s_axi_rdata <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + s_axi_rdata <= next_rdata; + // }}} + + // s_axi_rresp + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_axi_rresp <= OKAY; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (S_AXI_RVALID) + s_axi_rresp <= (skidm_rvalid) ? skidm_rresp : OKAY; + else if (skidm_rvalid) + casez({ s_axi_rresp, skidm_rresp[1] }) + // Let SLVERR take priority over DECERR + 3'b??0: s_axi_rresp <= s_axi_rresp; + 3'b0?1: s_axi_rresp <= skidm_rresp; + 3'b101: s_axi_rresp <= SLVERR; + 3'b111: s_axi_rresp <= skidm_rresp; + endcase + end + // }}} + + // rid + // {{{ + initial rid = 0; + always @(posedge S_AXI_ACLK) + if (read_from_rdfifo) + rid <= rfifo_rid; + // }}} + + // s_axi_rlast + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (read_from_rdfifo) + s_axi_rlast <= rfifo_rlast; + else + s_axi_rlast <= 0; + end + // }}} + + // s_axi_rid + // {{{ + initial s_axi_rid = 0; + always @(posedge S_AXI_ACLK) + if ((S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + ||(!S_AXI_RVALID && rfifo_end_of_beat)) + s_axi_rid <= (read_from_rdfifo && rfifo_end_of_beat)?rfifo_rid : rid; + // }}} + + // M_AXI_AR* + // {{{ + assign M_AXI_ARVALID= m_arvalid; + assign M_AXI_ARADDR = mst_araddr; + assign M_AXI_ARPROT = mst_arprot; + // }}} + // S_AXI_R* + // {{{ + assign S_AXI_RVALID = s_axi_rvalid; + assign S_AXI_RDATA = s_axi_rdata; + assign S_AXI_RRESP = s_axi_rresp; + assign S_AXI_RLAST = s_axi_rlast; + assign S_AXI_RID = s_axi_rid; + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_read; + assign unused_read = &{ 1'b0, + /* + S_AXI_AWID, S_AXI_AWVALID, + S_AXI_AWLEN, S_AXI_AWBURST, S_AXI_AWSIZE, + S_AXI_AWADDR, + S_AXI_WVALID, S_AXI_WDATA, S_AXI_WSTRB, + S_AXI_WLAST, S_AXI_BREADY, + M_AXI_AWREADY, M_AXI_WREADY, + M_AXI_BRESP, M_AXI_BVALID, + */ + rfifo_count, rfifo_full + }; + // Verilator lint_on UNUSED + // }}} + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + // + // Formal properties, read half + // {{{ + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////// +`ifdef FORMAL + // As with the write half, the following is a subset of the + // formal properties used to verify this section of the core. + // It may, or may not, be syntactically correct. I don't + // warrant this version of the design. + // + // Register declarations + // {{{ + localparam F_LGDEPTH = LGFIFO+1+8; + + wire [F_LGDEPTH-1:0] faxi_awr_nbursts; + wire [9-1:0] faxi_wr_pending; + wire [F_LGDEPTH-1:0] faxi_rd_nbursts; + wire [F_LGDEPTH-1:0] faxi_rd_outstanding; + + // + // ... + // + + localparam F_AXIL_LGDEPTH = F_LGDEPTH; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + // }}} + //////////////////////////////////////////////////////////////// + // + // AXI channel properties + // {{{ + //////////////////////////////////////////////////////////////// + // + // + faxi_slave #( + // {{{ + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(SLVDW), + .C_AXI_ADDR_WIDTH(AW), + .OPT_EXCLUSIVE(0) + // ... + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address + // {{{ + .i_axi_awvalid(1'b0), + .i_axi_awready(1'b0), + .i_axi_awid( skids_arid), + .i_axi_awaddr( skids_araddr), + .i_axi_awlen( 8'h0), + .i_axi_awsize( 3'h0), + .i_axi_awburst(2'h0), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( 0), + .i_axi_awqos( 0), + // }}} + // Write data + // {{{ + .i_axi_wvalid( 1'b0), + .i_axi_wready( 1'b0), + .i_axi_wdata( {(C_S_AXI_DATA_WIDTH ){1'b0}}), + .i_axi_wstrb( {(C_S_AXI_DATA_WIDTH/8){1'b0}}), + .i_axi_wlast( 1'b0), + // }}} + // Write return response + // {{{ + .i_axi_bvalid( 1'b0), + .i_axi_bready( 1'b0), + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( 2'b00), + // }}} + // Read address + // {{{ + .i_axi_arready(skids_arready), + .i_axi_arid( skids_arid), + .i_axi_araddr( skids_araddr), + .i_axi_arlen( skids_arlen), + .i_axi_arsize( skids_arsize), + .i_axi_arburst(skids_arburst), + .i_axi_arlock( 0), + .i_axi_arcache(0), + .i_axi_arprot( 0), + .i_axi_arqos( 0), + .i_axi_arvalid(skids_arvalid), + // }}} + // Read response + // {{{ + .i_axi_rid( S_AXI_RID), + .i_axi_rresp( S_AXI_RRESP), + .i_axi_rvalid( S_AXI_RVALID), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rlast( S_AXI_RLAST), + .i_axi_rready( S_AXI_RREADY), + // }}} + // Formal property data + // {{{ + .f_axi_awr_nbursts( faxi_awr_nbursts), + .f_axi_wr_pending( faxi_wr_pending), + .f_axi_rd_nbursts( faxi_rd_nbursts), + .f_axi_rd_outstanding(faxi_rd_outstanding), + // + // ... + // + // }}} + // }}} + ); + + always @(*) + begin + assert(faxi_awr_nbursts == 0); + assert(faxi_wr_pending == 0); + assert(faxi_wr_ckvalid == 0); + end + // }}} + //////////////////////////////////////////////////////////////// + // + // AXI-lite properties + // {{{ + //////////////////////////////////////////////////////////////// + // + // + faxil_master #( + // {{{ + .C_AXI_DATA_WIDTH(MSTDW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_NO_RESET(1), + .F_AXI_MAXWAIT(5), + .F_AXI_MAXDELAY(4), + .F_AXI_MAXRSTALL(0), + .F_OPT_WRITE_ONLY(1'b0), + .F_OPT_READ_ONLY(1'b1), + .F_LGDEPTH(F_AXIL_LGDEPTH) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // Write address channel + // {{{ + .i_axi_awvalid(1'b0), + .i_axi_awready(1'b0), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awprot( 3'h0), + // }}} + // Write data + // {{{ + .i_axi_wvalid( 1'b0), + .i_axi_wready( 1'b0), + .i_axi_wdata( {(C_M_AXI_DATA_WIDTH ){1'b0}}), + .i_axi_wstrb( {(C_M_AXI_DATA_WIDTH/8){1'b0}}), + // }}} + // Write response + // {{{ + .i_axi_bvalid( 1'b0), + .i_axi_bready( 1'b0), + .i_axi_bresp( 2'b00), + // }}} + // Read address + // {{{ + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arprot( M_AXI_ARPROT), + // }}} + // Read data return + // {{{ + .i_axi_rvalid( skidm_rvalid), + .i_axi_rready( skidm_rready), + .i_axi_rdata( skidm_rdata), + .i_axi_rresp( skidm_rresp), + // }}} + // Formal check variables + // {{{ + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + // }}} + ); + + always @(*) + begin + assert(faxil_awr_outstanding == 0); + assert(faxil_wr_outstanding == 0); + end + // }}} +`endif + // }}} + // }}} + end else begin : NO_READ_SUPPORT // if (!OPT_READS) + // {{{ + assign M_AXI_ARVALID= 0; + assign M_AXI_ARADDR = 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_RREADY = 0; + // + assign S_AXI_ARREADY= 0; + assign S_AXI_RVALID = 0; + assign S_AXI_RDATA = 0; + assign S_AXI_RRESP = 0; + assign S_AXI_RLAST = 0; + assign S_AXI_RID = 0; + + // Make Verilator happy + // Verilator lint_off UNUSED + wire unused_read; + assign unused_read = &{ 1'b0, M_AXI_ARREADY, M_AXI_RVALID, + M_AXI_RDATA, M_AXI_RRESP, S_AXI_RREADY, + S_AXI_ARLEN, S_AXI_ARSIZE, S_AXI_ARBURST, + S_AXI_ARADDR, S_AXI_ARVALID, S_AXI_ARID + }; + // Verilator lint_on UNUSED + // }}} + end end endgenerate + // }}} + // Minimal parameter validation + // {{{ + initial begin + if (SLVDW < MSTDW) + begin + $fatal; // Fatal elaboration error + $stop; // Stop any simulation + end + end + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, + S_AXI_AWLOCK, S_AXI_AWCACHE, S_AXI_AWPROT, S_AXI_AWQOS, + // skids_wlast, wfifo_count, rfifo_count + S_AXI_ARLOCK, S_AXI_ARCACHE, S_AXI_ARPROT, S_AXI_ARQOS + }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // Assume that the two write channels stay within an appropriate + // distance of each other. This is to make certain that the property + // file features are not violated, although not necessary true for + // actual operation + // + + //////////////////////////////////////////////////////////////////////// + // + // Select only write or only read operation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (!OPT_WRITES) + begin + always @(*) + begin + assume(!S_AXI_AWVALID); + assume(!S_AXI_WVALID); + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assume(!M_AXI_BVALID); + assert(!S_AXI_BVALID); + end + end endgenerate + + generate if (!OPT_READS) + begin + + always @(*) + begin + assume(!S_AXI_ARVALID); + assert(!M_AXI_ARVALID); + end + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Lowpower assertions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_LOWPOWER) + begin : F_LOWPOWER + + always @(*) + if (S_AXI_ARESETN) + begin + if (!M_AXI_AWVALID) + begin + // Not supported. + // assert(M_AXI_AWADDR == 0); + // assert(M_AXI_AWPROT == 0); + end + + if (!M_AXI_WVALID) + begin + // assert(M_AXI_WDATA == 0); + // assert(M_AXI_WSTRB == 0); + end + + if (!M_AXI_ARVALID) + begin + assert(M_AXI_ARADDR == 0); + assert(M_AXI_ARPROT == 0); + end + + if (!S_AXI_RVALID) + begin + // These items build over the course of a + // returned burst, so they might not be + // zero when RVALID is zero. + // + // assert(S_AXI_RLAST == 0); + // assert(S_AXI_RDATA == 0); + // assert(S_AXI_RID == 0); + end + end + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover statements, to show performance + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_READS) + begin + // {{{ + reg [3:0] cvr_read_count, cvr_read_count_simple; + + initial cvr_read_count_simple = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_count_simple <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN == 0) + cvr_read_count_simple <= cvr_read_count_simple + 1; + + initial cvr_read_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_count <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN > 2) + cvr_read_count <= cvr_read_count + 1; + + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // "Careless" assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + // }}} +`undef BMC_ASSERT +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axi32axi.v b/rtl/wb2axip/axi32axi.v new file mode 100644 index 0000000..fd1badb --- /dev/null +++ b/rtl/wb2axip/axi32axi.v @@ -0,0 +1,376 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi32axi.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Bridge from an AXI3 slave to an AXI4 master +// +// The goal here is to support as high a bus speed as possible, maintain +// burst support (if possible) and (more important) allow bus requests +// coming from the ARM within either the Zynq or one of Intel's SOC chips +// to speak with an AutoFPGA based design. +// +// Note that if you aren't using AutoFPGA, then you probably don't need +// this core--the vendor tools should be able to handle this conversion +// quietly and automatically for you. +// +// Notes: +// AxCACHE is remapped as per the AXI4 specification, since the values +// aren't truly equivalent. This forces a single clock delay in the Ax* +// channels and (likely) the W* channel as well as a system level +// consequence. +// +// AXI3 locking is not supported under AXI4. As per the AXI4 spec, +// AxLOCK is converteted from AXI3 to AXI4 by just dropping the high +// order bit. +// +// The WID input is ignored. Whether or not this input can be ignored +// is based upon how the ARM is implemented internally. After a bit +// of research into both Zynq's and Intel SOCs, this appears to be the +// appropriate answer here. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axi32axi #( + // {{{ + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + parameter OPT_REORDER_METHOD = 0, + parameter [0:0] OPT_TRANSFORM_AXCACHE = 1, + parameter [0:0] OPT_LOWPOWER = 0, + parameter [0:0] OPT_LOW_LATENCY = 0, + parameter WID_LGAWFIFO = 3, + parameter WID_LGWFIFO = 3 + // + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The AXI3 incoming/slave interface + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [3:0] S_AXI_AWLEN, + input wire [2:0] S_AXI_AWSIZE, + input wire [1:0] S_AXI_AWBURST, + input wire [1:0] S_AXI_AWLOCK, + input wire [3:0] S_AXI_AWCACHE, + input wire [2:0] S_AXI_AWPROT, + input wire [3:0] S_AXI_AWQOS, + // + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_WID, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [3:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire [1:0] S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire S_AXI_RLAST, + output wire [1:0] S_AXI_RRESP, + // + // + // The AXI4 Master (outgoing) interface + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [7:0] M_AXI_AWLEN, + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, + output wire M_AXI_AWLOCK, + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [7:0] M_AXI_ARLEN, + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP + // }}} + ); + + // Register/net declarations + // {{{ + // localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3; + localparam IW=C_AXI_ID_WIDTH; + reg [3:0] axi4_awcache, axi4_arcache; + reg axi4_awlock, axi4_arlock; + wire awskd_ready; + wire wid_reorder_awready; + wire [IW-1:0] reordered_wid; + // }}} + + // Write cache remapping + // {{{ + always @(*) + case(S_AXI_AWCACHE) + 4'b1010: axi4_awcache = 4'b1110; + 4'b1011: axi4_awcache = 4'b1111; + default: axi4_awcache = S_AXI_AWCACHE; + endcase + // }}} + + // AWLOCK + // {{{ + always @(*) + axi4_awlock = S_AXI_AWLOCK[0]; + // }}} + + // AW Skid buffer + // {{{ + generate if (OPT_TRANSFORM_AXCACHE) + begin : GEN_AWCACHE + // {{{ + skidbuffer #( + .DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + + 4 + 3 + 2 + 1+4+3+4), + .OPT_OUTREG(1'b1) + ) awskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID && wid_reorder_awready), + .o_ready(awskd_ready), + .i_data({ S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, + S_AXI_AWSIZE, S_AXI_AWBURST,axi4_awlock, + axi4_awcache, S_AXI_AWPROT, S_AXI_AWQOS + }), + .o_valid(M_AXI_AWVALID), .i_ready(M_AXI_AWREADY), + .o_data({ M_AXI_AWID, M_AXI_AWADDR, + M_AXI_AWLEN[3:0], + M_AXI_AWSIZE,M_AXI_AWBURST,M_AXI_AWLOCK, + M_AXI_AWCACHE,M_AXI_AWPROT, M_AXI_AWQOS + }) + ); + + assign M_AXI_AWLEN[7:4] = 4'h0; + assign S_AXI_AWREADY = awskd_ready && wid_reorder_awready; + // }}} + end else begin : IGN_AWCACHE + // {{{ + assign M_AXI_AWVALID = S_AXI_AWVALID && wid_reorder_awready; + assign S_AXI_AWREADY = M_AXI_AWREADY; + assign M_AXI_AWID = S_AXI_AWID; + assign M_AXI_AWADDR = S_AXI_AWADDR; + assign M_AXI_AWLEN = { 4'h0, S_AXI_AWLEN }; + assign M_AXI_AWSIZE = S_AXI_AWSIZE; + assign M_AXI_AWBURST = S_AXI_AWBURST; + assign M_AXI_AWLOCK = axi4_awlock; + assign M_AXI_AWCACHE = axi4_awcache; + assign M_AXI_AWPROT = S_AXI_AWPROT; + assign M_AXI_AWQOS = S_AXI_AWQOS; + + assign awskd_ready = 1; + // }}} + end endgenerate + // }}} + + // Handle write channel de-interleaving + // {{{ + axi3reorder #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .OPT_METHOD(OPT_REORDER_METHOD), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_LOW_LATENCY(OPT_LOW_LATENCY), + .LGAWFIFO(WID_LGAWFIFO), + .LGWFIFO(WID_LGWFIFO) + // }}} + ) widreorder ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), + // Incoming Write address ID + .S_AXI3_AWVALID(S_AXI_AWVALID && S_AXI_AWREADY), + .S_AXI3_AWREADY(wid_reorder_awready), + .S_AXI3_AWID(S_AXI_AWID), + // Incoming Write data info + .S_AXI3_WVALID(S_AXI_WVALID), + .S_AXI3_WREADY(S_AXI_WREADY), + .S_AXI3_WID(S_AXI_WID), + .S_AXI3_WDATA(S_AXI_WDATA), + .S_AXI3_WSTRB(S_AXI_WSTRB), + .S_AXI3_WLAST(S_AXI_WLAST), + // Outgoing write data channel + .M_AXI_WVALID(M_AXI_WVALID), + .M_AXI_WREADY(M_AXI_WREADY), + .M_AXI_WID(reordered_wid), + .M_AXI_WDATA(M_AXI_WDATA), + .M_AXI_WSTRB(M_AXI_WSTRB), + .M_AXI_WLAST(M_AXI_WLAST) + // }}} + ); + // }}} + + // Forward the B* channel return + // {{{ + assign S_AXI_BVALID = M_AXI_BVALID; + assign M_AXI_BREADY = S_AXI_BREADY; + assign S_AXI_BID = M_AXI_BID; + assign S_AXI_BRESP = M_AXI_BRESP; + // }}} + + // Read cache remapping + // {{{ + always @(*) + case(S_AXI_ARCACHE) + 4'b0110: axi4_arcache = 4'b1110; + 4'b0111: axi4_arcache = 4'b1111; + default: axi4_arcache = S_AXI_ARCACHE; + endcase + // }}} + + // ARLOCK + // {{{ + always @(*) + axi4_arlock = S_AXI_ARLOCK[0]; + // }}} + + // AR Skid buffer + // {{{ + generate if (OPT_TRANSFORM_AXCACHE) + begin : GEN_ARCACHE + // {{{ + skidbuffer #( + .DW(C_AXI_ADDR_WIDTH + C_AXI_ID_WIDTH + + 4 + 3 + 2 + 1+4+3+4), + .OPT_OUTREG(1'b1) + ) arskid ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data({ S_AXI_ARID, S_AXI_ARADDR, S_AXI_ARLEN, + S_AXI_ARSIZE, S_AXI_ARBURST, axi4_arlock, + axi4_arcache, S_AXI_ARPROT, S_AXI_ARQOS }), + .o_valid(M_AXI_ARVALID), .i_ready(M_AXI_ARREADY), + .o_data({ M_AXI_ARID, M_AXI_ARADDR, M_AXI_ARLEN[3:0], + M_AXI_ARSIZE, M_AXI_ARBURST, M_AXI_ARLOCK, + M_AXI_ARCACHE, M_AXI_ARPROT, M_AXI_ARQOS }) + ); + + assign M_AXI_ARLEN[7:4] = 4'h0; + // }}} + end else begin : IGN_ARCACHE + // {{{ + assign M_AXI_ARVALID = S_AXI_ARVALID; + assign S_AXI_ARREADY = M_AXI_ARREADY; + assign M_AXI_ARID = S_AXI_ARID; + assign M_AXI_ARADDR = S_AXI_ARADDR; + assign M_AXI_ARLEN = { 4'h0, S_AXI_ARLEN }; + assign M_AXI_ARSIZE = S_AXI_ARSIZE; + assign M_AXI_ARBURST = S_AXI_ARBURST; + assign M_AXI_ARLOCK = axi4_arlock; + assign M_AXI_ARCACHE = axi4_arcache; + assign M_AXI_ARPROT = S_AXI_ARPROT; + assign M_AXI_ARQOS = S_AXI_ARQOS; + // }}} + end endgenerate + // }}} + + // Forward the R* channel return + // {{{ + assign S_AXI_RVALID = M_AXI_RVALID; + assign M_AXI_RREADY = S_AXI_RREADY; + assign S_AXI_RID = M_AXI_RID; + assign S_AXI_RDATA = M_AXI_RDATA; + assign S_AXI_RLAST = M_AXI_RLAST; + assign S_AXI_RRESP = M_AXI_RRESP; + // }}} + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWLOCK[1], S_AXI_ARLOCK[1], + reordered_wid }; + // Verilator lint_on UNUSED + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL +// +// This design has not been formally verified. +// +`endif +endmodule diff --git a/rtl/wb2axip/axi3reorder.v b/rtl/wb2axip/axi3reorder.v new file mode 100644 index 0000000..3c92c0d --- /dev/null +++ b/rtl/wb2axip/axi3reorder.v @@ -0,0 +1,743 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi3reorder.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: One of the challenges of working with AXI3 are the (potentially) +// out of order writes. This modules is designed to re-order write +// beats back into the proper order given by the AW* channel. Once done, +// write beats will always be contiguous--only changing ID's after WLAST. +// Indeed, once done, the WID field can be properly removed and ignored. +// it's left within for forms sake, but isn't required. +// +// Algorithms: +// The design currently contains one of three reordering algorithms. +// +// MTHD_NONE Is a basic pass-through. This would be appropriate +// for AXI3 streams that are known to be in order already. +// +// MTHD_SHIFT_REGISTER Uses a shift-register based "FIFO". (Not block +// RAM) All write data goes into this FIFO. Items +// are read from this FIFO out of order as required for +// the given ID. When any item is read from the middle, +// the shift register back around the item read. +// +// This is a compromise implementation that uses less +// logic than the PER/ID FIFOS, while still allowing some +// amount of reordering to take place. +// +// MTHD_PERID_FIFOS Uses an explicit FIFO for each ID. Data come +// into the core and go directly into the FIFO's. +// Data are read out of the FIFOs in write-address order +// until WLAST, where the write address may (potentially) +// change. +// +// For a small number of IDs, this solution should be +// *complete*, and lacking nothing. (Unless you fill the +// write data FIFO's while needing data from another ID ..) +// +// Implementation notes: +// This module is intended to be used side by side with other AW* channel +// processing, and following an (external) AW* skidbuffer. For this +// reason, it doesn't use an AW skid buffer, nor does it output AW* +// information. External AWREADY handling should therefore be: +// +// master_awskid_read = reorder_awready && any_other_awready; +// +// going into a skid buffer, with the proper AWREADY being the upstream +// skidbuffer's AWREADY output. +// +// Expected performance: +// One beat per clock, all methods. +// +// Status: +// This module passes both a Verilator lint check and a 15-20 step formal +// bounded model check. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axi3reorder #( + // {{{ + parameter C_AXI_ID_WIDTH = 4, + parameter C_AXI_DATA_WIDTH = 32, + parameter LGAWFIFO = 3, // # packets we can handle + parameter LGWFIFO = 4, // Full size of an AXI burst + parameter OPT_METHOD = 0, + parameter [0:0] OPT_LOWPOWER = 0, + parameter [0:0] OPT_LOW_LATENCY = 0, + parameter NUM_FIFOS = (1<<C_AXI_ID_WIDTH) + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI3 (partial) slave interface + // {{{ + input wire S_AXI3_AWVALID, + output wire S_AXI3_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI3_AWID, + // input wire [AW-1:0] S_AXI3_AWADDR, + // input wire [3:0] S_AXI3_AWLEN, + // input wire [2:0] S_AXI3_AWSIZE, + // input wire [1:0] S_AXI3_AWBURST, + // input wire [1:0] S_AXI3_AWLOCK, + // input wire [3:0] S_AXI3_AWCACHE, + // input wire [2:0] S_AXI3_AWPROT, + // input wire [3:0] S_AXI3_AWQOS, + // + input wire S_AXI3_WVALID, + output wire S_AXI3_WREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI3_WID, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI3_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI3_WSTRB, + input wire S_AXI3_WLAST, + // }}} + // Reordered write data port. WID may now be discarded. + // {{{ + output reg M_AXI_WVALID, + input wire M_AXI_WREADY, + output reg [C_AXI_ID_WIDTH-1:0] M_AXI_WID, + output reg [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output reg [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output reg M_AXI_WLAST + // }}} + // }}} + ); + + // Register declarations + // {{{ + localparam MTHD_NONE = 0; + localparam MTHD_SHIFT_REGISTER = 1; + localparam MTHD_PERID_FIFOS = 2; + localparam IW = C_AXI_ID_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + wire awfifo_full, awfifo_empty; + wire read_awid_fifo; + wire [IW-1:0] awfifo_id; + wire [LGAWFIFO:0] awfifo_fill; + + genvar gk; + reg read_beat_fifo; + + reg cid_valid; + reg [IW-1:0] current_id; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write address ID's go to an address ID FIFO + // {{{ + //////////////////////////////////////////////////////////////////////// + // + generate if (OPT_METHOD == MTHD_NONE) + begin : IGNORE_AW_CHANNEL + // No write address ID's to keep track of + // {{{ + // These values are irrelevant. They are placeholders, put here + // to keep the linter from complaining. + + assign awfifo_id = S_AXI3_AWID; + assign S_AXI3_AWREADY = 1'b1; + assign awfifo_full = 0; + assign awfifo_fill = 0; + assign awfifo_empty = !S_AXI3_AWVALID; + always @(*) + cid_valid = S_AXI3_AWVALID; + always @(*) + current_id= S_AXI3_AWID; + assign read_awid_fifo = 0; + + // Verilator lint_off UNUSED + wire none_aw_unused; + assign none_aw_unused = &{ 1'b0, awfifo_full, awfifo_fill, + awfifo_empty, read_awid_fifo, awfifo_id }; + // }}} + end else begin : AWFIFO + //////////////////////////////////////////////////////////////// + // + // Write address ID FIFO + // {{{ + //////////////////////////////////////////////////////////////// + // + // + sfifo #( + // {{{ + .BW(IW), + .LGFLEN(LGAWFIFO), + .OPT_READ_ON_EMPTY(OPT_LOW_LATENCY) + // }}} + ) awidfifo( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(S_AXI3_AWVALID && S_AXI3_AWREADY), + .i_data(S_AXI3_AWID), + .o_full(awfifo_full), + .o_fill(awfifo_fill), + .i_rd(read_awid_fifo), + .o_data( awfifo_id ), + .o_empty(awfifo_empty) + // }}} + ); + + assign S_AXI3_AWREADY = !awfifo_full; + // }}} + //////////////////////////////////////////////////////////////// + // + // Current ID -- what ID shall we output next? + // {{{ + //////////////////////////////////////////////////////////////// + // + // The current ID is given by the write address channel + + initial cid_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cid_valid <= 1'b0; + else if (read_awid_fifo) + cid_valid <= !awfifo_empty; + + // The current ID is given by the write address channel + always @(posedge S_AXI_ACLK) + if (read_awid_fifo) + current_id <= awfifo_id; + + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write data channel processing and reordering + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_METHOD == MTHD_NONE) + begin : COPY_SW_TO_MW // Copy S_AXI3_W* values to M_AXIW* + // {{{ + // {{{ + always @(*) + begin + M_AXI_WVALID = S_AXI3_WVALID; + M_AXI_WID = S_AXI3_WID; + M_AXI_WDATA = S_AXI3_WDATA; + M_AXI_WSTRB = S_AXI3_WSTRB; + M_AXI_WLAST = S_AXI3_WLAST; + + read_beat_fifo = 0; + end + // }}} + + assign S_AXI3_WREADY = M_AXI_WREADY; + + // Keep Verilator happy + // Verilator lint_off UNUSED + wire none_unused; + assign none_unused = &{ 1'b0, S_AXI_ACLK, S_AXI_ARESETN, + current_id, cid_valid, read_beat_fifo }; + // Verilator lint_on UNUSED + // }}} + end else if (OPT_METHOD == MTHD_SHIFT_REGISTER) + begin : SHIFT_REGISTER + // {{{ + + //////////////////////////////////////////////////////////////// + // + // Local declarations + // {{{ + //////////////////////////////////////////////////////////////// + // + // + localparam NSREG = (1<<LGWFIFO); + reg [NSREG-1:0] sr_advance; + reg [NSREG-1:0] sr_write; + reg [NSREG-1:0] sr_valid; + reg [IW-1:0] sr_id [0:NSREG]; + reg [DW-1:0] sr_data [0:NSREG]; + reg [DW/8-1:0] sr_strb [0:NSREG]; + reg [NSREG-1:0] sr_last; + + integer ik; + // }}} + //////////////////////////////////////////////////////////////// + // + // Per-register-station logic + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + for (gk = 0; gk<NSREG; gk=gk+1) + begin + + // sr_advance + // {{{ + // Do we copy from the next station into this one or no? + always @(*) + begin + sr_advance[gk] = 0; + if ((gk > 0)&&(sr_advance[gk-1])) + sr_advance[gk] = 1; + if ((!M_AXI_WVALID || M_AXI_WREADY) + && cid_valid && current_id == sr_id[gk]) + sr_advance[gk] = 1; + if (!sr_valid[gk]) + sr_advance[gk] = 0; + end + // }}} + + // sw_write + // {{{ + // Do we write new data into this station? + always @(*) + begin + sr_write[gk] = S_AXI3_WVALID && S_AXI3_WREADY; + if (sr_valid[gk] && (!sr_advance[gk] + ||(gk < NSREG-1 && sr_valid[gk+1]))) + sr_write[gk] = 0; + if (gk > 0 && (!sr_valid[gk-1] + || (sr_advance[gk-1] && !sr_valid[gk]))) + sr_write[gk] = 0; + end + // }}} + + // sr_valid + // {{{ + initial sr_valid[gk] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + sr_valid[gk] <= 0; + else if (sr_write[gk]) + sr_valid[gk] <= 1; + else if (sr_advance[gk] && gk<NSREG-1) + sr_valid[gk] <= sr_valid[gk+1]; + else if (sr_advance[gk]) + sr_valid[gk] <= 0; + // }}} + + // sr_id, sr_data, sr_strb, sr_last + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + sr_id[gk] <= 0; + sr_data[gk] <= 0; + sr_strb[gk] <= 0; + sr_last[gk] <= 0; + end else if (sr_write[gk]) + begin + sr_id[gk] <= S_AXI3_WID; + sr_data[gk] <= S_AXI3_WDATA; + sr_strb[gk] <= S_AXI3_WSTRB; + sr_last[gk] <= S_AXI3_WLAST; + end else if ((gk < NSREG-1) && sr_advance[gk]) + begin + sr_id[gk] <= sr_id[gk+1]; + sr_data[gk] <= sr_data[gk+1]; + sr_strb[gk] <= sr_strb[gk+1]; + sr_last[gk] <= sr_last[gk+1]; + + if (OPT_LOWPOWER && gk < NSREG-1 && !sr_valid[gk+1]) + begin + // If we are running in low power, + // Keep every unused register == 0 + sr_id[gk] <= 0; + sr_data[gk] <= 0; + sr_strb[gk] <= 0; + sr_last[gk] <= 0; + end + end else if ((!OPT_LOWPOWER && !sr_valid[gk]) + || sr_write[gk]) + begin + sr_id[gk] <= S_AXI3_WID; + sr_data[gk] <= S_AXI3_WDATA; + sr_strb[gk] <= S_AXI3_WSTRB; + sr_last[gk] <= S_AXI3_WLAST; + end + // }}} + end + // }}} + //////////////////////////////////////////////////////////////// + // + // Pick a register location to output + // {{{ + //////////////////////////////////////////////////////////////// + // + reg known_next; + reg [LGWFIFO-1:0] sr_next; + always @(*) + begin + known_next = 0; + sr_next = -1; + for(ik=0; ik<NSREG; ik=ik+1) + if (!known_next && current_id == sr_id[ik]) + begin + sr_next = ik[LGWFIFO-1:0]; + known_next = sr_valid[ik]; + end + + if (!cid_valid) + known_next = 0; + end + + assign S_AXI3_WREADY = !sr_valid[NSREG-1]; + // }}} + //////////////////////////////////////////////////////////////// + // + // + // {{{ + //////////////////////////////////////////////////////////////// + // + // + assign read_awid_fifo = (!M_AXI_WVALID || M_AXI_WREADY) + && (!cid_valid + || (known_next && sr_last[sr_next])); + + always @(*) + read_beat_fifo = (!M_AXI_WVALID || M_AXI_WREADY) + &&(known_next); + + initial M_AXI_WVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXI_WVALID <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + M_AXI_WVALID <= known_next; + + initial M_AXI_WID = 0; + initial M_AXI_WDATA = 0; + initial M_AXI_WSTRB = 0; + initial M_AXI_WLAST = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + // {{{ + M_AXI_WID <= 0; + M_AXI_WDATA <= 0; + M_AXI_WSTRB <= 0; + M_AXI_WLAST <= 0; + // }}} + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (OPT_LOWPOWER && !known_next) + begin + // {{{ + M_AXI_WID <= 0; + M_AXI_WDATA <= 0; + M_AXI_WSTRB <= 0; + M_AXI_WLAST <= 0; + // }}} + end else begin + // {{{ + M_AXI_WID <= current_id; + // Verilator lint_off WIDTH + M_AXI_WDATA <= sr_data[sr_next]; + M_AXI_WSTRB <= sr_strb[sr_next]; + M_AXI_WLAST <= sr_last[sr_next]; + // Verilator lint_on WIDTH + // }}} + end + end + // }}} + // Verilator lint_off UNUSED + wire unused_sreg; + assign unused_sreg = &{ 1'b0, read_beat_fifo }; + // Verilator lint_on UNUSED +`ifdef FORMAL + always @(*) + if (S_AXI3_WVALID && S_AXI3_WREADY) + begin + assert($onehot(sr_write)); + end else if (S_AXI_ARESETN) + assert(sr_write == 0); + + always @(*) + assert(((sr_write << 1) & sr_valid) == 0); + + reg cvr_sreg_full; + + initial cvr_sreg_full = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_sreg_full <= 0; + else if (!S_AXI3_WREADY) + cvr_sreg_full <= 1; + + always @(*) + cover(cvr_sreg_full && sr_valid == 0); + +`endif + // }}} + end else if (OPT_METHOD == MTHD_PERID_FIFOS) + begin : MULTIPLE_FIFOS + // {{{ + wire [NUM_FIFOS-1:0] wbfifo_full, wbfifo_empty, wbfifo_last; + // wire [IW-1:0] wbfifo_id [0:NUM_FIFOS-1]; + wire [DW-1:0] wbfifo_data [0:NUM_FIFOS-1]; + wire [DW/8-1:0] wbfifo_strb [0:NUM_FIFOS-1]; + + //////////////////////////////////////////////////////////////// + // + // The write beat FIFO + //////////////////////////////////////////////////////////////// + // {{{ + // + // Every write beat goes into a separate FIFO based on its ID + // + for (gk=0; gk < NUM_FIFOS; gk=gk+1) + begin + // {{{ + wire [LGWFIFO:0] wbfifo_fill; + + sfifo #( + .BW(DW+(DW/8)+1), + .LGFLEN(LGWFIFO), + .OPT_READ_ON_EMPTY(OPT_LOW_LATENCY) + ) write_beat_fifo ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(S_AXI3_WVALID && S_AXI3_WREADY && S_AXI3_WID == gk), + .i_data({ S_AXI3_WDATA, S_AXI3_WSTRB, + S_AXI3_WLAST }), + .o_full(wbfifo_full[gk]), .o_fill(wbfifo_fill), + .i_rd(read_beat_fifo && current_id == gk), + .o_data({ wbfifo_data[gk], + wbfifo_strb[gk], wbfifo_last[gk] }), + .o_empty(wbfifo_empty[gk]) + ); + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, wbfifo_fill }; + // Verilator lint_on UNUSED + // }}} + end + + assign S_AXI3_WREADY = !wbfifo_full[S_AXI3_WID]; + // }}} + //////////////////////////////////////////////////////////////// + // + // Outgoing write data channel + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + assign read_awid_fifo = (!M_AXI_WVALID || M_AXI_WREADY) + && (!cid_valid + || (read_beat_fifo && wbfifo_last[current_id])); + // Write packets associated with the current ID can move forward + always @(*) + read_beat_fifo = (!M_AXI_WVALID || M_AXI_WREADY) + &&(cid_valid)&&(!wbfifo_empty[current_id]); + + initial M_AXI_WVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXI_WVALID <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + M_AXI_WVALID <= read_beat_fifo; + + initial M_AXI_WID = 0; + initial M_AXI_WDATA = 0; + initial M_AXI_WSTRB = 0; + initial M_AXI_WLAST = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + // {{{ + M_AXI_WID <= 0; + M_AXI_WDATA <= 0; + M_AXI_WSTRB <= 0; + M_AXI_WLAST <= 0; + // }}} + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + // {{{ + M_AXI_WID <= current_id; + M_AXI_WDATA <= wbfifo_data[current_id]; + M_AXI_WSTRB <= wbfifo_strb[current_id]; + M_AXI_WLAST <= wbfifo_last[current_id]; + + if (OPT_LOWPOWER && !read_beat_fifo) + begin + // {{{ + M_AXI_WID <= 0; + M_AXI_WDATA <= 0; + M_AXI_WSTRB <= 0; + M_AXI_WLAST <= 0; + // }}} + end + // }}} + end + // }}} + // }}} + end endgenerate + // }}} + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, awfifo_fill }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + reg [LGAWFIFO+LGWFIFO-1:0] f_awid_count; + (* anyconst *) reg [IW-1:0] f_const_id; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assume(!S_AXI3_AWVALID); + assume(!S_AXI3_WVALID); + + assume(!M_AXI_WVALID); + end else begin + if ($past(S_AXI3_AWVALID && !S_AXI3_AWREADY)) + begin + assume(S_AXI3_AWVALID); + assume($stable(S_AXI3_AWID)); + end + + if ($past(S_AXI3_WVALID && !S_AXI3_WREADY)) + begin + assume(S_AXI3_WVALID); + assume($stable(S_AXI3_WID)); + assume($stable(S_AXI3_WDATA)); + assume($stable(S_AXI3_WSTRB)); + assume($stable(S_AXI3_WLAST)); + end + + if ($past(M_AXI_WVALID && !M_AXI_WREADY)) + begin + assert(M_AXI_WVALID); + assert($stable(M_AXI_WID)); + assert($stable(M_AXI_WDATA)); + assert($stable(M_AXI_WSTRB)); + assert($stable(M_AXI_WLAST)); + end + end + + generate if (OPT_METHOD != MTHD_NONE) + begin : F_FIFO_CHECK + wire [LGWFIFO:0] f_ckfifo_fill; + wire [LGWFIFO:0] f_ckfifo_full, f_ckfifo_empty; + wire [IW-1:0] f_ckfifo_id; + wire [DW-1:0] f_ckfifo_data; + wire [DW/8-1:0] f_ckfifo_strb; + wire f_ckfifo_last; + + sfifo #( + .BW(IW + DW + (DW/8) + 1), + .LGFLEN(LGWFIFO), + .OPT_READ_ON_EMPTY(OPT_LOW_LATENCY) + ) f_checkfifo ( + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(S_AXI3_WVALID && S_AXI3_WREADY + && S_AXI3_WID == f_const_id), + .i_data({ S_AXI3_WID, S_AXI3_WDATA, S_AXI3_WSTRB, S_AXI3_WLAST }), + .o_full(f_ckfifo_full), .o_fill(f_ckfifo_fill), + .i_rd(M_AXI_WVALID && M_AXI_WREADY + && M_AXI_WID == f_const_id), + .o_data({ f_ckfifo_id, f_ckfifo_data, f_ckfifo_strb, + f_ckfifo_last }), + .o_empty(f_ckfifo_empty) + ); + + always @(*) + if (S_AXI_ARESETN && M_AXI_WVALID && M_AXI_WID == f_const_id) + begin + assert(!f_ckfifo_empty); + assert(f_ckfifo_id == M_AXI_WID); + assert(f_ckfifo_data == M_AXI_WDATA); + assert(f_ckfifo_strb == M_AXI_WSTRB); + assert(f_ckfifo_last == M_AXI_WLAST); + end + end endgenerate + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_awid_count = 0; + else case({S_AXI3_AWVALID&& S_AXI3_AWREADY && S_AXI3_AWID == f_const_id, + M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST + && M_AXI_WID == f_const_id }) + 2'b01: f_awid_count <= f_awid_count - 1; + 2'b10: f_awid_count <= f_awid_count + 1; + default begin end + endcase + + always @(*) + if (M_AXI_WVALID && M_AXI_WID == f_const_id) + assert(f_awid_count > 0); + + generate if (OPT_LOWPOWER) + begin : F_LOWPOWER_CHECK + always @(*) + if (!S_AXI3_AWVALID) + assume(S_AXI3_AWID == 0); + + always @(*) + if (!S_AXI3_WVALID) + begin + assume(S_AXI3_WID == 0); + assume(S_AXI3_WDATA == 0); + assume(S_AXI3_WSTRB == 0); + assume(S_AXI3_WLAST == 0); + end + + always @(*) + if (!M_AXI_WVALID) + begin + assert(M_AXI_WID == 0); + assert(M_AXI_WDATA == 0); + assert(M_AXI_WSTRB == 0); + assert(M_AXI_WLAST == 0); + end + + end endgenerate + +`endif + // }}} +endmodule diff --git a/rtl/wb2axip/axi_addr.v b/rtl/wb2axip/axi_addr.v new file mode 100644 index 0000000..8d8ac75 --- /dev/null +++ b/rtl/wb2axip/axi_addr.v @@ -0,0 +1,235 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axi_addr.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: The AXI (full) standard has some rather complicated addressing +// modes, where the address can either be FIXED, INCRementing, or +// even where it can WRAP around some boundary. When in either INCR or +// WRAP modes, the next address must always be aligned. In WRAP mode, +// the next address calculation needs to wrap around a given value, and +// that value is dependent upon the burst size (i.e. bytes per beat) and +// length (total numbers of beats). Since this calculation can be +// non-trivial, and since it needs to be done multiple times, the logic +// below captures it for every time it might be needed. +// +// 20200918 - modified to accommodate (potential) AXI3 burst lengths +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axi_addr #( + // {{{ + parameter AW = 32, + DW = 32, + // parameter [0:0] OPT_AXI3 = 1'b0, + localparam LENB = 8 + // }}} + ) ( + // {{{ + input wire [AW-1:0] i_last_addr, + input wire [2:0] i_size, // 1b, 2b, 4b, 8b, etc + input wire [1:0] i_burst, // fixed, incr, wrap, reserved + input wire [LENB-1:0] i_len, + output wire [AW-1:0] o_next_addr + // }}} + ); + + // Parameter/register declarations + // {{{ + localparam DSZ = $clog2(DW)-3; + localparam [1:0] FIXED = 2'b00; + // localparam [1:0] INCREMENT = 2'b01; + // localparam [1:0] WRAP = 2'b10; + localparam IN_AW = (AW >= 12) ? 12 : AW; + localparam [IN_AW-1:0] ONE = 1; + + reg [IN_AW-1:0] wrap_mask, increment; + reg [IN_AW-1:0] crossblk_addr, aligned_addr, unaligned_addr; + // }}} + + // Address increment + // {{{ + always @(*) + if (DSZ == 0) + increment = 1; + else if (DSZ == 1) + increment = (i_size[0]) ? 2 : 1; + else if (DSZ == 2) + increment = (i_size[1]) ? 4 : ((i_size[0]) ? 2 : 1); + else if (DSZ == 3) + case(i_size[1:0]) + 2'b00: increment = 1; + 2'b01: increment = 2; + 2'b10: increment = 4; + 2'b11: increment = 8; + endcase + else + increment = (1<<i_size); + // }}} + + // wrap_mask + // {{{ + // The wrap_mask is used to determine which bits remain stable across + // the burst, and which are allowed to change. It is only used during + // wrapped addressing. + always @(*) + begin + // Start with the default, minimum mask + + /* + // Here's the original code. It works, but it's + // not economical (uses too many LUTs) + // + if (i_len[3:0] == 1) + wrap_mask = (1<<(i_size+1)); + else if (i_len[3:0] == 3) + wrap_mask = (1<<(i_size+2)); + else if (i_len[3:0] == 7) + wrap_mask = (1<<(i_size+3)); + else if (i_len[3:0] == 15) + wrap_mask = (1<<(i_size+4)); + wrap_mask = wrap_mask - 1; + */ + + // Here's what we *want* + // + // wrap_mask[i_size:0] = -1; + // + // On the other hand, since we're already guaranteed that our + // addresses are aligned, do we really care about + // wrap_mask[i_size-1:0] ? + + // What we want: + // + // wrap_mask[i_size+3:i_size] |= i_len[3:0] + // + // We could simplify this to + // + // wrap_mask = wrap_mask | (i_len[3:0] << (i_size)); + // Verilator lint_off WIDTH + if (DSZ < 2) + wrap_mask = ONE | ({{(IN_AW-4){1'b0}},i_len[3:0]} << (i_size[0])); + else if (DSZ < 4) + wrap_mask = ONE | ({{(IN_AW-4){1'b0}},i_len[3:0]} << (i_size[1:0])); + else + wrap_mask = ONE | ({{(IN_AW-4){1'b0}},i_len[3:0]} << (i_size)); + // Verilator lint_on WIDTH + end + // }}} + + // unaligned_addr + always @(*) + unaligned_addr = i_last_addr[IN_AW-1:0] + increment[IN_AW-1:0]; + + // aligned_addr + // {{{ + always @(*) + if (i_burst != FIXED) + begin + // Align subsequent beats in any burst + // {{{ + aligned_addr = unaligned_addr; + // We use the bus size here to simplify the logic + // required in case the bus is smaller than the + // maximum. This depends upon AxSIZE being less than + // $clog2(DATA_WIDTH/8). + if (DSZ < 2) + begin + // {{{ + // Align any subsequent address + if (i_size[0]) + aligned_addr[0] = 0; + // }}} + end else if (DSZ < 4) + begin + // {{{ + // Align any subsequent address + case(i_size[1:0]) + 2'b00: aligned_addr = unaligned_addr; + 2'b01: aligned_addr[ 0] = 0; + 2'b10: aligned_addr[(AW-1>1) ? 1 : (AW-1):0]= 0; + 2'b11: aligned_addr[(AW-1>2) ? 2 : (AW-1):0]= 0; + endcase + // }}} + end else begin + // {{{ + // Align any subsequent address + case(i_size) + 3'b001: aligned_addr[ 0] = 0; + 3'b010: aligned_addr[(AW-1>1) ? 1 : (AW-1):0]=0; + 3'b011: aligned_addr[(AW-1>2) ? 2 : (AW-1):0]=0; + 3'b100: aligned_addr[(AW-1>3) ? 3 : (AW-1):0]=0; + 3'b101: aligned_addr[(AW-1>4) ? 4 : (AW-1):0]=0; + 3'b110: aligned_addr[(AW-1>5) ? 5 : (AW-1):0]=0; + 3'b111: aligned_addr[(AW-1>6) ? 6 : (AW-1):0]=0; + default: aligned_addr = unaligned_addr; + endcase + // }}} + end + // }}} + end else + aligned_addr = i_last_addr[IN_AW-1:0]; + // }}} + + // crossblk_addr from aligned_addr, for WRAP addressing + // {{{ + always @(*) + if (i_burst[1]) + begin + // WRAP! + crossblk_addr[IN_AW-1:0] = (i_last_addr[IN_AW-1:0] & ~wrap_mask) + | (aligned_addr & wrap_mask); + end else + crossblk_addr[IN_AW-1:0] = aligned_addr; + // }}} + + // o_next_addr: Guarantee only the bottom 12 bits change + // {{{ + // This is really a logic simplification. AXI bursts aren't allowed + // to cross 4kB boundaries. Given that's the case, we don't have to + // suffer from the propagation across all AW bits, and can limit any + // address propagation to just the lower 12 bits + generate if (AW > 12) + begin : WIDE_ADDRESS + assign o_next_addr = { i_last_addr[AW-1:12], + crossblk_addr[11:0] }; + end else begin : NARROW_ADDRESS + assign o_next_addr = crossblk_addr[AW-1:0]; + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = (LENB <= 4) ? &{1'b0, i_len[0] } + : &{ 1'b0, i_len[LENB-1:4], i_len[0] }; + // Verilator lint_on UNUSED + // }}} +endmodule diff --git a/rtl/wb2axip/axidma.v b/rtl/wb2axip/axidma.v new file mode 100644 index 0000000..46b1e69 --- /dev/null +++ b/rtl/wb2axip/axidma.v @@ -0,0 +1,2977 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axidma.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: To move memory from one location to another, at high speed. +// This is not a memory to stream, nor a stream to memory core, +// but rather a memory to memory core. +// +// +// Registers: +// +// 0. Control +// 8b KEY +// 3'b PROT +// 4'b QOS +// 1b Abort: Either aborting or aborted +// 1b Err: Ended on an error +// 1b Busy +// 1b Interrupt Enable +// 1b Interrupt Clear +// 1b Start +// 1. Unused +// 2-3. Source address, low and then high 64-bit words +// (Last value read address) +// 4-5. Destination address, low and then high 64-bit words +// (Next value to write address) +// 6-7. Length, low and then high words +// (Total number minus successful writes) +// +// Performance goals: +// 100% throughput +// Stay off the bus until you can drive it hard +// Other goals: +// Be both AXI3 and AXI4 capable +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// `define AXI3 +// }}} +module axidma #( + // {{{ + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + // + // These two "parameters" really aren't things that can be + // changed externally. They control the size of the AXI4-lite + // port. Internally, it's defined to have 8, 32-bit registers. + // The registers are configured wide enough to support 64-bit + // AXI addressing. Similarly, the AXI-lite data width is fixed + // at 32-bits. + localparam C_AXIL_ADDR_WIDTH = 5, + localparam C_AXIL_DATA_WIDTH = 32, + // + // OPT_UNALIGNED turns on support for unaligned addresses, + // whether source, destination, or length parameters. + parameter [0:0] OPT_UNALIGNED = 1'b1, + // + // OPT_WRAPMEM controls what happens if the transfer runs off + // of the end of memory. If set, the transfer will continue + // again from the beginning of memory. If clear, the transfer + // will be aborted with an error if either read or write + // address ever get this far. + parameter [0:0] OPT_WRAPMEM = 1'b1, + // + // LGMAXBURST controls the size of the maximum burst produced + // by this core. Specifically, its the log (based 2) of that + // maximum size. Hence, for AXI4, this size must be 8 + // (i.e. 2^8 or 256 beats) or less. For AXI3, the size must + // be 4 or less. Tests have verified performance for + // LGMAXBURST as low as 2. While I expect it to fail at + // LGMAXBURST=0, I haven't verified at what value this burst + // parameter is too small. +`ifdef AXI3 + parameter LGMAXBURST=4, // 16 beats max +`else + parameter LGMAXBURST=8, // 256 beats +`endif + // LGFIFO: This is the (log-based-2) size of the internal FIFO. + // Hence if LGFIFO=8, the internal FIFO will have 256 elements + // (words) in it. High throughput transfers are accomplished + // by first storing data into a FIFO, then once a full burst + // size is available bursting that data over the bus. In + // order to be able to keep receiving data while bursting it + // out, the FIFO size must be at least twice the size of the + // maximum burst size. Larger sizes are possible as well. + parameter LGFIFO = LGMAXBURST+1, // 512 element FIFO + // + // LGLEN: specifies the number of bits in the transfer length + // register. If a transfer cannot be specified in LGLEN bits, + // it won't happen. LGLEN must be less than or equal to the + // address width. + parameter LGLEN = C_AXI_ADDR_WIDTH, + // + // OPT_LOWPOWER: + parameter [0:0] OPT_LOWPOWER = 1'b0, + // + // OPT_CLKGATE: + parameter [0:0] OPT_CLKGATE = OPT_LOWPOWER, + // + // AXI uses ID's to transfer information. This core rather + // ignores them. Instead, it uses a constant ID for all + // transfers. The following two parameters control that ID. + parameter [C_AXI_ID_WIDTH-1:0] AXI_READ_ID = 0, + parameter [C_AXI_ID_WIDTH-1:0] AXI_WRITE_ID = 0, + // + // The "ABORT_KEY" is a byte that, if written to the control + // word while the core is running, will cause the data transfer + // to be aborted. + parameter [7:0] ABORT_KEY = 8'h6d, + // + localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3, + localparam AXILLSB= $clog2(C_AXIL_DATA_WIDTH)-3, + localparam LGLENW= LGLEN-ADDRLSB + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI low-power interface + // {{{ + // This has been removed, due to a lack of definition from the + // AXI standard for these wires. + // }}} + // + // The AXI4-lite control interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output reg S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output reg S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output reg [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // The AXI Master (DMA) interface + // {{{ + output reg M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output reg [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output reg [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, +`ifdef AXI3 + output reg [3:0] M_AXI_AWLEN, +`else + output reg [7:0] M_AXI_AWLEN, +`endif + output reg [2:0] M_AXI_AWSIZE, + output reg [1:0] M_AXI_AWBURST, + output reg M_AXI_AWLOCK, + output reg [3:0] M_AXI_AWCACHE, + output reg [2:0] M_AXI_AWPROT, + output reg [3:0] M_AXI_AWQOS, + // + // + output reg M_AXI_WVALID, + input wire M_AXI_WREADY, +`ifdef AXI3 + output reg [C_AXI_ID_WIDTH-1:0] M_AXI_WID, +`endif + output reg [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output reg [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output reg M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + output reg M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // + output reg M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output reg [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, +`ifdef AXI3 + output reg [3:0] M_AXI_ARLEN, +`else + output reg [7:0] M_AXI_ARLEN, +`endif + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP, + // }}} + output reg o_int + // }}} + ); + + // Local declarations + // {{{ + // The number of beats in this maximum burst size is + // automatically determined from LGMAXBURST, and so its + // forced to be a power of two this way. + localparam MAXBURST=(1<<LGMAXBURST); + // + localparam [2:0] CTRL_ADDR = 3'b000, + // UNUSED_ADDR = 3'b001, + SRCLO_ADDR = 3'b010, + SRCHI_ADDR = 3'b011, + DSTLO_ADDR = 3'b100, + DSTHI_ADDR = 3'b101, + LENLO_ADDR = 3'b110, + LENHI_ADDR = 3'b111; + localparam CTRL_START_BIT = 0, + CTRL_BUSY_BIT = 0, + CTRL_INT_BIT = 1, + CTRL_INTEN_BIT = 2, + CTRL_ABORT_BIT = 3, + CTRL_ERR_BIT = 4; + localparam [1:0] AXI_INCR = 2'b01, AXI_OKAY = 2'b00; + + wire gated_clk, clk_active; + wire i_clk = gated_clk; + wire i_reset = !S_AXI_ARESETN; + + reg axil_write_ready, axil_read_ready; + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_src, wide_dst, wide_len; + reg [2*C_AXIL_DATA_WIDTH-1:0] new_widesrc, new_widedst, new_widelen; + + reg r_busy, r_err, r_abort, w_start, r_int, r_int_enable, + r_done, last_write_ack, zero_len; + reg [3:0] r_qos; + reg [2:0] r_prot; + reg [LGLEN-1:0] r_len; // Length of transfer in octets + reg [C_AXI_ADDR_WIDTH-1:0] r_src_addr, r_dst_addr; + + reg fifo_reset; + wire [LGFIFO:0] fifo_fill; + reg [LGFIFO:0] fifo_space_available; + reg [LGFIFO:0] fifo_data_available, + next_fifo_data_available; + wire fifo_full, fifo_empty; + reg [8:0] write_count; + // + reg phantom_read, w_start_read, + no_read_bursts_outstanding; + reg [LGLEN:0] readlen_b; + reg [LGLENW:0] readlen_w, initial_readlen_w; + reg [C_AXI_ADDR_WIDTH:0] read_address; + reg [LGLENW:0] reads_remaining_w, + read_beats_remaining_w, + read_bursts_outstanding; + reg [C_AXI_ADDR_WIDTH-1:0] read_distance_to_boundary_b; + reg reads_remaining_nonzero; + // + reg phantom_write, w_write_start; + reg [C_AXI_ADDR_WIDTH:0] write_address; + reg [LGLENW:0] writes_remaining_w, + write_bursts_outstanding; + reg [LGLENW:0] write_burst_length; + reg write_requests_remaining; + reg [LGLEN:0] writelen_b; + reg [LGLENW:0] write_beats_remaining; + + wire awskd_valid; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + + wire arskd_valid; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + // + reg r_partial_in_valid; + reg r_write_fifo, r_read_fifo; + reg r_partial_outvalid; + reg [C_AXI_DATA_WIDTH/8-1:0] r_first_wstrb, + r_last_wstrb; + reg extra_realignment_write, + extra_realignment_read; + reg [2*ADDRLSB+2:0] write_realignment; + reg last_read_beat; + reg clear_read_pipeline; + reg last_write_burst; + + // + // Push some write length calculations across clocks + reg [LGLENW:0] w_writes_remaining_w; + reg multiple_write_bursts_remaining, + first_write_burst; + reg [LGMAXBURST:0] initial_write_distance_to_boundary_w, + first_write_len_w; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-Lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite control write interface + // {{{ + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WSTRB, S_AXIL_WDATA }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_strb, wskd_data })); + + always @(*) + begin + axil_write_ready = !S_AXIL_BVALID || S_AXIL_BREADY; + if (!awskd_valid || !wskd_valid) + axil_write_ready = 0; + if (!clk_active) + axil_write_ready = 0; + end + + initial S_AXIL_BVALID = 1'b0; + always @(posedge i_clk) + if (i_reset) + S_AXIL_BVALID <= 1'b0; + else if (!S_AXIL_BVALID || S_AXIL_BREADY) + S_AXIL_BVALID <= axil_write_ready; + + assign S_AXIL_BRESP = AXI_OKAY; + + always @(*) + begin + w_start = !r_busy && axil_write_ready && wskd_strb[0] + && wskd_data[CTRL_START_BIT] + && (awskd_addr == CTRL_ADDR); + if (r_err && (!wskd_strb[0] || !wskd_data[CTRL_ERR_BIT])) + w_start = 0; + if (zero_len) + w_start = 0; + end + + always @(posedge i_clk) + if (i_reset) + r_err <= 1'b0; + else if (!r_busy && axil_write_ready) + r_err <= (r_err) && (!wskd_strb[0] || !wskd_data[CTRL_ERR_BIT]); + else if (r_busy) + begin + if (M_AXI_BVALID && M_AXI_BRESP[1]) + r_err <= 1'b1; + if (M_AXI_RVALID && M_AXI_RRESP[1]) + r_err <= 1'b1; + + if (!OPT_WRAPMEM && write_address[C_AXI_ADDR_WIDTH] + && write_requests_remaining) + r_err <= 1'b1; + if (!OPT_WRAPMEM && read_address[C_AXI_ADDR_WIDTH] + && reads_remaining_nonzero) + r_err <= 1'b1; + end + + initial r_busy = 1'b0; + always @(posedge i_clk) + if (i_reset) + r_busy <= 1'b0; + else if (!r_busy && axil_write_ready) + r_busy <= w_start; + else if (r_busy) + begin + if (M_AXI_BVALID && M_AXI_BREADY && last_write_ack) + r_busy <= 1'b0; + if (r_done) + r_busy <= 1'b0; + end + + always @(posedge i_clk) + if (i_reset || !r_int_enable || !r_busy) + o_int <= 0; + else if (r_done) + o_int <= 1'b1; + else + o_int <= (last_write_ack && M_AXI_BVALID && M_AXI_BREADY); + + always @(posedge i_clk) + if (i_reset) + r_int <= 0; + else if (!r_busy) + begin + if (axil_write_ready && awskd_addr == CTRL_ADDR + && wskd_strb[0]) + begin + if (wskd_data[CTRL_START_BIT]) + r_int <= 0; + else if (wskd_data[CTRL_INT_BIT]) + r_int <= 0; + end + end else if (r_done) + r_int <= 1'b1; + else + r_int <= (last_write_ack && M_AXI_BVALID && M_AXI_BREADY); + + initial r_abort = 0; + always @(posedge i_clk) + if (i_reset) + r_abort <= 1'b0; + else if (!r_busy) + begin + if (axil_write_ready && awskd_addr == CTRL_ADDR + && wskd_strb[0]) + begin + if(wskd_data[CTRL_START_BIT] + ||wskd_data[CTRL_ABORT_BIT] + ||wskd_data[CTRL_ERR_BIT]) + r_abort <= 0; + end + end else if (!r_abort) + r_abort <= (axil_write_ready && awskd_addr == CTRL_ADDR) + &&(wskd_strb[3] && wskd_data[31:24] == ABORT_KEY); + + wire [C_AXIL_DATA_WIDTH-1:0] newsrclo, newsrchi, + newdstlo, newdsthi, newlenlo, newlenhi; + + always @(*) + begin + wide_src = 0; + wide_dst = 0; + wide_len = 0; + + wide_src[C_AXI_ADDR_WIDTH-1:0] = r_src_addr; + wide_dst[C_AXI_ADDR_WIDTH-1:0] = r_dst_addr; + wide_len[LGLEN-1:0] = r_len; + + if (!OPT_UNALIGNED) + begin + wide_src[ADDRLSB-1:0] = 0; + wide_dst[ADDRLSB-1:0] = 0; + wide_len[ADDRLSB-1:0] = 0; + end + end + + assign newsrclo = apply_wstrb( + wide_src[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + assign newsrchi = apply_wstrb( + wide_src[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + assign newdstlo = apply_wstrb( + wide_dst[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + assign newdsthi = apply_wstrb( + wide_dst[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + assign newlenlo = apply_wstrb( + wide_len[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + assign newlenhi = apply_wstrb( + wide_len[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + + always @(*) + begin + new_widesrc = wide_src; + new_widedst = wide_dst; + new_widelen = wide_len; + + if (!awskd_addr[0]) + begin + new_widesrc[C_AXIL_DATA_WIDTH-1:0] = newsrclo; + new_widedst[C_AXIL_DATA_WIDTH-1:0] = newdstlo; + new_widelen[C_AXIL_DATA_WIDTH-1:0] = newlenlo; + end else begin + new_widesrc[2*C_AXIL_DATA_WIDTH-1 + :C_AXIL_DATA_WIDTH] = newsrchi; + new_widedst[2*C_AXIL_DATA_WIDTH-1 + :C_AXIL_DATA_WIDTH] = newdsthi; + new_widelen[2*C_AXIL_DATA_WIDTH-1 + :C_AXIL_DATA_WIDTH] = newlenhi; + end + + new_widesrc[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + new_widedst[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + new_widelen[2*C_AXIL_DATA_WIDTH-1:LGLEN] = 0; + + if (!OPT_UNALIGNED) + begin + new_widesrc[ADDRLSB-1:0] = 0; + new_widedst[ADDRLSB-1:0] = 0; + new_widelen[ADDRLSB-1:0] = 0; + end + end + + initial r_len = 0; + initial zero_len = 1; + initial r_src_addr = 0; + initial r_dst_addr = 0; + always @(posedge i_clk) + if (i_reset) + begin + // {{{ + r_len <= 0; + zero_len <= 1; + r_prot <= 0; + r_qos <= 0; + r_src_addr <= 0; + r_dst_addr <= 0; + r_int_enable <= 0; + // }}} + end else if (!r_busy && axil_write_ready) + begin + // {{{ + case(awskd_addr) + CTRL_ADDR: begin + if (wskd_strb[2]) + begin + r_prot <= wskd_data[22:20]; + r_qos <= wskd_data[19:16]; + end + if (wskd_strb[0]) + r_int_enable <= wskd_data[CTRL_INTEN_BIT]; + end + SRCLO_ADDR: begin + r_src_addr <= new_widesrc[C_AXI_ADDR_WIDTH-1:0]; + end + SRCHI_ADDR: if (C_AXI_ADDR_WIDTH > C_AXIL_DATA_WIDTH) begin + r_src_addr <= new_widesrc[C_AXI_ADDR_WIDTH-1:0]; + end + DSTLO_ADDR: begin + r_dst_addr <= new_widedst[C_AXI_ADDR_WIDTH-1:0]; + end + DSTHI_ADDR: if (C_AXI_ADDR_WIDTH > C_AXIL_DATA_WIDTH) begin + r_dst_addr <= new_widedst[C_AXI_ADDR_WIDTH-1:0]; + end + LENLO_ADDR: begin + r_len <= new_widelen[LGLEN-1:0]; + zero_len <= (new_widelen == 0); + end + LENHI_ADDR: if (LGLEN > C_AXIL_DATA_WIDTH) begin + r_len <= new_widelen[LGLEN-1:0]; + zero_len <= (new_widelen == 0); + end + default: begin end + endcase + // }}} + end else if (r_busy) + begin + // {{{ + r_dst_addr <= write_address[C_AXI_ADDR_WIDTH-1:0]; + if (writes_remaining_w[LGLENW]) + r_len <= -1; + else + r_len <= { writes_remaining_w[LGLENW-1:0], + {(ADDRLSB){1'b0}} }; + if (OPT_UNALIGNED) + r_len[ADDRLSB-1:0] <= 0; + + zero_len <= (writes_remaining_w == 0); + + if (M_AXI_RVALID && M_AXI_RREADY && !M_AXI_RRESP[1]) + begin + r_src_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + <= r_src_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]+1; + r_src_addr[ADDRLSB-1:0] <= 0; + end + // }}} + end + + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + // {{{ + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] = wstrb[k] ? new_data[k*8 +: 8] + : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite control read interface + // {{{ + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + always @(*) + begin + axil_read_ready = !S_AXIL_RVALID || S_AXIL_RREADY; + if (!arskd_valid) + axil_read_ready = 1'b0; + if (!clk_active) + axil_read_ready = 1'b0; + end + + initial S_AXIL_RVALID = 1'b0; + always @(posedge i_clk) + if (i_reset) + S_AXIL_RVALID <= 1'b0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + S_AXIL_RVALID <= axil_read_ready; + + always @(posedge i_clk) + if (i_reset) + S_AXIL_RDATA <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + begin + S_AXIL_RDATA <= 0; + case(arskd_addr) + CTRL_ADDR: begin + S_AXIL_RDATA[CTRL_ERR_BIT] <= r_err; + S_AXIL_RDATA[CTRL_ABORT_BIT] <= r_abort; + S_AXIL_RDATA[CTRL_INTEN_BIT] <= r_int_enable; + S_AXIL_RDATA[CTRL_INT_BIT] <= r_int; + S_AXIL_RDATA[CTRL_BUSY_BIT] <= r_busy; + end + SRCLO_ADDR: + S_AXIL_RDATA <= wide_src[C_AXIL_DATA_WIDTH-1:0]; + SRCHI_ADDR: + S_AXIL_RDATA <= wide_src[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + DSTLO_ADDR: + S_AXIL_RDATA <= wide_dst[C_AXIL_DATA_WIDTH-1:0]; + DSTHI_ADDR: + S_AXIL_RDATA <= wide_dst[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + LENLO_ADDR: + S_AXIL_RDATA <= wide_len[C_AXIL_DATA_WIDTH-1:0]; + LENHI_ADDR: + S_AXIL_RDATA <= wide_len[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + default: begin end + endcase + + if (!axil_read_ready) + S_AXIL_RDATA <= 0; + end + + assign S_AXIL_RRESP = AXI_OKAY; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI read processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Read data into our FIFO + // + + // read_address + // {{{ + always @(posedge i_clk) + if (!r_busy) + read_address <= { 1'b0, r_src_addr }; + else if (phantom_read) + begin + // Verilator lint_off WIDTH + read_address[C_AXI_ADDR_WIDTH:ADDRLSB] + <= read_address[C_AXI_ADDR_WIDTH:ADDRLSB] +(M_AXI_ARLEN+1); + // Verilator lint_on WIDTH + read_address[ADDRLSB-1:0] <= 0; + end + // }}} + + // reads_remaining_w, reads_remaining_nonzero + // {{{ + // Verilator lint_off WIDTH + always @(posedge i_clk) + if (!r_busy) + reads_remaining_w <= readlen_b[LGLEN:ADDRLSB]; + else if (phantom_read) + reads_remaining_w <= reads_remaining_w - (M_AXI_ARLEN+1); + + always @(posedge i_clk) + if (!r_busy) + reads_remaining_nonzero <= 1; + else if (phantom_read) + reads_remaining_nonzero + <= (reads_remaining_w != (M_AXI_ARLEN+1)); + // Verilator lint_on WIDTH + // }}} + + // read_beats_remaining_w + // {{{ + always @(posedge i_clk) + if (!r_busy) + read_beats_remaining_w <= readlen_b[LGLEN:ADDRLSB]; + else if (M_AXI_RVALID && M_AXI_RREADY) + read_beats_remaining_w <= read_beats_remaining_w - 1; + // }}} + + // read_bursts_outstanding, no_read_bursts_outstanding + // {{{ + initial read_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + read_bursts_outstanding <= 0; + end else case({phantom_read,M_AXI_RVALID&& M_AXI_RREADY && M_AXI_RLAST}) + 2'b01: read_bursts_outstanding <= read_bursts_outstanding - 1; + 2'b10: read_bursts_outstanding <= read_bursts_outstanding + 1; + default: begin end + endcase + + initial no_read_bursts_outstanding = 1; + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + no_read_bursts_outstanding <= 1; + end else case({phantom_read,M_AXI_RVALID&& M_AXI_RREADY && M_AXI_RLAST}) + 2'b01: no_read_bursts_outstanding <= (read_bursts_outstanding == 1); + 2'b10: no_read_bursts_outstanding <= 0; + default: begin end + endcase + // }}} + + // M_AXI_ARADDR + // {{{ + always @(posedge i_clk) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + M_AXI_ARADDR <= 0; + else if (!r_busy) + begin + if (!OPT_LOWPOWER || w_start) + M_AXI_ARADDR <= r_src_addr; + else + M_AXI_ARADDR <= 0; + end else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + M_AXI_ARADDR <= read_address[C_AXI_ADDR_WIDTH-1:0]; + if (OPT_LOWPOWER && !w_start_read) + M_AXI_ARADDR <= 0; + end + // }}} + + // readlen_b + // {{{ + always @(*) + if (OPT_UNALIGNED) + readlen_b = r_len + { {(C_AXI_ADDR_WIDTH-ADDRLSB){1'b0}}, + r_src_addr[ADDRLSB-1:0] } + + { {(C_AXI_ADDR_WIDTH-ADDRLSB){1'b0}}, + {(ADDRLSB){1'b1}} }; + else begin + readlen_b = { 1'b0, r_len }; + readlen_b[ADDRLSB-1:0] = 0; + end + // }}} + + // read_distance_to_boundary_b + // {{{ + always @(*) + begin + read_distance_to_boundary_b = 0; + read_distance_to_boundary_b[ADDRLSB +: LGMAXBURST] + = -r_src_addr[ADDRLSB +: LGMAXBURST]; + end + // }}} + + // initial_readlen_w + // {{{ + always @(*) + begin + initial_readlen_w = 0; + initial_readlen_w[LGMAXBURST] = 1; + + if (r_src_addr[ADDRLSB +: LGMAXBURST] != 0) + initial_readlen_w[LGMAXBURST:0] = { 1'b0, + read_distance_to_boundary_b[ADDRLSB +: LGMAXBURST] }; + if (initial_readlen_w > readlen_b[LGLEN:ADDRLSB]) + initial_readlen_w[LGMAXBURST:0] = { 1'b0, + readlen_b[ADDRLSB +: LGMAXBURST] }; + initial_readlen_w[LGLENW-1:LGMAXBURST+1] = 0; + end + // }}} + + // readlen_w + // {{{ + // Verilator lint_off WIDTH + always @(posedge i_clk) + if (!r_busy) + begin + readlen_w <= initial_readlen_w; + end else if (phantom_read) + begin + readlen_w <= reads_remaining_w - (M_AXI_ARLEN+1); + if (reads_remaining_w - (M_AXI_ARLEN+1) > MAXBURST) + readlen_w <= MAXBURST; + end + // Verilator lint_on WIDTH + // }}} + + // w_start_read + // {{{ + always @(*) + begin + w_start_read = r_busy && reads_remaining_nonzero; + if (phantom_read) + w_start_read = 0; + if (!OPT_WRAPMEM && read_address[C_AXI_ADDR_WIDTH]) + w_start_read = 0; + if (fifo_space_available < MAXBURST) + w_start_read = 0; + if (M_AXI_ARVALID && !M_AXI_ARREADY) + w_start_read = 0; + if (r_err || r_abort) + w_start_read = 0; + end + // }}} + + // M_AXI_ARVALID, phantom_read + // {{{ + initial M_AXI_ARVALID = 1'b0; + initial phantom_read = 1'b0; + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + M_AXI_ARVALID <= 0; + phantom_read <= 0; + end else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + M_AXI_ARVALID <= w_start_read; + phantom_read <= w_start_read; + end else + phantom_read <= 0; + // }}} + + // M_AXI_ARLEN + // {{{ + always @(posedge i_clk) + if (i_reset || !r_busy) + M_AXI_ARLEN <= 0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin +`ifdef AXI3 + M_AXI_ARLEN <= readlen_w[3:0] - 4'h1; +`else + M_AXI_ARLEN <= readlen_w[7:0] - 8'h1; +`endif + if (OPT_LOWPOWER && !w_start_read) + M_AXI_ARLEN <= 0; + end + // }}} + + assign M_AXI_ARID = AXI_READ_ID; + assign M_AXI_ARBURST = AXI_INCR; + assign M_AXI_ARSIZE = ADDRLSB[2:0]; + assign M_AXI_ARLOCK = 1'b0; + assign M_AXI_ARCACHE = 4'b0011; + assign M_AXI_ARPROT = r_prot; + assign M_AXI_ARQOS = r_qos; + // + assign M_AXI_RREADY = !no_read_bursts_outstanding; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The intermediate FIFO + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + fifo_reset = i_reset || !r_busy || r_done; + + generate if (OPT_UNALIGNED) + begin : REALIGNMENT_FIFO + // {{{ + reg [ADDRLSB-1:0] inbyte_shift, outbyte_shift, + remaining_read_realignment; + reg [ADDRLSB+3-1:0] inshift_down, outshift_down, + inshift_up, outshift_up; + reg [C_AXI_DATA_WIDTH-1:0] r_partial_inword, + r_outword, r_partial_outword, + r_realigned_incoming; + wire [C_AXI_DATA_WIDTH-1:0] fifo_data; + reg [ADDRLSB-1:0] r_last_write_addr; + reg r_oneword, r_firstword; + + + /////////////////// + + + always @(posedge i_clk) + if (!r_busy) + begin + inbyte_shift <= r_src_addr[ADDRLSB-1:0]; + inshift_up <= 0; + inshift_up[3 +: ADDRLSB] <= -r_src_addr[ADDRLSB-1:0]; + end + + always @(*) + inshift_down = { inbyte_shift, 3'b000 }; + + always @(*) + remaining_read_realignment = -r_src_addr[ADDRLSB-1:0]; + + // extra_realignment_read will be true if we need to flush + // the read processor after the last word has been read in an + // extra write to the FIFO that isn't associated with any reads. + // In other words, if the number of writes to the FIFO is + // greater than the number of read beats + // - (src_addr unaligned?1:0) + always @(posedge i_clk) + if (!r_busy) + begin + extra_realignment_read <= (remaining_read_realignment + >= r_len[ADDRLSB-1:0]) ? 1:0; + if (r_len[ADDRLSB-1:0] == 0) + extra_realignment_read <= 1'b0; + if (r_src_addr[ADDRLSB-1:0] == 0) + extra_realignment_read <= 1'b0; + end else if ((!r_write_fifo || !fifo_full) && clear_read_pipeline) + extra_realignment_read <= 1'b0; + + always @(posedge i_clk) + if (!r_busy || !extra_realignment_read || clear_read_pipeline) + clear_read_pipeline <= 0; + else if (!r_write_fifo || !fifo_full) + clear_read_pipeline <= (read_beats_remaining_w + == (M_AXI_RVALID ? 1:0)); + +`ifdef FORMAL + always @(*) + if (r_busy) + begin + if (!extra_realignment_read) + begin + assert(!clear_read_pipeline); + end else if (read_beats_remaining_w > 0) + begin + assert(!clear_read_pipeline); + end else if (!no_read_bursts_outstanding) + begin + assert(!clear_read_pipeline); + end + end +`endif + + always @(posedge i_clk) + if (fifo_reset) + r_partial_in_valid <= (r_src_addr[ADDRLSB-1:0] == 0); + else if (M_AXI_RVALID) + r_partial_in_valid <= 1; + else if ((!r_write_fifo || !fifo_full) && clear_read_pipeline) + // If we have to flush the final partial valid signal, + // the do it when writing to the FIFO with clear_read + // pipelin set. Actually, this is one clock before + // that ... + r_partial_in_valid <= 0; + + always @(posedge i_clk) + if (fifo_reset || (inbyte_shift == 0)) + r_partial_inword <= 0; + else if (M_AXI_RVALID) + r_partial_inword <= M_AXI_RDATA >> inshift_down; + + initial r_write_fifo = 0; + always @(posedge i_clk) + if (fifo_reset) + r_write_fifo <= 0; + else if (M_AXI_RVALID || clear_read_pipeline) + r_write_fifo <= r_partial_in_valid; + else if (!fifo_full) + r_write_fifo <= 0; + + always @(posedge i_clk) + if (fifo_reset) + r_realigned_incoming <= 0; + else if (M_AXI_RVALID) + r_realigned_incoming <= r_partial_inword + | (M_AXI_RDATA << inshift_up); + else if (!r_write_fifo || !fifo_full) + r_realigned_incoming <= r_partial_inword; + + sfifo #( + // {{{ + .BW(C_AXI_DATA_WIDTH), + .LGFLEN(LGFIFO), + .OPT_ASYNC_READ(1'b1) + // }}} + ) middata( + // {{{ + i_clk, fifo_reset, + r_write_fifo, r_realigned_incoming, + fifo_full, fifo_fill, + r_read_fifo, fifo_data, fifo_empty + // }}} + ); + + always @(posedge i_clk) + if (!r_busy) + begin + outbyte_shift <= r_dst_addr[ADDRLSB-1:0]; + outshift_down <= 0; + outshift_down[3 +: ADDRLSB] <= -r_dst_addr[ADDRLSB-1:0]; + end + + always @(*) + outshift_up = { outbyte_shift, 3'b000 }; + + + always @(posedge i_clk) + if (fifo_reset) + r_partial_outword <= 0; + else if (r_read_fifo && outshift_up != 0) + r_partial_outword + <= (fifo_data >> outshift_down); + else if (M_AXI_WVALID && M_AXI_WREADY) + r_partial_outword <= 0; + + always @(posedge i_clk) + if (fifo_reset) + r_partial_outvalid <= 0; + else if (r_read_fifo && !fifo_empty) + r_partial_outvalid <= 1; + else if (fifo_empty && M_AXI_WVALID && M_AXI_WREADY) + r_partial_outvalid <= extra_realignment_write; + + always @(posedge i_clk) + if (fifo_reset) + r_outword <= 0; + else if (!r_partial_outvalid || (M_AXI_WVALID && M_AXI_WREADY)) + begin + if (!fifo_empty) + r_outword <= r_partial_outword | + (fifo_data << outshift_up); + else + r_outword <= r_partial_outword; + end + + always @(*) + M_AXI_WDATA = r_outword; + + always @(*) + begin + r_read_fifo = 0; + if (!r_partial_outvalid) + r_read_fifo = 1; + if (M_AXI_WVALID && M_AXI_WREADY) + r_read_fifo = 1; + + if (fifo_empty) + r_read_fifo = 0; + end + + //////////////////////////////////////////////////////////////// + // + // Write strobe logic for the unaligned case + // + //////////////////////////////////////////////////////////////// + // + // + + always @(posedge i_clk) + if (!r_busy) + begin + if (r_len[(LGLEN-1):(ADDRLSB+1)] != 0) + r_oneword <= 0; + else + r_oneword <= (({ 2'b0, r_dst_addr[ADDRLSB-1:0]} + + r_len[ADDRLSB+1:0]) <= + { 2'b01, {(ADDRLSB){1'b0}} } ? 1:0); + end + + initial r_first_wstrb = 0; + always @(posedge i_clk) + if (!r_busy) + begin + if (r_len[LGLEN-1:ADDRLSB] != 0) + r_first_wstrb <= -1 << r_dst_addr[ADDRLSB-1:0]; + else + r_first_wstrb <= ((1<<r_len[ADDRLSB-1:0]) -1) << r_dst_addr[ADDRLSB-1:0]; + end + + always @(*) + r_last_write_addr = r_dst_addr[ADDRLSB-1:0] + r_len[ADDRLSB-1:0]; + + always @(posedge i_clk) + if (!r_busy) + begin + if (r_last_write_addr[ADDRLSB-1:0] == 0) + r_last_wstrb <= -1; + else + r_last_wstrb <= (1<<r_last_write_addr)-1; + end + + always @(posedge i_clk) + if (!r_busy) + r_firstword <= 1; + else if (M_AXI_WVALID && M_AXI_WREADY) + r_firstword <= 0; + + always @(posedge i_clk) + if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (r_oneword) + M_AXI_WSTRB <= r_first_wstrb & r_last_wstrb; + else if (M_AXI_WVALID && M_AXI_WREADY) + begin + if (write_beats_remaining > 2) + M_AXI_WSTRB <= -1; + else + M_AXI_WSTRB <= r_last_wstrb; + end else if (r_firstword) + M_AXI_WSTRB <= r_first_wstrb; + + if (r_err || r_abort) + M_AXI_WSTRB <= 0; + end + // }}} + end else begin : ALIGNED_FIFO + // {{{ + always @(*) + begin + r_first_wstrb = -1; + r_last_wstrb = -1; + r_partial_in_valid = 1; + r_partial_outvalid = !fifo_empty; + + r_write_fifo = M_AXI_RVALID; + r_read_fifo = M_AXI_WVALID && M_AXI_WREADY; + + clear_read_pipeline = 1'b0; + end + + sfifo #( + // {{{ + .BW(C_AXI_DATA_WIDTH), + .LGFLEN(LGFIFO), + .OPT_ASYNC_READ(1'b1) + // }}} + ) middata( + // {{{ + i_clk, fifo_reset, + r_write_fifo, M_AXI_RDATA, fifo_full, fifo_fill, + r_read_fifo, M_AXI_WDATA, fifo_empty + // }}} + ); + + + initial M_AXI_WSTRB = -1; + always @(posedge i_clk) + if (!S_AXI_ARESETN || !r_busy) + M_AXI_WSTRB <= -1; + else if (!M_AXI_WVALID || M_AXI_WREADY) + M_AXI_WSTRB <= (r_err || r_abort) ? 0 : -1; + + always @(*) + extra_realignment_read <= 0; + // }}} + end endgenerate + + // fifo_space_available + // {{{ + always @(posedge i_clk) + if (fifo_reset) + fifo_space_available <= (1<<LGFIFO) + // space for r_partial_outvalid + + (OPT_UNALIGNED ? 1:0) + // space for r_partial_in_valid + + (OPT_UNALIGNED && (r_src_addr[ADDRLSB-1:0] != 0) ? 1:0); + else case({ phantom_read, M_AXI_WVALID && M_AXI_WREADY }) + // Verilator lint_off WIDTH + 2'b10: fifo_space_available <= fifo_space_available - (M_AXI_ARLEN+1); + 2'b01: fifo_space_available <= fifo_space_available + 1; + 2'b11: fifo_space_available <= fifo_space_available - M_AXI_ARLEN; + // Verilator lint_on WIDTH + default: begin end + endcase + // }}} + + // write_realignment + // {{{ + always @(*) + if (OPT_UNALIGNED) + begin + // Verilator lint_off WIDTH + // write_remaining + write_realignment[ADDRLSB+1:0] + = r_len[ADDRLSB-1:0]+r_dst_addr[ADDRLSB-1:0] + + (1<<ADDRLSB)-1; + + // Raw length + write_realignment[2*ADDRLSB+2:ADDRLSB+2] + = r_len[ADDRLSB-1:0] + (1<<ADDRLSB)-1; + // Verilator lint_on WIDTH + end else + write_realignment = 0; + // }}} + + // extra_realignment_write + // {{{ + always @(posedge i_clk) + if (!OPT_UNALIGNED) + extra_realignment_write <= 1'b0; + else if (!r_busy) + begin + if ({ 1'b0, write_realignment[2*ADDRLSB+2] } + != write_realignment[ADDRLSB+1:ADDRLSB]) + extra_realignment_write <= 1'b1; + else + extra_realignment_write <= 1'b0; + end else if (M_AXI_WVALID && M_AXI_WREADY && fifo_empty) + extra_realignment_write <= 1'b0; + // }}} + + // last_read_beat + // {{{ + always @(posedge i_clk) + if (!r_busy) + last_read_beat <= 1'b0; + else + last_read_beat <= M_AXI_RVALID && M_AXI_RREADY + && (read_beats_remaining_w == 1); + // }}} + + // next_fifo_data_available + // {{{ + always @(*) + begin + next_fifo_data_available = fifo_data_available; + // Verilator lint_off WIDTH + if (phantom_write) + next_fifo_data_available = next_fifo_data_available + - (M_AXI_AWLEN + (r_write_fifo && !fifo_full ? 0:1)); + else if (r_write_fifo && !fifo_full) + next_fifo_data_available = next_fifo_data_available + 1; + + if (extra_realignment_write && last_read_beat) + next_fifo_data_available = next_fifo_data_available + 1; + // Verilator lint_on WIDTH + end + // }}} + + // fifo_data_available + // {{{ + initial fifo_data_available = 0; + always @(posedge i_clk) + if (!r_busy || r_done) + fifo_data_available <= 0; + else + fifo_data_available <= next_fifo_data_available; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI write processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write data from the FIFO to the AXI bus + // + + // write_address + // {{{ + always @(posedge i_clk) + if (!r_busy) + write_address <= { 1'b0, r_dst_addr }; + else if (phantom_write) + begin + // Verilator lint_off WIDTH + write_address <= write_address + ((M_AXI_AWLEN+1)<< M_AXI_AWSIZE); + // Verilator lint_on WIDTH + write_address[ADDRLSB-1:0] <= 0; + end + // }}} + + // writes_remaining_w, multiple_write_bursts_remaining + // {{{ + // Verilator lint_off WIDTH + always @(*) + w_writes_remaining_w = writes_remaining_w - (M_AXI_AWLEN+1); + + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + writes_remaining_w <= writelen_b[LGLEN:ADDRLSB]; + multiple_write_bursts_remaining <= |writelen_b[LGLEN:(ADDRLSB+LGMAXBURST)]; + end else if (phantom_write) + begin + writes_remaining_w <= w_writes_remaining_w; + multiple_write_bursts_remaining + <= |w_writes_remaining_w[LGLENW:LGMAXBURST]; + end + // Verilator lint_on WIDTH + // }}} + + // write_beats_remaining + // {{{ + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + write_beats_remaining <= writelen_b[LGLEN:ADDRLSB]; + end else if (M_AXI_WVALID && M_AXI_WREADY) + write_beats_remaining <= write_beats_remaining - 1; + // }}} + + // write_requests_remaining + // {{{ + // Verilator lint_off WIDTH + initial write_requests_remaining = 0; + always @(posedge i_clk) + if (i_reset) + write_requests_remaining <= 1'b0; + else if (!r_busy) + write_requests_remaining <= w_start; + else if (phantom_write) + write_requests_remaining <= (writes_remaining_w != (M_AXI_AWLEN+1)); + // Verilator lint_on WIDTH + // }}} + + // write_bursts_outstanding + // {{{ + initial write_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + write_bursts_outstanding <= 0; + end else case({phantom_write, M_AXI_BVALID && M_AXI_BREADY }) + 2'b01: write_bursts_outstanding <= write_bursts_outstanding - 1; + 2'b10: write_bursts_outstanding <= write_bursts_outstanding + 1; + default: begin end + endcase + // }}} + + // last_write_ack + // {{{ + // Verilator lint_off WIDTH + always @(posedge i_clk) + if (!r_busy) + last_write_ack <= 0; + else if (writes_remaining_w > ((phantom_write) ? (M_AXI_AWLEN+1) : 0)) + last_write_ack <= 0; + else + last_write_ack <= (write_bursts_outstanding + == (phantom_write ? 0:1) + (M_AXI_BVALID ? 1:0)); + // Verilator lint_on WIDTH + // }}} + + // r_done + // {{{ + always @(posedge i_clk) + if (!r_busy || M_AXI_ARVALID || M_AXI_AWVALID) + r_done <= 0; + else if (read_bursts_outstanding > 0) + r_done <= 0; + else if (write_bursts_outstanding > (M_AXI_BVALID ? 1:0)) + r_done <= 0; + else if (r_abort || r_err) + r_done <= 1; + else if (writes_remaining_w > 0) + r_done <= 0; + else + r_done <= 1; + // }}} + + // writelen_b + // {{{ + always @(*) + if (OPT_UNALIGNED) + writelen_b = { 1'b0, r_len } + { {(LGLEN+1-ADDRLSB){1'b0}}, + r_dst_addr[ADDRLSB-1:0] } + + { {(LGLEN+1-ADDRLSB){1'b0}}, {(ADDRLSB){1'b1}} }; + // was + (1<<ADDRLSB)-1; + else begin + writelen_b = { 1'b0, r_len }; + writelen_b[ADDRLSB-1:0] = 0; + end + // }}} + + // initial_write_distance_to_boundary_w + // {{{ + always @(*) + begin + initial_write_distance_to_boundary_w + = - { 1'b0, write_address[ADDRLSB +: LGMAXBURST] }; + initial_write_distance_to_boundary_w[LGMAXBURST] = 1'b0; + end + // }}} + + // first_write_burst, first_write_len_w + // {{{ + always @(posedge i_clk) + if (!r_busy) + begin + first_write_burst <= 1'b1; + if (|writelen_b[LGLEN:ADDRLSB+LGMAXBURST]) + first_write_len_w <= MAXBURST; + else + first_write_len_w <= { 1'b0, + writelen_b[ADDRLSB +: LGMAXBURST] }; + end else begin + if (phantom_write) + first_write_burst <= 1'b0; + + if (first_write_burst + && write_address[ADDRLSB +: LGMAXBURST] != 0 + && first_write_len_w + > initial_write_distance_to_boundary_w) + first_write_len_w<=initial_write_distance_to_boundary_w; + end + // }}} + + // write_burst_length + // {{{ + // Verilator lint_off WIDTH + always @(*) + if (first_write_burst) + write_burst_length = first_write_len_w; + else begin + write_burst_length = MAXBURST; + + if (!multiple_write_bursts_remaining + && (write_burst_length[ADDRLSB +: LGMAXBURST] + > writes_remaining_w[ADDRLSB +: LGMAXBURST])) + write_burst_length = writes_remaining_w; + end + // Verilator lint_on WIDTH + // }}} + + // write_count + // {{{ + initial write_count = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + write_count <= 0; + else if (w_write_start) + begin + write_count <= 0; + write_count[LGMAXBURST:0] <= write_burst_length[LGMAXBURST:0]; + end else if (M_AXI_WVALID && M_AXI_WREADY) + write_count <= write_count - 1; + // }}} + + // M_AXI_WLAST + // {{{ + initial M_AXI_WLAST = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + begin + M_AXI_WLAST <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + M_AXI_WLAST <= 1; + if (write_count > 2) + M_AXI_WLAST <= 0; + if (w_write_start) +`ifdef AXI3 + M_AXI_WLAST <= (write_burst_length[3:0] == 1); +`else + M_AXI_WLAST <= (write_burst_length[7:0] == 1); +`endif + end + // }}} + + // w_write_start + // {{{ + always @(*) + last_write_burst = (write_burst_length == writes_remaining_w); + + always @(*) + begin + // Verilator lint_off WIDTH + if (!last_write_burst && OPT_UNALIGNED) + w_write_start = (fifo_data_available > 1) + &&(fifo_data_available > write_burst_length); + else + w_write_start = (fifo_data_available >= write_burst_length); + // Verilator lint_on WIDTH + if (!write_requests_remaining) + w_write_start = 0; + if (!OPT_WRAPMEM && write_address[C_AXI_ADDR_WIDTH]) + w_write_start = 0; + if (phantom_write) + w_write_start = 0; + if (M_AXI_AWVALID && !M_AXI_AWREADY) + w_write_start = 0; + if (M_AXI_WVALID && (!M_AXI_WLAST || !M_AXI_WREADY)) + w_write_start = 0; + if (i_reset || r_err || r_abort || !r_busy) + w_write_start = 0; + end + // }}} + + // M_AXI_AWVALID, phantom_write + // {{{ + initial M_AXI_AWVALID = 0; + initial phantom_write = 0; + always @(posedge i_clk) + if (i_reset) + begin + M_AXI_AWVALID <= 0; + phantom_write <= 0; + end else if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + M_AXI_AWVALID <= w_write_start; + phantom_write <= w_write_start; + end else + phantom_write <= 0; + // }}} + + // M_AXI_WVALID + // {{{ + initial M_AXI_WVALID = 1'b0; + always @(posedge i_clk) + if (i_reset) + M_AXI_WVALID <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + M_AXI_WVALID <= 0; + if (M_AXI_WVALID && !M_AXI_WLAST) + M_AXI_WVALID <= 1; + if (w_write_start) + M_AXI_WVALID <= 1; + end + // }}} + + // M_AXI_AWLEN + // {{{ + initial M_AXI_AWLEN = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + M_AXI_AWLEN <= 0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + M_AXI_AWLEN <= 0; + if (w_write_start) +`ifdef AXI3 + M_AXI_AWLEN <= write_burst_length[3:0]-1; +`else + M_AXI_AWLEN <= write_burst_length[7:0]-1; +`endif + end + // }}} + + // M_AXI_AWADDR + // {{{ + always @(posedge i_clk) + if (!r_busy) + M_AXI_AWADDR <= r_dst_addr; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + M_AXI_AWADDR <= write_address[C_AXI_ADDR_WIDTH-1:0]; + // }}} + + always @(*) + begin + M_AXI_AWID = AXI_WRITE_ID; + M_AXI_AWBURST = AXI_INCR; + M_AXI_AWSIZE = ADDRLSB[2:0]; + M_AXI_AWLOCK = 1'b0; + M_AXI_AWCACHE = 4'b0011; + M_AXI_AWPROT = r_prot; + M_AXI_AWQOS = r_qos; + // +`ifdef AXI3 + M_AXI_WID = AXI_WRITE_ID; +`endif + M_AXI_BREADY = !r_done; + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // (Optional) Clock gating + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_CLKGATE) + begin : CLK_GATING + // {{{ + reg r_clk_active; + reg gaten /* verilator clock_enable */; + + // clk_active + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_clk_active <= 1'b1; + else begin + r_clk_active <= 1'b0; + + if (r_busy) + r_clk_active <= 1'b1; + if (awskd_valid || wskd_valid || arskd_valid) + r_clk_active <= 1'b1; + if (S_AXIL_BVALID || S_AXIL_RVALID) + r_clk_active <= 1'b1; + end + + assign clk_active = r_clk_active; + // }}} + // Gate the clock here locally + // {{{ + always @(negedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + gaten <= 1'b1; + else + gaten <= r_clk_active; + + assign gated_clk = S_AXI_ACLK && gaten; + + assign clk_active = r_clk_active; + // }}} + // }}} + end else begin : NO_CLK_GATING + // {{{ + // Always active + assign clk_active = 1'b1; + assign gated_clk = S_AXI_ACLK; + // }}} + end endgenerate + // }}} + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + // Verilator coverage_off + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_BID, + M_AXI_RID, M_AXI_BRESP[0], M_AXI_RRESP[0], + S_AXIL_AWADDR[AXILLSB-1:0], S_AXIL_ARADDR[AXILLSB-1:0], + fifo_full, fifo_fill, fifo_empty, +`ifdef AXI3 + readlen_w[LGLENW:4], +`else + readlen_w[LGLENW:8], +`endif + writelen_b[ADDRLSB-1:0], readlen_b[ADDRLSB-1:0], + read_distance_to_boundary_b + }; + + generate if (C_AXI_ADDR_WIDTH < 2*C_AXIL_DATA_WIDTH) + begin : NEW_UNUSED + wire genunused; + + assign genunused = &{ 1'b0, + new_widesrc[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], + new_widedst[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] }; + end endgenerate + + // Verilator coverage_on + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The following formal properties are only a subset of those used + // to verify the full core. Do not be surprised to find syntax errors + // here, or registers that are not defined. These are correct in the + // full version. + // + //////////////////////////////////////////////////////////////////////// + // + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + // + // ... + // + + reg [C_AXI_ADDR_WIDTH:0] f_next_wraddr, f_next_rdaddr; + + reg [C_AXI_ADDR_WIDTH:0] f_src_addr, f_dst_addr, + f_read_address, f_write_address, + f_read_check_addr, f_write_beat_addr, + f_read_beat_addr; + reg [LGLEN:0] f_length, f_rdlength, f_wrlength, + f_writes_complete, f_reads_complete; + + + + //////////////////////////////////////////////////////////////////////// + // + // The control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxil_slave #( + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH) + // + // ... + // + ) + faxil( + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr(S_AXIL_AWADDR), + .i_axi_awprot(S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP) + // + // ... + // + ); + + // + // ... + // + + // + // Verify the AXI-lite result registers + // + always @(*) + if (!r_busy) + assert((r_done && (r_err || r_abort)) + || (zero_len == (r_len == 0))); + else + assert(zero_len == (r_len == 0 && writes_remaining_w == 0)); + + always @(*) + if (!i_reset && !OPT_UNALIGNED) + assert(r_src_addr[ADDRLSB-1:0] == 0); + + always @(*) + if (!i_reset && !OPT_UNALIGNED) + assert(r_dst_addr[ADDRLSB-1:0] == 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The main AXI data interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + faxi_master #( + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .OPT_MAXBURST(MAXBURST) + // + // ... + // + ) + faxi( + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awid( M_AXI_AWID), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awlen( M_AXI_AWLEN), + .i_axi_awsize( M_AXI_AWSIZE), + .i_axi_awburst(M_AXI_AWBURST), + .i_axi_awlock( M_AXI_AWLOCK), + .i_axi_awcache(M_AXI_AWCACHE), + .i_axi_awprot( M_AXI_AWPROT), + .i_axi_awqos( M_AXI_AWQOS), + // + .i_axi_wvalid(M_AXI_WVALID), + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + .i_axi_wlast( M_AXI_WLAST), + // + .i_axi_bvalid(M_AXI_BVALID), + .i_axi_bready(M_AXI_BREADY), + .i_axi_bid( M_AXI_BID), + .i_axi_bresp( M_AXI_BRESP), + // + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + // + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rid( M_AXI_RID), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rlast( M_AXI_RLAST), + .i_axi_rresp( M_AXI_RRESP) + // + // ... + // + ); + + always @(*) + begin + // + // ... + // + if (r_done) + begin + // + // ... + // + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assert(!M_AXI_ARVALID); + end + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Internal assertions (Induction) + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK) + if (w_start) + begin + f_src_addr <= { 1'b0, r_src_addr }; + f_dst_addr <= { 1'b0, r_dst_addr }; + f_length <= r_len; + end else if (r_busy) + begin + assert(f_length != 0); + assert(f_length[LGLEN] == 0); + + assert(f_src_addr[C_AXI_ADDR_WIDTH] == 1'b0); + assert(f_dst_addr[C_AXI_ADDR_WIDTH] == 1'b0); + end + + always @(*) + begin + f_rdlength = f_length + f_src_addr[ADDRLSB-1:0] + + (1<<ADDRLSB)-1; + f_rdlength[ADDRLSB-1:0] = 0; + + f_wrlength = f_length + f_dst_addr[ADDRLSB-1:0] + + (1<<ADDRLSB)-1; + f_wrlength[ADDRLSB-1:0] = 0; + + f_raw_length = f_length + (1<<ADDRLSB)-1; + f_raw_length[ADDRLSB-1:0] = 0; + + // One past the last address we'll actually read + f_last_src_addr = f_src_addr + f_length + (1<<ADDRLSB)-1; + f_last_src_addr[ADDRLSB-1:0] = 0; + f_last_src_beat = f_src_addr + f_length -1; + f_last_src_beat[ADDRLSB-1:0] = 0; + + f_last_dst_addr = f_dst_addr + f_length + (1<<ADDRLSB)-1; + f_last_dst_addr[ADDRLSB-1:0] = 0; + + f_rd_arfirst = ({ 1'b0, M_AXI_ARADDR } == f_src_addr); + f_rd_arlast = (M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB] + + (M_AXI_ARLEN+1) + == f_last_src_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]); + f_rd_ckfirst = (faxi_rd_ckaddr == f_src_addr); + f_rd_cklast = (faxi_rd_ckaddr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + + faxi_rd_cklen == f_last_src_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]); + + // f_extra_realignment_read + // true if we need to write a partial word to the FIFO/buffer + // *after* all of our reads are complete. This is a final + // flush sort of thing. + if (OPT_UNALIGNED && f_src_addr[ADDRLSB-1:0] != 0) + begin + // In general, if we are 1) unaligned, and 2) the + // length is greater than a word, and 3) there's a + // fraction of a word remaining, then we need to flush + // a last word into the FIFO. + f_extra_realignment_read + = (((1<<ADDRLSB)-f_src_addr[ADDRLSB-1:0]) + >= f_length[ADDRLSB-1:0]) ? 1:0; + if (f_length[ADDRLSB-1:0] == 0) + f_extra_realignment_read = 0; + end else + f_extra_realignment_read = 1'b0; + + // + // f_extra_realignment_preread + // will be true anytime we need to read a first word before + // writing anything to the buffer. + if (OPT_UNALIGNED && f_src_addr[ADDRLSB-1:0] != 0) + f_extra_realignment_preread = 1'b1; + else + f_extra_realignment_preread = 1'b0; + + // + // f_extra_realignment_write + // true if following the last read from the FIFO there's a + // partial word that will need to be flushed through the + // system. + if (OPT_UNALIGNED && f_raw_length[LGLEN:ADDRLSB] + != f_wrlength[LGLEN:ADDRLSB]) + f_extra_realignment_write = 1'b1; + else + f_extra_realignment_write = 1'b0; + + f_extra_write_in_fifo = (f_extra_realignment_write) + && (read_beats_remaining_w == 0)&&(!last_read_beat); + end + + always @(*) + if (r_busy && !OPT_UNALIGNED) + begin + assert(f_src_addr[ADDRLSB-1:0] == 0); + assert(f_dst_addr[ADDRLSB-1:0] == 0); + assert(f_length[ADDRLSB-1:0] == 0); + end + + always @(*) + if (r_busy) + begin + if (!f_extra_realignment_write) + assert(!extra_realignment_write); + else if (f_writes_complete >= f_wrlength[LGLEN:ADDRLSB]-1) + assert(!extra_realignment_write); + else + assert(extra_realignment_write); + + if ((f_writes_complete > 0 || M_AXI_WVALID) + && extra_realignment_write) + assert(r_partial_outvalid); + end + + always @(*) + if (r_busy) + begin + if (extra_realignment_read) + assert(f_extra_realignment_read); + else if (read_beats_remaining_w > 0) + assert(f_extra_realignment_read == extra_realignment_read); + end + + // + // ... + // + + always @(*) + if (fifo_full) + assert(no_read_bursts_outstanding); + + always @(*) + if (r_busy) + assert(!r_int); + + //////////////////////////////////////////////////////////////////////// + // + // Write checks + // + + // + // ... + // + + always @(*) + begin + f_write_address = 0; + f_write_address[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_dst_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + + (f_wrlength[LGLEN:ADDRLSB] - writes_remaining_w); + end + + always @(*) + begin + f_next_wraddr = { 1'b0, M_AXI_AWADDR } + + ((M_AXI_AWLEN+1)<<M_AXI_AWSIZE); + f_next_wraddr[ADDRLSB-1:0] = 0; + end + + always @(*) + if (M_AXI_AWVALID) + begin + assert(M_AXI_WVALID); + assert(M_AXI_AWLEN < (1<<LGMAXBURST)); + + if (M_AXI_AWLEN != (1<<LGMAXBURST)-1) + begin + assert((M_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB] + == f_dst_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]) + ||(phantom_write) + ||(writes_remaining_w == 0)); + end else begin + assert(M_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB] + == f_write_beat_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]); + end + + if (M_AXI_AWADDR[ADDRLSB-1:0] != 0) + assert({ 1'b0, M_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:0] } + == f_dst_addr); + else if (M_AXI_AWADDR != f_dst_addr) + assert(M_AXI_AWADDR[0 +: LGMAXBURST+ADDRLSB] == 0); + end + + always @(*) + if (!OPT_UNALIGNED) + begin + assert(r_len[ADDRLSB-1:0] == 0); + assert(r_src_addr[ADDRLSB-1:0] == 0); + assert(r_dst_addr[ADDRLSB-1:0] == 0); + end + + always @(*) + if (r_busy) + begin + assert(writes_remaining_w <= f_wrlength[LGLEN:ADDRLSB]); + assert(f_writes_complete <= f_wrlength[LGLEN:ADDRLSB]); + assert(fifo_fill <= f_wrlength[LGLEN:ADDRLSB]); + + assert(write_address[C_AXI_ADDR_WIDTH:ADDRLSB] + == f_write_address[C_AXI_ADDR_WIDTH:ADDRLSB]); + + if (M_AXI_AWADDR != f_dst_addr && writes_remaining_w != 0) + assert(M_AXI_AWADDR[LGMAXBURST+ADDRLSB-1:0] == 0); + else if (!OPT_UNALIGNED) + assert(M_AXI_AWADDR[ADDRLSB-1:0] == 0); + + if((writes_remaining_w!= f_wrlength[LGLEN:ADDRLSB]) + &&(writes_remaining_w != 0)) + assert(write_address[LGMAXBURST+ADDRLSB-1:0] == 0); + + if ((write_address[C_AXI_ADDR_WIDTH:ADDRLSB] + != f_dst_addr[C_AXI_ADDR_WIDTH:ADDRLSB]) + &&(writes_remaining_w > 0)) + assert(write_address[LGMAXBURST+ADDRLSB-1:0] == 0); + + if (writes_remaining_w != f_wrlength[LGLEN:ADDRLSB] + && writes_remaining_w != 0) + assert((write_burst_length == (1<<LGMAXBURST)) + ||(write_burst_length == writes_remaining_w)); + + assert(write_requests_remaining == (writes_remaining_w != 0)); + end + + // + // ... + // + + always @(*) + if (M_AXI_AWVALID && !phantom_write) + begin + assert(write_count == (M_AXI_AWLEN+1)); + // + // ... + // + end + + always @(*) + if (r_busy && last_write_ack) + begin + assert(reads_remaining_w == 0); + assert(!M_AXI_ARVALID); + assert(writes_remaining_w == 0); + end + + always @(*) + if (r_busy && !r_abort && !r_err) + assert(write_address[C_AXI_ADDR_WIDTH:ADDRLSB] + == f_dst_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + || (write_address[LGMAXBURST-1:0] == 0) + || (writes_remaining_w == 0)); + + always @(*) + if (phantom_write) + assert(writes_remaining_w >= (M_AXI_AWLEN+1)); + else if (M_AXI_AWVALID) + assert(write_address[C_AXI_ADDR_WIDTH-1:0] + == f_next_wraddr[C_AXI_ADDR_WIDTH-1:0]); + + // + // ... + // + + always @(*) + if (M_AXI_WVALID) + begin + if (OPT_UNALIGNED) + assert(r_partial_outvalid); + else + assert(!fifo_empty || r_abort || r_err); + // + // ... + // + end + + always @(*) + begin + f_write_beat_addr = 0; + f_write_beat_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_dst_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + + (f_wrlength[LGLEN:ADDRLSB]-write_beats_remaining); + + // + // ... + // + end + + always @(*) + if (r_busy) + begin + // + // ... + // + + if (write_beats_remaining == 0) + assert(!M_AXI_WVALID); + end + + always @(*) + if (writes_remaining_w < f_wrlength[LGLEN:ADDRLSB]) + begin + if (writes_remaining_w == 0) + assert(fifo_data_available == 0); + // else ... + end + + //////////////////////////////////////////////////////////////////////// + // + // Read checks + // + always @(*) + begin + f_reads_complete = f_rdlength[LGLEN:ADDRLSB] + - reads_remaining_w - // ... + // + // ... + // + end + + always @(*) + begin + if (reads_remaining_w == f_rdlength[LGLEN:ADDRLSB]) + f_read_address = f_src_addr; + else begin + f_read_address = 0; + f_read_address[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_src_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + + (f_rdlength[LGLEN:ADDRLSB] - reads_remaining_w); + end + + f_araddr = f_read_address; + if (M_AXI_ARVALID && !phantom_read) + f_araddr[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_araddr[C_AXI_ADDR_WIDTH:ADDRLSB] + - (M_AXI_ARLEN+1); + if (f_araddr[C_AXI_ADDR_WIDTH:ADDRLSB] + == f_src_addr[C_AXI_ADDR_WIDTH:ADDRLSB]) + f_araddr[ADDRLSB-1:0] = f_src_addr[ADDRLSB-1:0]; + + // + // Match the read check address to our source address + // + f_read_check_addr = 0; + f_read_check_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + = f_src_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + + f_reads_complete + // ... + + + // + // Match the RVALID address to our source address + // + f_read_beat_addr = 0; + if (f_reads_complete == 0) + f_read_beat_addr = f_src_addr; + else + f_read_beat_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_src_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + + f_reads_complete; + + f_end_of_read_burst = M_AXI_ARADDR; + f_end_of_read_burst[ADDRLSB-1:0] = 0; + f_end_of_read_burst[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_end_of_read_burst[C_AXI_ADDR_WIDTH:ADDRLSB] + + (M_AXI_ARLEN + 1); + + // + // ... + // + end + + always @(*) + begin + f_next_rdaddr = { 1'b0, M_AXI_ARADDR } + ((M_AXI_ARLEN+1)<<M_AXI_ARSIZE); + f_next_rdaddr[ADDRLSB-1:0] = 0; + end + + ///////// + // + // Constrain the read address + // + + always @(*) + if (r_busy) + begin + if (!f_rd_arfirst) + begin + assert(reads_remaining_w < f_rdlength[LGLEN:ADDRLSB]); + + // All bursts following the first one must be aligned + if (M_AXI_ARVALID || reads_remaining_w > 0) + assert(M_AXI_ARADDR[0 +: LGMAXBURST + ADDRLSB] == 0); + end else if (phantom_read) + assert(reads_remaining_w == f_rdlength[LGLEN:ADDRLSB]); + else if (M_AXI_ARVALID) + assert(reads_remaining_w == f_rdlength[LGLEN:ADDRLSB] + - (M_AXI_ARLEN+1)); + + // All but the first burst should be aligned + if (!OPT_UNALIGNED || M_AXI_ARADDR != f_src_addr) + assert(M_AXI_ARADDR[ADDRLSB-1:0] == 0); + + if (phantom_read) + assert(M_AXI_ARADDR == read_address[C_AXI_ADDR_WIDTH-1:0]); + else if (M_AXI_ARVALID) + assert(read_address[C_AXI_ADDR_WIDTH-1:0] == f_next_rdaddr[C_AXI_ADDR_WIDTH-1:0]); + end // else ... + + // + // ... + // + + // Constrain read_address--our pointer to the next bursts address + always @(*) + if (r_busy) + begin + assert((read_address == f_src_addr) + || (read_address[LGMAXBURST+ADDRLSB-1:0] == 0) + || (reads_remaining_w == 0)); + + + assert(read_address == f_read_address); + if (!OPT_UNALIGNED) + assert(read_address[ADDRLSB-1:0] == 0); + + if ((reads_remaining_w != f_rdlength[LGLEN:ADDRLSB]) + &&(reads_remaining_w > 0)) + assert(read_address[LGMAXBURST+ADDRLSB-1:0] == 0); + + if ((read_address[C_AXI_ADDR_WIDTH:ADDRLSB] + != f_src_addr[C_AXI_ADDR_WIDTH:ADDRLSB]) + &&(reads_remaining_w > 0)) + assert(read_address[LGMAXBURST+ADDRLSB-1:0] == 0); + end + + + ///////// + // + // Constrain the read length + // + always @(*) + if (M_AXI_ARVALID) + begin + assert(M_AXI_ARLEN < (1<<LGMAXBURST)); + if (M_AXI_ARLEN != (1<<LGMAXBURST)-1) + begin + assert((M_AXI_ARADDR == f_src_addr) + ||(phantom_read) + ||(reads_remaining_w == 0)); + end + + assert(f_end_of_read_burst_last[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB] + == M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB]); + end + + always @(*) + if (M_AXI_ARVALID && reads_remaining_w > (M_AXI_ARLEN+1)) + assert(f_end_of_read_burst[ADDRLSB+LGMAXBURST-1:0]==0); + + + ///////// + // + // Constrain RLAST + // + + always @(*) + if (M_AXI_RVALID) + begin + if (M_AXI_RLAST) + begin + if (f_read_beat_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + != f_last_src_beat[C_AXI_ADDR_WIDTH:ADDRLSB]) + assert(&f_read_beat_addr[ADDRLSB+LGMAXBURST-1:ADDRLSB]); + end else + assert(f_read_beat_addr[ADDRLSB+LGMAXBURST-1:ADDRLSB] + != {(LGMAXBURST){1'b1}}); + end + + always @(*) + if (f_read_beat_addr == f_last_src_beat) + assert(!M_AXI_RVALID || M_AXI_RLAST); + else + assert(!M_AXI_RVALID + || M_AXI_RLAST == (&f_read_beat_addr[LGMAXBURST+ADDRLSB-1:ADDRLSB])); + + + ///////// + // + // + // + + + always @(*) + assert(no_read_bursts_outstanding == (read_bursts_outstanding == 0)); + + always @(*) + if (r_busy && !r_err) + assert(f_read_beat_addr[C_AXI_ADDR_WIDTH-1:0] == r_src_addr); + + always @(*) + if (r_busy) + begin + // Some quick checks to make sure the subsequent checks + // don't overflow anything + assert(reads_remaining_w <= f_rdlength[LGLEN:ADDRLSB]); + assert(f_reads_complete <= f_rdlength[LGLEN:ADDRLSB]); + // ... + assert(fifo_fill <= f_raw_length[LGLEN:ADDRLSB]); + assert(fifo_space_available<= (1<<LGFIFO)); + assert(fifo_space_available<= (1<<LGFIFO) - fifo_fill); + // ... + + + if (reads_remaining_w != f_rdlength[LGLEN:ADDRLSB] + && reads_remaining_w != 0) + assert((readlen_w == (1<<LGMAXBURST)) + ||(readlen_w == reads_remaining_w)); + + // + // Read-length checks + assert(readlen_w <= (1<<LGMAXBURST)); + if (reads_remaining_w > 0) + assert(readlen_w != 0); + if (readlen_w != (1<<LGMAXBURST)) + assert(reads_remaining_w == readlen_w + ||(read_address == f_src_addr)); + end + + // + // ... + // + + //////////////////////////////////////// + // + // Errors or aborts -- do we properly end gracefully + // + // Make certain we don't deadlock here, but also that we wait + // for the last burst return before we clear + // + + always @(posedge S_AXI_ACLK) + if (f_past_valid && r_busy && $past(r_busy)) + begin + // Not allowed to set r_done while anything remains outstanding + if (!no_read_bursts_outstanding) + assert(!r_done); + if (write_bursts_outstanding > 0) + assert(!r_done); + + // + // If no returns are outstanding, and following an error, then + // r_done should be set + if ($past(r_busy && (r_err || r_abort)) + &&($past(!M_AXI_ARVALID && read_bursts_outstanding==0)) + &&($past(!M_AXI_AWVALID && write_bursts_outstanding==0))) + assert(r_done); + + if ($past(r_err || r_abort)) + begin + // + // Just double check that we aren't starting anything + // new following either an abort or an error + // + assert(!$rose(M_AXI_ARVALID)); + assert(!$rose(M_AXI_AWVALID)); + + if ($past(!M_AXI_WVALID || M_AXI_WREADY)) + assert(M_AXI_WSTRB == 0); + end + end + + // + // ... + // + + //////////////////////////////////////// + + // + // ... + // + + always @(*) + if (r_busy) + begin + if (readlen_w == 0) + assert(reads_remaining_w == 0); + else begin + assert(reads_remaining_w > 0); + if (!w_start_read) + begin + assert(readlen_w <= reads_remaining_w); + assert(readlen_w <= (1<<LGMAXBURST)); + end + end + end + + always @(*) + if (M_AXI_BVALID) + assert(M_AXI_BREADY); + + always @(*) + if (M_AXI_RVALID) + assert(M_AXI_RREADY); + + generate if (OPT_UNALIGNED) + begin : REALIGNMENT_CHECKS + + // + // ... + // + + end endgenerate + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // FIFO checks + // + + // + // ... + // + + always @(*) + if (phantom_read) + assert(M_AXI_ARVALID); + + always @(*) + if (phantom_write) + assert(M_AXI_AWVALID); + + //////////////////////////////////////////////////////////////////////// + // + // Combined + // + + always @(*) + if (r_busy) + begin + // + // ... + // + + assert(writes_remaining_w + write_bursts_outstanding + <= f_wrlength[LGLEN:ADDRLSB]); + + // + // ... + // + if (write_count > 0) + assert(M_AXI_WVALID); + // + // ... + // + end + + always @(*) + if (r_busy) + assert(last_write_ack == ((write_bursts_outstanding <= 1) + &&(writes_remaining_w == 0))); + + //////////////////////////////////////////////////////////////////////// + // + // Initial (only) constraints + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assert(!S_AXIL_BVALID); + assert(!S_AXIL_RVALID); + + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assert(!M_AXI_ARVALID); + + assert(write_bursts_outstanding == 0); + assert(write_requests_remaining == 0); + + assert(!phantom_read); + assert(!phantom_write); + assert(!r_busy); + assert(read_bursts_outstanding == 0); + assert(no_read_bursts_outstanding); + + assert(r_len == 0); + assert(zero_len); + + assert(write_count == 0); + assert(!M_AXI_WLAST); + assert(M_AXI_AWLEN == 0); + assert(!r_write_fifo); + assert(r_src_addr == 0); + assert(r_dst_addr == 0); + end + + always @(*) + assert(ADDRLSB + LGMAXBURST <= 12); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Formal contract checking + // {{{ + // Given an arbitrary address within the source address range, and an + // arbitrary piece of data at that source address, prove that said + // piece of data will get properly written to the destination address + // range + // + //////////////////////////////////////////////////////////////////////// + // + // + // We'll pick the byte read from const_addr_src, and then written to + // const_addr_dst. + // + generate if (OPT_UNALIGNED) + begin : F_UNALIGNED_SAMPLE_CHECK + (* anyconst *) reg [C_AXI_ADDR_WIDTH-1:0] f_const_posn; + reg [C_AXI_ADDR_WIDTH:0] f_const_addr_src, + f_const_addr_dst; + reg [8-1:0] f_const_byte; + reg [C_AXI_ADDR_WIDTH:0] f_write_fifo_addr, + f_read_fifo_addr, + f_partial_out_addr; + reg [C_AXI_DATA_WIDTH-1:0] f_shifted_read, f_shifted_write; + reg [C_AXI_DATA_WIDTH/8-1:0] f_shifted_wstrb; + reg [C_AXI_DATA_WIDTH-1:0] f_shifted_to_fifo, + f_shifted_partial_to_fifo, + f_shifted_partial_from_fifo; + reg [C_AXI_DATA_WIDTH-1:0] f_shifted_from_fifo, + f_shifted_from_partial_out; + reg [ADDRLSB-1:0] subshift; + + + always @(*) + begin + assume(f_const_posn < f_length); + + f_const_addr_src = f_src_addr + f_const_posn; + f_const_addr_dst = f_dst_addr + f_const_posn; + + f_shifted_read =(M_AXI_RDATA >> (8*f_const_addr_src[ADDRLSB-1:0])); + f_shifted_write=(M_AXI_WDATA >> (8*f_const_addr_dst[ADDRLSB-1:0])); + f_shifted_wstrb=(M_AXI_WSTRB >> (f_const_addr_dst[ADDRLSB-1:0])); + end + + //////////////////////////////////////////////////////////////// + // + // Step 1: Assume a known input + // Actually, we'll copy it when it comes in + // + always @(posedge S_AXI_ACLK) + if (M_AXI_RVALID + && f_read_beat_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + == f_const_addr_src[C_AXI_ADDR_WIDTH:ADDRLSB]) + begin + // Record our byte to be read + f_const_byte <= f_shifted_read[7:0]; + end + + //////////////////////////////////////////////////////////////// + // + // Step 2: Assert that value gets written on the way out + // + always @(*) + if (M_AXI_WVALID + && f_write_beat_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + == f_const_addr_dst[C_AXI_ADDR_WIDTH:ADDRLSB]) + begin + // Assert that we have the right byte in the end + if (!r_err && !r_abort) + begin + // Although it only really matters if we are + // actually writing it to the bus + assert(f_shifted_wstrb[0]); + assert(f_shifted_write[7:0] == f_const_byte); + end else + assert(f_shifted_wstrb[0] || M_AXI_WSTRB==0); + end + + + //////////////////////////////////////////////////////////////// + // + // Assert the write side of the realignment FIFO + // + always @(*) + begin + // + // ... + // + + subshift = f_const_posn[ADDRLSB-1:0]; + end + + always @(*) + f_shifted_to_fifo = REALIGNMENT_FIFO.r_realigned_incoming + >> (8*subshift); + + always @(*) + f_shifted_partial_to_fifo = REALIGNMENT_FIFO.r_partial_inword + >> (8*subshift); + + // + // ... + // + + always @(*) + if (S_AXI_ARESETN && r_write_fifo + && f_write_fifo_addr <= f_const_addr_src + && f_write_fifo_addr + (1<<ADDRLSB) > f_const_addr_src) + begin + // Assert that our special byte gets written to the FIFO + assert(f_const_byte == f_shifted_to_fifo[7:0]); + end + + //////////////////////////////////////////////////////////////// + // + // Assert the read side of the realignment FIFO + // + always @(*) + begin + f_read_fifo_addr =f_dst_addr; + f_read_fifo_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + = f_dst_addr[C_AXI_ADDR_WIDTH:ADDRLSB] + + (r_partial_outvalid ? 1:0) + + f_writes_complete; + + f_partial_out_addr = f_read_fifo_addr; + f_partial_out_addr[ADDRLSB-1:0] = 0; + end + + always @(*) + f_shifted_from_fifo = REALIGNMENT_FIFO.fifo_data + >> (8*subshift); + + always @(*) + f_shifted_from_partial_out + = REALIGNMENT_FIFO.r_partial_outword + >> (8*f_const_addr_dst[ADDRLSB-1:0]); + + + always @(*) + if (!fifo_empty && f_read_fifo_addr <= f_const_addr_dst + && f_read_fifo_addr + (1<<ADDRLSB) > f_const_addr_dst) + begin + // Assume that our special byte gets read from the FIFO + // That way we don't have to verify every element of the + // FIFO. We'll instead rely upon the FIFO working from + // here. + assume(f_const_byte == f_shifted_from_fifo[7:0]); + end + + // + // ... + // + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [3:0] f_cvr_rd_bursts, f_cvr_wr_bursts; + reg f_cvr_complete; + + always @(posedge S_AXI_ACLK) + if (i_reset) + f_cvr_wr_bursts <= 0; + else if (w_start) + f_cvr_wr_bursts <= 0; + else if (M_AXI_AWVALID && M_AXI_AWREADY && !f_cvr_wr_bursts[3]) + f_cvr_wr_bursts <= f_cvr_wr_bursts + 1; + + always @(posedge S_AXI_ACLK) + if (i_reset) + f_cvr_rd_bursts <= 0; + else if (w_start) + f_cvr_rd_bursts <= 0; + else if (M_AXI_ARVALID && M_AXI_ARREADY && !f_cvr_rd_bursts[3]) + f_cvr_rd_bursts <= f_cvr_rd_bursts + 1; + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy)) + cover(!r_busy && !r_err && !r_abort && f_cvr_wr_bursts[0] + && f_cvr_rd_bursts[0]); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy)) + cover(r_done && !r_err && !r_abort && f_cvr_wr_bursts[0] + && f_cvr_rd_bursts[0]); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy)) + cover(!r_busy && !r_err && !r_abort && &f_cvr_wr_bursts[1] + && f_cvr_rd_bursts[1]); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy)) + cover(!r_busy && !r_err && !r_abort && (&f_cvr_wr_bursts[1:0]) + && (&f_cvr_rd_bursts[1:0])); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy)) + cover(!r_busy && !r_err && !r_abort && f_cvr_wr_bursts[2] + && f_cvr_rd_bursts[2]); + + always @(*) + cover(f_past_valid && S_AXI_ARESETN && r_busy && r_err); + + always @(*) + cover(f_past_valid && S_AXI_ARESETN && r_busy + && M_AXI_RVALID && M_AXI_RRESP[1]); + + always @(*) + cover(f_past_valid && S_AXI_ARESETN && r_busy + && M_AXI_RVALID && M_AXI_BRESP[1]); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy && r_err)) + cover(!r_busy && r_err); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && r_busy && r_abort)) + cover(!r_busy && r_abort); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && r_busy && !r_abort && !r_err) + cover(reads_remaining_w == 0); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && r_busy && !r_abort && !r_err) + cover(reads_remaining_w == 0 && fifo_empty); + + generate if (OPT_UNALIGNED) + begin : COVER_MISALIGNED_COPYING + + reg [2:0] cvr_opt_checks; + integer ik; + + always @(*) + begin + cvr_opt_checks[0] = (f_src_addr[ADDRLSB-1:0] == 0); + cvr_opt_checks[1] = (f_dst_addr[ADDRLSB-1:0] == 0); + cvr_opt_checks[2] = (f_length[ ADDRLSB-1:0] == 0); + end + + always @(posedge S_AXI_ACLK) + if (f_past_valid && S_AXI_ARESETN && o_int) + begin + for(ik=0; ik<8; ik=ik+1) + begin + cover(cvr_opt_checks == ik[2:0] + && !r_busy && r_err && !r_abort + && f_cvr_wr_bursts[0] + && f_cvr_rd_bursts[0]); + + cover(cvr_opt_checks == ik[2:0] + && !r_busy && !r_err && r_abort + && f_cvr_wr_bursts[0] + && f_cvr_rd_bursts[0]); + + cover(cvr_opt_checks == ik[2:0] + && !r_busy && !r_err && !r_abort + && f_cvr_wr_bursts[0] + && f_cvr_rd_bursts[0]); + + cover(cvr_opt_checks == ik[2:0] + && !r_busy && !r_err && !r_abort + && f_cvr_wr_bursts[2] + && f_cvr_rd_bursts[2]); + end + end + + always @(posedge S_AXI_ACLK) + if (f_past_valid && S_AXI_ARESETN && o_int) + begin + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && f_extra_realignment_read + && f_extra_realignment_write); + + // + // Will never happen--since f_extra_realignment_read + // can only be true if f_extra_realignment_preread + // is also true + // + // cover(!r_busy && !r_err && !r_abort + // && !f_extra_realignment_preread + // && f_extra_realignment_read + // && f_extra_realignment_write); + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && !f_extra_realignment_read + && f_extra_realignment_write); + + cover(!r_busy && !r_err && !r_abort + && !f_extra_realignment_preread + && !f_extra_realignment_read + && f_extra_realignment_write); + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && f_extra_realignment_read + && !f_extra_realignment_write); + + // !preread && read will never happen + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && !f_extra_realignment_read + && !f_extra_realignment_write); + + cover(!r_busy && !r_err && !r_abort + && !f_extra_realignment_preread + && !f_extra_realignment_read + && !f_extra_realignment_write); + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && f_extra_realignment_read + && f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + + // !preread && read will never happen + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && !f_extra_realignment_read + && f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + + cover(!r_busy && !r_err && !r_abort + && !f_extra_realignment_preread + && !f_extra_realignment_read + && f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && f_extra_realignment_read + && !f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + + + // !preread && read will never happen + + cover(!r_busy && !r_err && !r_abort + && f_extra_realignment_preread + && !f_extra_realignment_read + && !f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + + cover(!r_busy && !r_err && !r_abort + && !f_extra_realignment_preread + && !f_extra_realignment_read + && !f_extra_realignment_write + && f_length[LGLEN-1:ADDRLSB] > 2); + end + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions (i.e. constraints) + // + //////////////////////////////////////////////////////////////////////// + // + // + + // None (currently) +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axidouble.v b/rtl/wb2axip/axidouble.v new file mode 100644 index 0000000..6842e71 --- /dev/null +++ b/rtl/wb2axip/axidouble.v @@ -0,0 +1,1406 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axidouble.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Create a special AXI slave which can be used to reduce crossbar +// logic for multiple simplified AXI slaves. This is a companion +// core to the similar axisingle core, but allowing the slave to +// decode the clock between multiple possible addresses. +// +// To use this, the slave must follow specific (simplified AXI) rules: +// +// Write interface +// -------------------- +// 1. The controller will guarantee that AWVALID == WVALID +// (You can connect AWVALID to WVALID when connecting to your core) +// 2. The controller will guarantee that AWID == 0 for the slave +// All ID logic will be handled internally +// 3. The controller will guarantee that AWLEN == 0 and WLAST == 1 +// Instead, the controller will handle all burst addressing +// internally +// 4. This makes AWBURST irrelevant +// 5. Other wires are simplified as well: AWLOCK=0, AWCACHE=3, AWQOS=0 +// 6. If OPT_EXCLUSIVE_ACCESS is set, the controller will handle lock +// logic internally +// 7. The slave must guarantee that AWREADY == WREADY = 1 +// (This core doesn't have AWREADY or WREADY inputs) +// 8. The slave must also guarantee that BVALID == $past(AWVALID) +// (This core internally generates BVALID, and so the slave's +// BVALID return is actually ignored.) +// 9. The controller will also guarantee that BREADY = 1 +// (This core doesn't have a BVALID input) +// +// The controller will maintain AWPROT in case the slave wants to +// disallow particular writes, and AWSIZE so the slave can know how many +// bytes are being accessed. +// +// Read interface +// -------------------- +// 1. The controller will guarantee that RREADY = 1 +// (This core doesn't have an RREADY output) +// 2. The controller will guarantee that ARID = 0 +// All IDs are handled internally +// 3. The controller will guarantee that ARLEN == 0 +// All burst logic is handled internally +// 4. As before, this makes ARBURST irrelevant +// 5. Other wires are simplified: ARLOCK=0, ARCACHE = 3, ARQOS=0, etc +// 6. The slave must guarantee that RVALID == $past(ARVALID) +// The controller actually ignores RVALID--but to be a valid slave, +// this must be assumed. +// 7. The slave must also guarantee that RLAST == 1 anytime RVALID +// +// As with the write side, the controller will fill in ARSIZE and ARPROT. +// They may be used or ignored by the slave. +// +// Why? This simplifies slave logic. Slaves may interact with the bus +// using only the logic below: +// +// always @(posedge S_AXI_ACLK) +// if (AWVALID) case(AWADDR) +// R1: slvreg_1 <= WDATA; +// R2: slvreg_2 <= WDATA; +// R3: slvreg_3 <= WDATA; +// R4: slvreg_4 <= WDATA; +// endcase +// +// always @(*) +// BRESP = 2'b00; // OKAY +// +// always @(posedge S_AXI_ACLK) +// if (ARVALID) +// case(ARADDR) +// R1: RDATA <= slvreg_1; +// R2: RDATA <= slvreg_2; +// R3: RDATA <= slvreg_3; +// R4: RDATA <= slvreg_4; +// endcase +// +// always @(*) +// RRESP = 2'b00; // OKAY +// +// This core will then keep track of the more complex bus logic, locking, +// burst length, burst ID's, etc, simplifying both slaves and connection +// logic. Slaves with the more complicated (and proper/accurate) logic, +// that follow the rules above, should have no problems with this +// additional logic. +// +// Performance: +// +// Throughput: The slave can sustain one read/write per clock as long as +// the upstream master keeps S_AXI_[BR]READY high. If S_AXI_[BR]READY +// ever drops, there's some flexibility provided by the return FIFO, so +// the master might not notice a drop in throughput until the FIFO fills. +// +// Latency: This core will create a four clock latency on all requests. +// +// Logic: Actual logic depends upon how this is set up and built. As +// parameterized below, this core can fit within 639 Xilinx 6-LUTs and +// 39 M-LUTs. +// +// Narrow bursts: This core supports narrow bursts by nature. Whether the +// subcores pay attention to WSTRB, AWSIZE, and ARSIZE is up to the +// subcore itself. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axidouble #( + // {{{ + parameter integer C_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 32, + parameter integer C_AXI_ID_WIDTH = 1, + // + // NS is the number of slave interfaces. If you are interested + // in a single slave interface, checkout the demofull.v core + // in this same repository. + parameter NS = 8, + // + // OPT_LOWPOWER is generated by the interconnect, so we need + // to define it here. + parameter [0:0] OPT_LOWPOWER = 1'b0, + // + // Shorthand for address width, data width, and id width + // AW, and DW, are short-hand abbreviations used locally. + localparam AW = C_AXI_ADDR_WIDTH, + // + // Each of the slave interfaces has an address range. The + // base address for each slave is given by AW bits of SLAVE_ADDR + // below. + parameter [NS*AW-1:0] SLAVE_ADDR = { + { 3'b111, {(AW-3){1'b0}} }, + { 3'b110, {(AW-3){1'b0}} }, + { 3'b101, {(AW-3){1'b0}} }, + { 3'b100, {(AW-3){1'b0}} }, + { 3'b011, {(AW-3){1'b0}} }, + { 3'b010, {(AW-3){1'b0}} }, + { 4'b0001,{(AW-4){1'b0}} }, + { 4'b0000,{(AW-4){1'b0}} } }, + // + // + // The relevant bits of the slave address are given in + // SLAVE_MASK below, at AW bits per slave. To be valid, + // SLAVE_ADDR & ~SLAVE_MASK must be zero. Only the masked + // bits will be used in any compare. + // + // Also, while not technically required, it is strongly + // recommended that the bottom 12-bits of each AW bits of + // the SLAVE_MASK bust be zero. + parameter [NS*AW-1:0] SLAVE_MASK = + (NS <= 1) ? 0 + : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, + {(2){ 4'b1111, {(AW-4){1'b0}} }} + }, + // + // LGFLEN specifies the log (based two) of the number of + // transactions that may need to be held outstanding internally. + // If you really want high throughput, and if you expect any + // back pressure at all, then increase LGFLEN. Otherwise the + // default value of 3 (FIFO size = 8) should be sufficient + // to maintain full loading + parameter LGFLEN=3, + // + // This core will handle exclusive access if + // OPT_EXCLUSIVE_ACCESS is set to one. If set to 1, all + // subcores will have exclusive access applied. There is no + // core-by-core means of enabling exclusive access at this time. + parameter [0:0] OPT_EXCLUSIVE_ACCESS = 1'b1 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // Write address channel coming from upstream + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [8-1:0] S_AXI_AWLEN, + input wire [3-1:0] S_AXI_AWSIZE, + input wire [2-1:0] S_AXI_AWBURST, + input wire S_AXI_AWLOCK, + input wire [4-1:0] S_AXI_AWCACHE, + input wire [3-1:0] S_AXI_AWPROT, + input wire [4-1:0] S_AXI_AWQOS, + // + // Write data channel coming from upstream + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // + // Write responses sent back + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [2-1:0] S_AXI_BRESP, + // + // Read address request channel from upstream + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [8-1:0] S_AXI_ARLEN, + input wire [3-1:0] S_AXI_ARSIZE, + input wire [2-1:0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [4-1:0] S_AXI_ARCACHE, + input wire [3-1:0] S_AXI_ARPROT, + input wire [4-1:0] S_AXI_ARQOS, + // + // Read data return channel back upstream + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire S_AXI_RLAST, + output wire [2-1:0] S_AXI_RRESP, + // + // + // Now for the simplified downstream interface to a series + // of downstream slaves. All outgoing wires are shared between + // the slaves save the AWVALID and ARVALID signals. Slave + // returns are not shared. + // + // + // Simplified Write address channel. + output wire [NS-1:0] M_AXI_AWVALID, + // input wire M_AXI_AWREADY is assumed to be 1 + output wire [0:0] M_AXI_AWID,// = 0 + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [8-1:0] M_AXI_AWLEN,// = 0 + output wire [3-1:0] M_AXI_AWSIZE, + output wire [2-1:0] M_AXI_AWBURST,//=INC + output wire M_AXI_AWLOCK,// = 0 + output wire [4-1:0] M_AXI_AWCACHE,// = 0 + output wire [3-1:0] M_AXI_AWPROT,// = 0 + output wire [4-1:0] M_AXI_AWQOS,// = 0 + // + // Simplified write data channel + output wire [NS-1:0] M_AXI_WVALID,//=AWVALID + // input wire M_AXI_WVALID is *assumed* to be 1 + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST,// = 1 + // + // Simplified write response channel + // input wire M_AXI_BVALID is *assumed* to be + // $past(M_AXI_AWVALID), and so ignored + output wire M_AXI_BREADY,// = 1 + input wire [NS*2-1:0] M_AXI_BRESP, + // The controller handles BID, so this can be ignored as well + // + // Simplified read address channel + output wire [NS-1:0] M_AXI_ARVALID, + // input wire M_AXI_ARREADY is assumed to be 1 + output wire [0:0] M_AXI_ARID,// = 0 + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [8-1:0] M_AXI_ARLEN,// = 0 + output wire [3-1:0] M_AXI_ARSIZE, + output wire [2-1:0] M_AXI_ARBURST,//=INC + output wire M_AXI_ARLOCK,// = 0 + output wire [4-1:0] M_AXI_ARCACHE,// = 0 + output wire [3-1:0] M_AXI_ARPROT,// = 0 + output wire [4-1:0] M_AXI_ARQOS,// = 0 + // + // Simplified read data return channel + // input wire M_AXI_RVALID is assumed to be $past(ARVALID,1) + output wire M_AXI_RREADY,// = 1 + input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [NS*2-1:0] M_AXI_RRESP + // input wire M_AXI_RLAST is assumed to be 1 + // }}} + ); + + // Signal declarations + // {{{ + localparam DW = C_AXI_DATA_WIDTH; + localparam IW = C_AXI_ID_WIDTH; + // LGNS is the number of bits required in a slave index + localparam LGNS = (NS <= 1) ? 1 : $clog2(NS); + // + localparam [1:0] OKAY = 2'b00, + EXOKAY = 2'b01, + SLVERR = 2'b10, + INTERCONNECT_ERROR = 2'b11; + // localparam ADDR_LSBS = $clog2(DW)-3; + // + reg locked_burst, locked_write, lock_valid; + + // Write signals + // {{{ + wire awskd_stall; + wire awskid_valid, bffull, bempty, write_awskidready, + dcd_awvalid; + reg write_bvalid, write_response; + reg bfull, write_no_index; + wire [NS:0] raw_wdecode; + reg [NS:0] last_wdecode, wdecode; + wire [AW-1:0] m_awaddr; + wire [LGNS-1:0] write_windex; + reg [LGNS-1:0] write_bindex; + wire [3-1:0] awskid_prot, m_axi_awprot; + wire [LGFLEN:0] bfill; + reg [LGFLEN:0] write_count; + reg [1:0] write_resp; + // + reg [C_AXI_ID_WIDTH-1:0] write_id, write_bid, write_retid; + reg [C_AXI_ADDR_WIDTH-1:0] write_addr; + wire [C_AXI_ID_WIDTH-1:0] awskid_awid; + wire [C_AXI_ADDR_WIDTH-1:0] awskid_awaddr, next_waddr; + reg write_request, write_topofburst, + write_beat_bvalid; + reg [3-1:0] write_size; + reg [2-1:0] write_burst; + reg [8-1:0] write_len, write_awlen; + wire [8-1:0] awskid_awlen; + wire [2-1:0] awskid_awburst; + wire [3-1:0] awskid_awsize; + wire awskid_awlock; + // + reg write_top_beat; + // }}} + + // Read signals + // {{{ + wire rempty, rdfull; + wire [LGFLEN:0] rfill; + wire [LGNS-1:0] read_index; + reg [LGNS-1:0] last_read_index; + reg [1:0] read_resp; + reg [DW-1:0] read_rdata; + wire read_rwait, arskd_stall; + reg read_rvalid, read_result, read_no_index; + wire [AW-1:0] m_araddr; + reg [AW-1:0] araddr; + reg [3-1:0] arprot; + wire [NS:0] raw_rdecode; + + reg [C_AXI_ID_WIDTH-1:0] arid, read_rvid, read_retid; + reg [3-1:0] arsize; + reg [2-1:0] arburst; + reg arlock, read_rvlock; + reg read_rvlast, read_retlast; + reg [8-1:0] arlen, rlen; + wire [C_AXI_ADDR_WIDTH-1:0] next_araddr; + wire issue_read; + reg read_full; + reg [LGFLEN:0] read_count; + reg arvalid; + reg [NS:0] last_rdecode, rdecode; + + wire [0:0] unused_pin; + // }}} + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Unused wire assignments + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + assign M_AXI_AWID = 0; + assign M_AXI_AWLEN = 0; + assign M_AXI_AWBURST = 2'b00; + assign M_AXI_AWLOCK = 1'b0; + assign M_AXI_AWCACHE = 4'h3; + // assign M_AXI_AWPROT = 3'h0; + assign M_AXI_AWQOS = 4'h0; + // + assign M_AXI_WVALID = M_AXI_AWVALID; + assign M_AXI_WLAST = 1'b1; + // + assign M_AXI_BREADY = 1'b1; + // + assign M_AXI_ARID = 1'b0; + assign M_AXI_ARLEN = 8'h0; // Burst of one beat + assign M_AXI_ARBURST = 2'b00; // INC + assign M_AXI_ARLOCK = 1'b0; + assign M_AXI_ARCACHE = 4'h3; + // assign M_AXI_ARPROT = 3'h0; + assign M_AXI_ARQOS = 4'h0; + // + assign M_AXI_RREADY = -1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write logic: + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Incoming write address requests must go through a skidbuffer. By + // keeping OPT_OUTREG == 0, this shouldn't cost us any time, but should + // instead buy us the ability to keep AWREADY high even if for reasons + // we can't act on AWVALID & AWREADY on the same cycle + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .DW(C_AXI_ID_WIDTH+AW+8+3+2+1+3) + // }}} + ) awskid( + // {{{ + .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), + .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, + S_AXI_AWSIZE, S_AXI_AWBURST, + S_AXI_AWLOCK, S_AXI_AWPROT }), + .o_valid(awskid_valid), .i_ready(write_awskidready), + .o_data({ awskid_awid, awskid_awaddr, awskid_awlen, + awskid_awsize, awskid_awburst, awskid_awlock, + awskid_prot }) + // }}} + ); + + // write_addr and other write_* + // {{{ + // On any write address request (post-skidbuffer), copy down the details + // of that request. Once these details are valid (i.e. on the next + // clock), S_AXI_WREADY will be true. + always @(posedge S_AXI_ACLK) + if (awskid_valid && write_awskidready) + begin + write_id <= awskid_awid; + write_addr <= awskid_awaddr; + write_size <= awskid_awsize; + write_awlen <= awskid_awlen; + write_burst <= awskid_awburst; + // write_lock <= awskid_awlock; + + if (OPT_LOWPOWER && !awskid_valid) + begin + write_id <= {(C_AXI_ID_WIDTH){1'b0}}; + write_addr <= {(C_AXI_ADDR_WIDTH){1'b0}}; + write_size <= 3'h0; + write_awlen <= 8'h0; + write_burst <= 2'b00; + end + end else if (S_AXI_WVALID && S_AXI_WREADY) + // Following each write beat, we need to update our address + write_addr <= next_waddr; + // }}} + + // next_waddr from get_next_write_address + // {{{ + // Given the details of the address request, get the next address to + // write to. + axi_addr #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) + // }}} + ) get_next_write_address( + // {{{ + write_addr, + write_size, write_burst, write_awlen, next_waddr + // }}} + ); + // }}} + + + // write_request, write_topofburst, write_len + // {{{ + // Count through the beats of the burst in write_len. write_topofburst + // indicates the first beat in any new burst, but will be zero for all + // subsequent burst beats. write_request is true anytime we are trying + // to write. + initial write_request = 1'b0; + initial write_topofburst = 1'b1; + initial write_len = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + // {{{ + write_request <= 1'b0; + write_topofburst <= 1'b1; + write_len <= 0; + // }}} + end else if (write_awskidready) + begin + // {{{ + write_request <= awskid_valid; + write_topofburst <= awskid_valid; + write_len <= (awskid_valid) ? awskid_awlen : 8'h00; + // }}} + end else if (S_AXI_WVALID && S_AXI_WREADY) + begin + // {{{ + write_topofburst <= 1'b0; + if (S_AXI_WLAST) + write_request <= 1'b0; + if (write_len > 0) + write_len <= write_len - 1; + // }}} + end + // }}} + + // Slave address decoding + // {{{ + // Decode our incoming address in order to determine the next + // slave the address addresses + addrdecode #( + // {{{ + .AW(AW), .DW(3), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(1'b1) + // }}} + ) wraddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(awskid_valid && write_awskidready), .o_stall(awskd_stall), + // .i_addr(awskid_valid && write_awskidready + // ? awskid_awaddr : next_waddr), + .i_addr(awskid_awaddr), + .i_data(awskid_prot), + .o_valid(dcd_awvalid), .i_stall(!S_AXI_WVALID), + .o_decode(raw_wdecode), .o_addr(m_awaddr), + .o_data(m_axi_awprot) + // }}} + ); + // }}} + + // last_wdecode + // {{{ + // We only do our decode on the address request. We need the decoded + // values long after the top of the burst. Therefore, let's use + // dcd_awvalid to know we have a valid output from the decoder and + // then we'll latch that (register it really) for the rest of the burst + always @(posedge S_AXI_ACLK) + if (dcd_awvalid) + last_wdecode <= raw_wdecode; + // }}} + + // wdecode + // {{{ + always @(*) + begin + if (dcd_awvalid) + wdecode = raw_wdecode; + else + wdecode = last_wdecode; + end + // }}} + + // Downstream slave (write) signals + // {{{ + // It's now time to create our write request for the slave. Slave + // writes take place on the clock after address valid is true as long + // as S_AXI_WVALID is true. This places combinatorial logic onto the + // outgoing AWVALID. The sign that we are in the middle of a burst + // will specifically be that WREADY is true. + // + + // + // If there were any part of this algorithm I disliked it would be the + // AWVALID logic here. It shouldn't nearly be this loaded. + assign S_AXI_WREADY = write_request; + assign M_AXI_AWVALID = (S_AXI_WVALID && write_request + && (!locked_burst || locked_write)) ? wdecode[NS-1:0] : 0; + assign M_AXI_AWADDR = write_addr; + assign M_AXI_AWPROT = m_axi_awprot; + assign M_AXI_AWSIZE = write_size; + assign M_AXI_WDATA = S_AXI_WDATA; + assign M_AXI_WSTRB = S_AXI_WSTRB; + // }}} + + // write_awskidready + // {{{ + // We can accept a new value from the skid buffer as soon as the last + // write value comes in, or equivalently if we are not in the middle + // of a write. This is all subject, of course, to our backpressure + // FIFO not being full. + assign write_awskidready = ((S_AXI_WVALID&&S_AXI_WLAST) + || !S_AXI_WREADY) && !bfull; + // }}} + + // write_windex + // {{{ + // Back out an index from our decoded slave value + generate if (NS <= 1) + begin : WR_ONE_SLAVE + + assign write_windex = 0; + + end else begin : WR_INDEX + reg [LGNS-1:0] r_write_windex; + integer k; + + always @(*) + begin + r_write_windex = 0; + for(k=0; k<NS; k=k+1) + if (wdecode[k]) + r_write_windex = r_write_windex | k[LGNS-1:0]; + end + + assign write_windex = r_write_windex; + end endgenerate + // }}} + + always @(posedge S_AXI_ACLK) + begin + write_bindex <= write_windex; + write_no_index <= wdecode[NS]; + end + + always @(posedge S_AXI_ACLK) + // if (write_top_of_burst) // -- not necessary + write_bid <= write_id; + + // write_response, write_bvalid + // {{{ + // write_bvalid will be true one clock after the last write is accepted. + // This is the internal signal that would've come from a subordinate + // slave's BVALID, save that we are generating it internally. + initial { write_response, write_bvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { write_response, write_bvalid } <= 0; + else + { write_response, write_bvalid } <= { write_bvalid, + (S_AXI_WVALID && S_AXI_WREADY&& S_AXI_WLAST) }; + // }}} + + // write_top_beat, write_beat_bvalid + // {{{ + // Examine write beats, not just write bursts + always @(posedge S_AXI_ACLK) + begin + write_top_beat <= write_topofburst; + write_beat_bvalid <= S_AXI_WVALID && S_AXI_WREADY; + end + // }}} + + // write_resp + // {{{ + // The response from any burst should be an DECERR (interconnect + // error) if ever the addressed slave doesn't exist in our address map. + // This is sticky: any attempt to generate a request to a non-existent + // slave will generate an interconnect error. Likewise, if the slave + // ever returns a slave error, we'll propagate it back in the burst + // return. Finally, on an exclusive access burst, we'll return EXOKAY + // if we could write the values. + always @(posedge S_AXI_ACLK) + if (write_beat_bvalid) + begin + if (write_no_index) + write_resp <= INTERCONNECT_ERROR; + else if (M_AXI_BRESP[2*write_bindex]) + write_resp <= { 1'b1, (write_top_beat) + ? 1'b0 : write_resp[0] }; + else if (write_top_beat || !write_resp[1]) + write_resp <= { 1'b0, (write_top_beat && locked_burst && locked_write) }; + end else if (OPT_LOWPOWER) + write_resp <= 2'b00; + // }}} + + // write_retid + // {{{ + always @(posedge S_AXI_ACLK) + write_retid <= write_bid; + // }}} + + // write_count and bfull -- pseudo FIFO counters + // {{{ + // The pseudo-FIFO for the write side. This counter will let us know + // if any write response will ever overflow our write response FIFO, + // allowing us to be able to confidently deal with any backpressure. + initial write_count = 0; + initial bfull = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + write_count <= 0; + bfull <= 0; + end else case({ (awskid_valid && write_awskidready), + (S_AXI_BVALID & S_AXI_BREADY) }) + 2'b01: begin + write_count <= write_count - 1; + bfull <= 1'b0; + end + 2'b10: begin + write_count <= write_count + 1; + bfull <= (&write_count[LGFLEN-1:0]); + end + default: begin end + endcase + // }}} + + // Backpressure FIFO on write response returns + // {{{ + sfifo #( + // {{{ + .BW(C_AXI_ID_WIDTH+2), + .OPT_ASYNC_READ(0), + .LGFLEN(LGFLEN) + // }}} + ) bfifo ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(write_response), .i_data({ write_retid, write_resp }), + .o_full(bffull), .o_fill(bfill), + .i_rd(S_AXI_BVALID && S_AXI_BREADY), + .o_data({ S_AXI_BID, S_AXI_BRESP }), + .o_empty(bempty) + // }}} + ); + // }}} + + assign S_AXI_BVALID = !bempty; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // ar* + // {{{ + // Copy the burst information, for use in determining the next address + always @(posedge S_AXI_ACLK) + if (S_AXI_ARVALID && S_AXI_ARREADY) + begin + // {{{ + araddr <= S_AXI_ARADDR; + arid <= S_AXI_ARID; + arlen <= S_AXI_ARLEN; + arsize <= S_AXI_ARSIZE; + arburst <= S_AXI_ARBURST; + arlock <= S_AXI_ARLOCK && S_AXI_ARBURST == 2'b01; + arprot <= S_AXI_ARPROT; + // }}} + end else if (issue_read) + araddr <= next_araddr; + // }}} + + // rlen + // {{{ + // Count the number of remaining items in a burst. Note that rlen + // counts from N-1 to 0, not from N to 1. + initial rlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rlen <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + rlen <= S_AXI_ARLEN; + else if (issue_read && (rlen > 0)) + rlen <= rlen - 1; + // }}} + + // arvalid + // {{{ + // Should the slave M_AXI_ARVALID be true in general? Based upon + // rlen above, but still needs to be gated across all slaves. + initial arvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + arvalid <= 1'b0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + arvalid <= 1'b1; + else if (issue_read && (rlen == 0)) + arvalid <= 1'b0; + // }}} + + // next_araddr -- Get the next AXI address + // {{{ + axi_addr #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH) + // }}} + ) get_next_read_address( + // {{{ + araddr, + arsize, arburst, arlen, next_araddr + // }}} + ); + // }}} + + // raw_rdecode-- Decode which slave is being addressed by this read. + // {{{ + addrdecode #( + // {{{ + .AW(AW), .DW(1), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(1'b1) + // }}} + ) rdaddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID && S_AXI_ARREADY || rlen>0), + // Warning: there's no skid on this stall + .o_stall(arskd_stall), + .i_addr((S_AXI_ARVALID & S_AXI_ARREADY) + ? S_AXI_ARADDR : next_araddr), + .i_data(1'b0), + .o_valid(read_rwait), .i_stall(!issue_read), + .o_decode(raw_rdecode), .o_addr(m_araddr), + .o_data(unused_pin[0]) + // }}} + ); + // }}} + + // last_rdecode + // {{{ + // We want the value from the decoder on the first clock cycle. It + // may not be valid after that, so we'll hold on to it in last_rdecode + initial last_rdecode = 0; + always @(posedge S_AXI_ACLK) + if (read_rwait) + last_rdecode <= raw_rdecode; + // }}} + + // rdecode + // {{{ + always @(*) + if (read_rwait) + rdecode = raw_rdecode; + else + rdecode = last_rdecode; + // }}} + + // Finally, issue our read request any time the FIFO isn't full + // {{{ + assign issue_read = !read_full; + + assign M_AXI_ARVALID = issue_read ? rdecode[NS-1:0] : 0; + assign M_AXI_ARADDR = m_araddr; + assign M_AXI_ARPROT = arprot; + assign M_AXI_ARSIZE = arsize; + // }}} + + // read_rvalid, read_result + // {{{ + // read_rvalid would be the RVALID response from the slave that would + // be returned if we checked it. read_result is the same thing--one + // clock later. + initial { read_result, read_rvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_result, read_rvalid } <= 2'b00; + else + { read_result, read_rvalid } <= { read_rvalid, + (arvalid&issue_read) }; + // }}} + + // read_rvid, read_rvlast, read_rvlock + // {{{ + // On the same clock when rvalid is true, we'll also want to know + // if RLAST should be true (decoded here, not in the slave), and + // whether or not the transaction is locked. These values are valid + // any time read_rvalid is true. + always @(posedge S_AXI_ACLK) + begin + if (arvalid && issue_read) + begin + read_rvid <= arid; + read_rvlast <= (rlen == 0); + read_rvlock <= (read_rvlock && !read_rvlast) || (OPT_EXCLUSIVE_ACCESS && arlock && lock_valid); + end else if (read_rvlast) + read_rvlock <= 1'b0; + + if (!S_AXI_ARESETN) + begin + read_rvlock <= 1'b0; + read_rvlast <= 1'b1; + end + end + // }}} + + // read_retid, read_retlast + // {{{ + // read_result is true one clock after read_rvalid is true. Copy + // the ID and LAST values into this pipeline clock cycle + always @(posedge S_AXI_ACLK) + begin + read_retid <= read_rvid; + read_retlast <= read_rvlast; + end + // }}} + + // + // Decode the read value. + // + + // read_index - First step is to calculate the index of the slave + // {{{ + generate if (NS <= 1) + begin : RD_ONE_SLAVE + + assign read_index = 0; + + end else begin : RD_INDEX + reg [LGNS-1:0] r_read_index = 0; + integer k; + + always @(*) + begin + r_read_index = 0; + + for(k=0; k<NS; k=k+1) + if (rdecode[k]) + r_read_index = r_read_index | k[LGNS-1:0]; + end + + assign read_index = r_read_index; + end endgenerate + // }}} + + // last_read_index + // {{{ + // Keep this index into the RVALID cycle + always @(posedge S_AXI_ACLK) + last_read_index <= read_index; + // }}} + + // read_no_index is a flag to indicate that no slave was indexed. + // {{{ + always @(posedge S_AXI_ACLK) + read_no_index <= rdecode[NS]; + // }}} + + // read_rdata + // {{{ + // Now we can use last_read_index to determine the return data. + // read_rdata will be valid on the same clock $past(RVALID) or + // read_return cycle + always @(posedge S_AXI_ACLK) + read_rdata <= M_AXI_RDATA[DW*last_read_index +: DW]; + // }}} + + // read_resp + // {{{ + // As with read_rdata, read_resp is the response from the slave + always @(posedge S_AXI_ACLK) + if (read_no_index) + read_resp <= INTERCONNECT_ERROR; + else if (M_AXI_RRESP[2*last_read_index + 1]) + read_resp <= SLVERR; // SLVERR + else if (OPT_EXCLUSIVE_ACCESS && read_rvlock) + read_resp <= EXOKAY; // Exclusive access Okay + else + read_resp <= OKAY; // OKAY + // }}} + + // read_count, read_full + // {{{ + // Since we can't allow the incoming requests to overflow in the + // presence of any back pressure, let's create a phantom FIFO here + // counting the number of values either in the final pipeline or in + // final read FIFO. If read_full is true, the FIFO is full and we + // cannot move any more data forward. + initial { read_count, read_full } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_count, read_full } <= 0; + else case({ read_rwait && issue_read, S_AXI_RVALID & S_AXI_RREADY}) + 2'b10: begin + read_count <= read_count + 1; + read_full <= &read_count[LGFLEN-1:0]; + end + 2'b01: begin + read_count <= read_count - 1; + read_full <= 1'b0; + end + default: begin end + endcase + // }}} + + assign S_AXI_ARREADY = (rlen == 0) && !read_full; + + // Read return FIFO for dealing with backpressure + // {{{ + // Send the return results through a synchronous FIFO to handle + // back-pressure. Doing this costs us one clock of latency. + sfifo #( + // {{{ + .BW(C_AXI_ID_WIDTH+DW+1+2), + .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN) + // }}} + ) rfifo ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(read_result), .i_data({ read_retid, read_rdata, + read_retlast, read_resp }), + .o_full(rdfull), .o_fill(rfill), + .i_rd(S_AXI_RVALID && S_AXI_RREADY), + .o_data({ S_AXI_RID, S_AXI_RDATA, + S_AXI_RLAST, S_AXI_RRESP }), + .o_empty(rempty) + // }}} + ); + // }}} + + assign S_AXI_RVALID = !rempty; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Exclusive access / Bus locking logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_EXCLUSIVE_ACCESS) + begin : EXCLUSIVE_ACCESS + // {{{ + reg r_lock_valid, r_locked_burst; + reg [AW-1:0] lock_addr, lock_last; + reg [4-1:0] lock_len; + reg [3-1:0] lock_size; + reg [IW-1:0] lock_id; + + initial r_lock_valid = 1'b0; + initial r_locked_burst = 1'b0; + initial locked_write = 1'b0; + always @(posedge S_AXI_ACLK) + begin + + // + // Step one: Set the lock_valid signal. This means + // that a read request has been successful requesting + // the lock for this address. + // + if (awskid_valid && write_awskidready) + begin + // On any write to the value inside our lock + // range, disable the lock_valid signal + if ((awskid_awaddr + + ({ {(AW-4){1'b0}},awskid_awlen[3:0]} << S_AXI_AWSIZE) + >= lock_addr) + &&(S_AXI_AWADDR <= lock_last)) + r_lock_valid <= 0; + end + + if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLOCK + && S_AXI_ARBURST == 2'b01) + begin + r_lock_valid <= !locked_write; + lock_addr <= S_AXI_ARADDR; + lock_id <= S_AXI_ARID; + lock_size <= S_AXI_ARSIZE; + lock_len <= S_AXI_ARLEN[3:0]; + lock_last <= S_AXI_ARADDR + + ({ {(AW-4){1'b0}}, lock_len } + << S_AXI_ARSIZE); + end + + if (awskid_valid && write_awskidready) + begin + r_locked_burst <= 1'b0; + locked_write <= awskid_awlock; + + if (awskid_awlock) + begin + r_locked_burst <= r_lock_valid; + if (lock_addr != awskid_awaddr) + r_locked_burst <= 1'b0; + if (lock_id != awskid_awid) + r_locked_burst <= 1'b0; + if (lock_size != awskid_awsize) + r_locked_burst <= 1'b0; + if (lock_len != awskid_awlen[3:0]) + r_locked_burst <= 1'b0; + if (2'b01 != awskid_awburst) + r_locked_burst <= 1'b0; + end + + // Write if !locked_write || write_burst + // EXOKAY on locked_write && write_burst + // OKAY on all other writes where the slave + // does not assert an error + end else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) + r_locked_burst <= 1'b0; + + if (!S_AXI_ARESETN) + begin + r_lock_valid <= 1'b0; + r_locked_burst <= 1'b0; + end + end + + assign locked_burst = r_locked_burst; + assign lock_valid = r_lock_valid; + // }}} + end else begin : NO_EXCLUSIVE_ACCESS + // {{{ + // Keep track of whether or not the current burst requests + // exclusive access or not. locked_write is an important + // signal used to make certain that we do not write to our + // slave on any locked write requests. (Shouldn't happen, + // since we aren't returning any EXOKAY's from reads ...) + always @(posedge S_AXI_ACLK) + if (awskid_valid && write_awskidready) + locked_write <= awskid_awlock; + else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) + locked_write <= 1'b0; + + assign locked_burst = 0; + assign lock_valid = 0; + // }}} + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, + S_AXI_AWCACHE, S_AXI_ARCACHE, + S_AXI_AWQOS, S_AXI_ARQOS, + dcd_awvalid, m_awaddr, unused_pin, + bffull, rdfull, bfill, rfill, + awskd_stall, arskd_stall }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal verification properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = LGFLEN+9; + + // + // ... + // + + faxi_slave #( .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .F_AXI_MAXDELAY(5), + .F_LGDEPTH(F_LGDEPTH)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awid( S_AXI_AWID), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awlen( S_AXI_AWLEN), + .i_axi_awsize( S_AXI_AWSIZE), + .i_axi_awburst(S_AXI_AWBURST), + .i_axi_awlock( S_AXI_AWLOCK), + .i_axi_awcache(S_AXI_AWCACHE), + .i_axi_awprot( S_AXI_AWPROT), + .i_axi_awqos( S_AXI_AWQOS), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + .i_axi_wlast( S_AXI_WLAST), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_arid( S_AXI_ARID), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arlen( S_AXI_ARLEN), + .i_axi_arsize( S_AXI_ARSIZE), + .i_axi_arburst(S_AXI_ARBURST), + .i_axi_arlock( S_AXI_ARLOCK), + .i_axi_arcache(S_AXI_ARCACHE), + .i_axi_arprot( S_AXI_ARPROT), + .i_axi_arqos( S_AXI_ARQOS), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rid( S_AXI_RID), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rlast( S_AXI_RLAST), + .i_axi_rresp( S_AXI_RRESP) + // + // ... + // + ); + + // + // ... + // + + always @(*) + if (!OPT_EXCLUSIVE_ACCESS) + begin + assert(!S_AXI_BVALID || S_AXI_BRESP != EXOKAY); + assert(!S_AXI_RVALID || S_AXI_RRESP != EXOKAY); + end + + //////////////////////////////////////////////////////////////////////// + // + // Properties necessary to pass induction + // + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + assert($onehot0(M_AXI_AWVALID)); + + always @(*) + assert($onehot0(M_AXI_ARVALID)); + + // + // + // Write properties + // + // + + always @(*) + if (S_AXI_WVALID && S_AXI_WREADY) + begin + if (locked_burst && !locked_write) + assert(M_AXI_AWVALID == 0); + else if (wdecode[NS]) + assert(M_AXI_AWVALID == 0); + else begin + assert($onehot(M_AXI_AWVALID)); + assert(M_AXI_AWVALID == wdecode[NS-1:0]); + end + end else + assert(M_AXI_AWVALID == 0); + + // + // ... + // + + // + // + // Read properties + // + // + + // + // ... + // + + + //////////////////////////////////////////////////////////////////////// + // + // Simplifying (careless) assumptions + // + // Caution: these might void your proof + // + //////////////////////////////////////////////////////////////////////// + // + // + localparam [0:0] F_CHECK_WRITES = 1'b1; + localparam [0:0] F_CHECK_READS = 1'b1; + + generate if (!F_CHECK_WRITES) + begin + always @(*) + assume(!S_AXI_AWVALID); + always @(*) + assert(!S_AXI_BVALID); + always @(*) + assert(!M_AXI_AWVALID); + + // ... + end endgenerate + + generate if (!F_CHECK_READS) + begin + always @(*) + assume(!S_AXI_ARVALID); + always @(*) + assert(!S_AXI_RVALID); + always @(*) + assert(M_AXI_ARVALID == 0); + always @(*) + assert(rdecode == 0); + // ... + end endgenerate + + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + //////////////////////////////////////////////////////////////////////// + // + // + reg [3:0] cvr_arvalids, cvr_awvalids, cvr_reads, cvr_writes; + (* anyconst *) reg cvr_burst; + + always @(*) + if (cvr_burst && S_AXI_AWVALID) + assume(S_AXI_AWLEN > 2); + + always @(*) + if (cvr_burst && S_AXI_ARVALID) + assume(S_AXI_ARLEN > 2); + + initial cvr_awvalids = 0; + always @(posedge S_AXI_ACLK) + if (!cvr_burst || !S_AXI_ARESETN) + cvr_awvalids <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && !(&cvr_awvalids)) + cvr_awvalids <= cvr_awvalids + 1; + + initial cvr_arvalids = 0; + always @(posedge S_AXI_ACLK) + if (!cvr_burst || !S_AXI_ARESETN) + cvr_arvalids <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && !(&cvr_arvalids)) + cvr_arvalids <= cvr_arvalids + 1; + + initial cvr_writes = 0; + always @(posedge S_AXI_ACLK) + if (!cvr_burst || !S_AXI_ARESETN) + cvr_writes <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY && !(&cvr_writes)) + cvr_writes <= cvr_writes + 1; + + initial cvr_reads = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_reads <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST + && !(&cvr_arvalids)) + cvr_reads <= cvr_reads + 1; + + generate if (F_CHECK_WRITES) + begin : COVER_WRITES + + always @(*) + cover(cvr_awvalids > 2); + + always @(*) + cover(cvr_writes > 2); + + always @(*) + cover(cvr_writes > 4); + end endgenerate + + generate if (F_CHECK_READS) + begin : COVER_READS + always @(*) + cover(cvr_arvalids > 2); + + always @(*) + cover(cvr_reads > 2); + + always @(*) + cover(cvr_reads > 4); + end endgenerate + + always @(*) + cover((cvr_writes > 2) && (cvr_reads > 2)); + + generate if (OPT_EXCLUSIVE_ACCESS) + begin : COVER_EXCLUSIVE_ACCESS + + always @(*) + cover(S_AXI_BVALID && S_AXI_BRESP == EXOKAY); + + end endgenerate +`endif +endmodule diff --git a/rtl/wb2axip/axiempty.v b/rtl/wb2axip/axiempty.v new file mode 100644 index 0000000..d0ec896 --- /dev/null +++ b/rtl/wb2axip/axiempty.v @@ -0,0 +1,490 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axiempty.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A basic AXI core to provide a response to an AXI master when +// no other slaves are connected to the bus. All results are +// bus errors. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axiempty #( + // {{{ + parameter integer C_AXI_ID_WIDTH = 2, + parameter integer C_AXI_DATA_WIDTH = 32, + // Verilator lint_off UNUSED + parameter integer C_AXI_ADDR_WIDTH = 6, + // Verilator lint_on UNUSED + parameter [0:0] OPT_LOWPOWER = 0 + // Some useful short-hand definitions + // localparam AW = C_AXI_ADDR_WIDTH, + // localparam DW = C_AXI_DATA_WIDTH + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire S_AXI_WLAST, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [7:0] S_AXI_ARLEN, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire S_AXI_RLAST, + output wire [1:0] S_AXI_RRESP + // }}} + ); + + localparam IW = C_AXI_ID_WIDTH; + // Double buffer the write response channel only + reg [IW-1 : 0] axi_bid; + reg axi_bvalid; + + //////////////////////////////////////////////////////////////////////// + // + // Write logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Start with the two skid buffers + // {{{ + wire m_awvalid, m_wvalid; + wire m_awready, m_wready, m_wlast; + wire [IW-1:0] m_awid; + // + skidbuffer #(.DW(IW), .OPT_OUTREG(1'b0)) + awskd(S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_AWVALID, S_AXI_AWREADY, S_AXI_AWID, + m_awvalid, m_awready, m_awid ); + + skidbuffer #(.DW(1), .OPT_OUTREG(1'b0)) + wskd(S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_WVALID, S_AXI_WREADY, S_AXI_WLAST, + m_wvalid, m_wready, m_wlast ); + // }}} + + // m_awready, m_wready + // {{{ + // The logic here is pretty simple--accept a write address burst + // into the skid buffer, then leave it there while the write data comes + // on. Once we get to the last write data element, accept both it and + // the address. This spares us the trouble of counting out the elements + // in the write burst. + // + assign m_awready= (m_awvalid && m_wvalid && m_wlast) + && (!S_AXI_BVALID || S_AXI_BREADY); + assign m_wready = !m_wlast || m_awready; + // }}} + + // bvalid + // {{{ + // As soon as m_awready above, a packet has come through successfully. + // Acknowledge it with a bus error. + // + initial axi_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_bvalid <= 1'b0; + else if (m_awready) + axi_bvalid <= 1'b1; + else if (S_AXI_BREADY) + axi_bvalid <= 1'b0; + // }}} + + // bid + // {{{ + always @(posedge S_AXI_ACLK) + if (m_awready) + axi_bid <= m_awid; + // }}} + + assign S_AXI_BVALID = axi_bvalid; + assign S_AXI_BID = axi_bid; + assign S_AXI_BRESP = 2'b11; // An interconnect bus error + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read half + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [IW-1:0] rid, axi_rid; + reg axi_arready, axi_rlast, axi_rvalid; + reg [8:0] axi_rlen; + + // axi_arready + // {{{ + initial axi_arready = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arready <= 1; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_arready <= (S_AXI_ARLEN==0)&&(!S_AXI_RVALID|| S_AXI_RREADY); + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if ((!axi_arready)&&(S_AXI_RVALID)) + axi_arready <= (axi_rlen <= 2); + end + // }}} + + // axi_rlen + // {{{ + initial axi_rlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_rlen <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_rlen <= (S_AXI_ARLEN+1) + + ((S_AXI_RVALID && !S_AXI_RREADY) ? 1:0); + else if (S_AXI_RREADY && S_AXI_RVALID) + axi_rlen <= axi_rlen - 1; + // }}} + + // rid + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + rid <= 0; + else if (S_AXI_ARREADY && (!OPT_LOWPOWER || S_AXI_ARVALID)) + rid <= S_AXI_ARID; + else if (OPT_LOWPOWER && S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + rid <= 0; + // }}} + + // axi_rvalid + // {{{ + initial axi_rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_rvalid <= 0; + else if (S_AXI_ARVALID || (axi_rlen > 1)) + axi_rvalid <= 1; + else if (S_AXI_RREADY) + axi_rvalid <= 0; + // }}} + + // axi_rid + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + axi_rid <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_rid <= S_AXI_ARID; + else if (OPT_LOWPOWER && S_AXI_RVALID && S_AXI_RREADY + && S_AXI_RLAST) + axi_rid <= 0; + else + axi_rid <= rid; + end + // }}} + + // axi_rlast + // {{{ + initial axi_rlast = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + axi_rlast <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_rlast <= (S_AXI_ARLEN == 0); + else if (S_AXI_RVALID) + axi_rlast <= (axi_rlen == 2); + else + axi_rlast <= (axi_rlen == 1); + end + // }}} + + // + assign S_AXI_ARREADY = axi_arready; + assign S_AXI_RVALID = axi_rvalid; + assign S_AXI_RID = axi_rid; + assign S_AXI_RDATA = 0; + assign S_AXI_RRESP = 2'b11; + assign S_AXI_RLAST = axi_rlast; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // The following properties are only some of the properties used + // to verify this core + // + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + faxi_slave #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH) + // }}} + f_slave( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + // Address write channel + // {{{ + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awid( S_AXI_AWID), + .i_axi_awaddr( {(C_AXI_ADDR_WIDTH){1'b0}}), + .i_axi_awlen( S_AXI_AWLEN), + .i_axi_awsize( LSB[2:0]), + .i_axi_awburst(2'b0), + .i_axi_awlock( 1'b0), + .i_axi_awcache(4'h0), + .i_axi_awprot( 3'h0), + .i_axi_awqos( 4'h0), + // }}} + // Write Data Channel + // {{{ + // Write Data + .i_axi_wdata({(C_AXI_DATA_WIDTH){1'b0}}), + .i_axi_wstrb({(C_AXI_DATA_WIDTH/8){1'b0}}), + .i_axi_wlast(S_AXI_WLAST), + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + // }}} + // Write response + // {{{ + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + // }}} + // Read address channel + // {{{ + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_arid( S_AXI_ARID), + .i_axi_araddr( {(C_AXI_ADDR_WIDTH){1'b0}}), + .i_axi_arlen( S_AXI_ARLEN), + .i_axi_arsize( LSB[2:0]), + .i_axi_arburst(2'b00), + .i_axi_arlock( 1'b0), + .i_axi_arcache(4'h0), + .i_axi_arprot( 3'h0), + .i_axi_arqos( 4'h0), + // }}} + // Read data return channel + // {{{ + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rid(S_AXI_RID), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + .i_axi_rlast(S_AXI_RLAST), + // + // ... + // }}} + ); + + // + + //////////////////////////////////////////////////////////////////////// + // + // Write induction properties + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read induction properties + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + // ... + // + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $rose(S_AXI_RLAST)) + assert(S_AXI_ARREADY); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract checking + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + reg f_wr_cvr_valid, f_rd_cvr_valid; + + initial f_wr_cvr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_wr_cvr_valid <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN > 4) + f_wr_cvr_valid <= 1; + + always @(*) + cover(!S_AXI_BVALID && axi_awready && !m_awvalid + && f_wr_cvr_valid /* && ... */)); + + initial f_rd_cvr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_rd_cvr_valid <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN > 4) + f_rd_cvr_valid <= 1; + + always @(*) + cover(S_AXI_ARREADY && f_rd_cvr_valid /* && ... */); + + // + // Generate cover statements associated with multiple successive bursts + // + // These will be useful for demonstrating the throughput of the core. + // + reg [4:0] f_dbl_rd_count, f_dbl_wr_count; + + initial f_dbl_wr_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dbl_wr_count = 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN == 3) + begin + if (!(&f_dbl_wr_count)) + f_dbl_wr_count <= f_dbl_wr_count + 1; + end + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 1)); //! + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 3)); //! + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 3) && !m_awvalid + &&(!S_AXI_AWVALID && !S_AXI_WVALID && !S_AXI_BVALID) + && (f_axi_awr_nbursts == 0) + && (f_axi_wr_pending == 0)); //!! + + initial f_dbl_rd_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dbl_rd_count = 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN == 3) + begin + if (!(&f_dbl_rd_count)) + f_dbl_rd_count <= f_dbl_rd_count + 1; + end + + always @(*) + cover(!S_AXI_ARESETN && (f_dbl_rd_count > 3) + /* && ... */ + && !S_AXI_ARVALID && !S_AXI_RVALID); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assumptions necessary to pass a formal check + // + //////////////////////////////////////////////////////////////////////// + // + // + + // + // No limiting assumptions at present, check is currently full and + // complete + // +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axil2apb.v b/rtl/wb2axip/axil2apb.v new file mode 100644 index 0000000..d8a73ae --- /dev/null +++ b/rtl/wb2axip/axil2apb.v @@ -0,0 +1,717 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axil2apb.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: High throughput AXI-lite bridge to APB. With both skid +// buffers enabled, it can handle 50% throughput--the maximum +// that APB can handle. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axil2apb #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + // OPT_OUTGOING_SKIDBUFFER: required for 50% throughput + parameter [0:0] OPT_OUTGOING_SKIDBUFFER = 1'b0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The AXI-lite interface + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, + input wire [(C_AXI_DATA_WIDTH/8)-1:0] S_AXI_WSTRB, + // + output reg S_AXI_BVALID, + input wire S_AXI_BREADY, + output reg [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output reg S_AXI_RVALID, + input wire S_AXI_RREADY, + output reg [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output reg [1:0] S_AXI_RRESP, + // }}} + // + // The APB interface + // {{{ + output reg M_APB_PSEL, + output reg M_APB_PENABLE, + input wire M_APB_PREADY, + output reg [C_AXI_ADDR_WIDTH-1:0] M_APB_PADDR, + output reg M_APB_PWRITE, + output reg [C_AXI_DATA_WIDTH-1:0] M_APB_PWDATA, + output reg [C_AXI_DATA_WIDTH/8-1:0] M_APB_PWSTRB, + output reg [2:0] M_APB_PPROT, + input wire [C_AXI_DATA_WIDTH-1:0] M_APB_PRDATA, + input wire M_APB_PSLVERR + // }}} + // }}} + ); + + // Register declarations + // {{{ + localparam AW = C_AXI_ADDR_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + localparam AXILLSB = $clog2(C_AXI_DATA_WIDTH)-3; + wire awskd_valid, wskd_valid, arskd_valid; + reg axil_write_ready, axil_read_ready, + write_grant, apb_idle; + wire [AW-AXILLSB-1:0] awskd_addr, arskd_addr; + wire [DW-1:0] wskd_data; + wire [DW/8-1:0] wskd_strb; + wire [2:0] awskd_prot, arskd_prot; + reg apb_bvalid, apb_rvalid, apb_error, out_skid_full; + reg [DW-1:0] apb_data; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming AXI-lite write interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // awskd - write address skid buffer + // {{{ + skidbuffer #(.DW(C_AXI_ADDR_WIDTH-AXILLSB + 3), + .OPT_OUTREG(0) + ) awskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWADDR[AW-1:AXILLSB], S_AXI_AWPROT }), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data({ awskd_addr, awskd_prot })); + // }}} + + // wskd - write data skid buffer + // {{{ + skidbuffer #(.DW(DW+(DW/8)), + .OPT_OUTREG(0) + ) wskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + // }}} + + // apb_idle + // {{{ + always @(*) + begin + apb_idle = !M_APB_PSEL;// || (M_APB_PENABLE && M_APB_PREADY); + if (OPT_OUTGOING_SKIDBUFFER && (M_APB_PENABLE && M_APB_PREADY)) + apb_idle = 1'b1; + end + // }}} + + // axil_write_ready + // {{{ + always @(*) + begin + axil_write_ready = apb_idle; + if (S_AXI_BVALID && !S_AXI_BREADY) + axil_write_ready = 1'b0; + if (!awskd_valid || !wskd_valid) + axil_write_ready = 1'b0; + if (!write_grant && arskd_valid) + axil_write_ready = 1'b0; + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming AXI-lite read interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // arskd buffer + // {{{ + skidbuffer #(.DW(C_AXI_ADDR_WIDTH-AXILLSB+3), + .OPT_OUTREG(0) + ) arskd (.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data({ S_AXI_ARADDR[AW-1:AXILLSB], S_AXI_ARPROT }), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data({ arskd_addr, arskd_prot })); + // }}} + + // axil_read_ready + // {{{ + always @(*) + begin + axil_read_ready = apb_idle; + if (S_AXI_RVALID && !S_AXI_RREADY) + axil_read_ready = 1'b0; + if (write_grant && awskd_valid && wskd_valid) + axil_read_ready = 1'b0; + if (!arskd_valid) + axil_read_ready = 1'b0; + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Arbitrate among reads and writes --- alternating arbitration + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // write_grant -- alternates + // {{{ + always @(posedge S_AXI_ACLK) + if (apb_idle) + begin + if (axil_write_ready) + write_grant <= 1'b0; + else if (axil_read_ready) + write_grant <= 1'b1; + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Drive the APB bus + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // APB bus + // {{{ + initial M_APB_PSEL = 1'b0; + initial M_APB_PENABLE = 1'b0; + always @(posedge S_AXI_ACLK) + begin + if (apb_idle) + begin + M_APB_PSEL <= 1'b0; + if (axil_read_ready) + begin + M_APB_PSEL <= 1'b1; + M_APB_PADDR <= { arskd_addr, {(AXILLSB){1'b0}} }; + M_APB_PWRITE <= 1'b0; + M_APB_PPROT <= arskd_prot; + end else if (axil_write_ready) + begin + M_APB_PSEL <= 1'b1; + M_APB_PADDR <= { awskd_addr, {(AXILLSB){1'b0}} }; + M_APB_PWRITE <= 1'b1; + M_APB_PPROT <= awskd_prot; + end + + if (wskd_valid) + begin + M_APB_PWDATA <= wskd_data; + M_APB_PWSTRB <= wskd_strb; + end + + M_APB_PENABLE <= 1'b0; + end else if (!M_APB_PENABLE) + M_APB_PENABLE <= 1'b1; + else if (M_APB_PREADY) + begin // if (M_APB_PSEL && M_APB_ENABLE) + M_APB_PENABLE <= 1'b0; + M_APB_PSEL <= 1'b0; + end + + if (!S_AXI_ARESETN) + begin + M_APB_PSEL <= 1'b0; + M_APB_PENABLE <= 1'b0; + end + end + // }}} + + reg r_apb_bvalid, r_apb_rvalid, r_apb_error; + reg [DW-1:0] r_apb_data; + + generate if (OPT_OUTGOING_SKIDBUFFER) + begin : GEN_OSKID + // {{{ + // r_apb_bvalid, r_apb_rvalid, r_apb_error, r_apb_data + // {{{ + initial r_apb_bvalid = 1'b0; + initial r_apb_rvalid = 1'b0; + always @(posedge S_AXI_ACLK) + begin + if (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY) + begin + r_apb_bvalid <= (S_AXI_BVALID && !S_AXI_BREADY) && M_APB_PWRITE; + r_apb_rvalid <= (S_AXI_RVALID && !S_AXI_RREADY) && !M_APB_PWRITE; + if (!M_APB_PWRITE) + r_apb_data <= M_APB_PRDATA; + r_apb_error <= M_APB_PSLVERR; + end else begin + if (S_AXI_BREADY) + r_apb_bvalid <= 1'b0; + if (S_AXI_RREADY) + r_apb_rvalid <= 1'b0; + end + + if (!S_AXI_ARESETN) + begin + r_apb_bvalid <= 1'b0; + r_apb_rvalid <= 1'b0; + end + end + // }}} + + // apb_bvalid + // {{{ + always @(*) + apb_bvalid = (M_APB_PSEL && M_APB_PENABLE + && M_APB_PREADY && M_APB_PWRITE)|| r_apb_bvalid; + // }}} + + // apb_rvalid + // {{{ + always @(*) + apb_rvalid = (M_APB_PSEL && M_APB_PENABLE + && M_APB_PREADY && !M_APB_PWRITE)||r_apb_rvalid; + // }}} + + // apb_data + // {{{ + always @(*) + if (out_skid_full) + apb_data = r_apb_data; + else + apb_data = M_APB_PRDATA; + // }}} + + // apb_error + // {{{ + always @(*) + if (out_skid_full) + apb_error = r_apb_error; + else + apb_error = M_APB_PSLVERR; + // }}} + + always @(*) + out_skid_full = r_apb_bvalid || r_apb_rvalid; + // }}} + end else begin : NO_OSKID + // {{{ + + initial r_apb_bvalid = 1'b0; + initial r_apb_rvalid = 1'b0; + initial r_apb_error = 1'b0; + initial r_apb_data = 0; + always @(*) + begin + r_apb_bvalid = 1'b0; + r_apb_rvalid = 1'b0; + r_apb_error = 1'b0; + r_apb_data = 0; + + apb_bvalid = M_APB_PSEL && M_APB_PENABLE + && M_APB_PREADY && M_APB_PWRITE; + + apb_rvalid = M_APB_PSEL && M_APB_PENABLE + && M_APB_PREADY && !M_APB_PWRITE; + + apb_data = M_APB_PRDATA; + + apb_error = M_APB_PSLVERR; + + out_skid_full = 1'b0; + end + + // Verilator lint_off UNUSED + wire skd_unused; + assign skd_unused = &{ 1'b0, r_apb_bvalid, r_apb_rvalid, + r_apb_data, r_apb_error, out_skid_full }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite write return signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // BVALID + // {{{ + initial S_AXI_BVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_BVALID <= 1'b0; + else if (!S_AXI_BVALID || S_AXI_BREADY) + S_AXI_BVALID <= apb_bvalid; + // }}} + + // BRESP + // {{{ + initial S_AXI_BRESP = 2'b00; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_BRESP <= 2'b00; + else if (!S_AXI_BVALID || S_AXI_BREADY) + S_AXI_BRESP <= { apb_error, 1'b0 }; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite read return signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + + // RVALID + // {{{ + initial S_AXI_RVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_RVALID <= 1'b0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + S_AXI_RVALID <= apb_rvalid; + // }}} + + // RRESP + // {{{ + initial S_AXI_RRESP = 2'b00; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_RRESP <= 2'b00; + else if (!S_AXI_RVALID || S_AXI_RREADY) + S_AXI_RRESP <= { apb_error, 1'b0 }; + // }}} + + // RDATA + // {{{ + always @(posedge S_AXI_ACLK) + if ((!S_AXI_RVALID || S_AXI_RREADY) && apb_rvalid) + S_AXI_RDATA <= apb_data; + // }}} + + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_AWADDR[AXILLSB-1:0], S_AXI_ARADDR[AXILLSB-1:0] + }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = 3; + + wire [F_LGDEPTH-1:0] faxi_rd_outstanding, + faxi_wr_outstanding, + faxi_awr_outstanding; + reg f_past_valid; + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite interface properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + faxil_slave #( + // {{{ + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH (C_AXI_DATA_WIDTH), + .F_OPT_COVER_BURST(4), + .F_AXI_MAXWAIT(17), + .F_AXI_MAXDELAY(17), + .F_AXI_MAXRSTALL(3), + .F_LGDEPTH(F_LGDEPTH) + // }}} + ) faxil ( + // {{{ + .i_clk(S_AXI_ACLK), // System clock + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr(S_AXI_AWADDR), + .i_axi_awprot(S_AXI_AWPROT), + // + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + .i_axi_wvalid(S_AXI_WVALID), + // + .i_axi_bresp(S_AXI_BRESP), + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arprot(S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rresp(S_AXI_RRESP), + .i_axi_rdata(S_AXI_RDATA), + // + .f_axi_rd_outstanding(faxi_rd_outstanding), + .f_axi_wr_outstanding(faxi_wr_outstanding), + .f_axi_awr_outstanding(faxi_awr_outstanding) + // }}} + ); + + // Correlate outstanding counters against our state + // {{{ + always @(*) + if (S_AXI_ARESETN) + begin + assert(faxi_awr_outstanding == (S_AXI_AWREADY ? 0:1) + + (r_apb_bvalid ? 1:0) + + (S_AXI_BVALID ? 1:0) + + ((M_APB_PSEL && M_APB_PWRITE) ? 1:0)); + + assert(faxi_wr_outstanding == (S_AXI_WREADY ? 0:1) + + (r_apb_bvalid ? 1:0) + + (S_AXI_BVALID ? 1:0) + + ((M_APB_PSEL && M_APB_PWRITE) ? 1:0)); + + assert(faxi_rd_outstanding == (S_AXI_ARREADY ? 0:1) + + (r_apb_rvalid ? 1:0) + + (S_AXI_RVALID ? 1:0) + + ((M_APB_PSEL && !M_APB_PWRITE) ? 1:0)); + + if (r_apb_bvalid) + assert(S_AXI_BVALID); + if (r_apb_rvalid) + assert(S_AXI_RVALID); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // APB interface properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + fapb_master #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .F_OPT_MAXSTALL(3) + // }}} + ) fapb ( + // {{{ + .PCLK(S_AXI_ACLK), .PRESETn(S_AXI_ARESETN), + .PSEL( M_APB_PSEL), + .PENABLE(M_APB_PENABLE), + .PREADY( M_APB_PREADY), + .PADDR( M_APB_PADDR), + .PWRITE( M_APB_PWRITE), + .PWDATA( M_APB_PWDATA), + .PWSTRB( M_APB_PWSTRB), + .PPROT( M_APB_PPROT), + .PRDATA( M_APB_PRDATA), + .PSLVERR(M_APB_PSLVERR) + // }}} + ); + + always @(*) + if (!M_APB_PSEL) + assert(!M_APB_PENABLE); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Induction invariants + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + assert(!axil_write_ready || !axil_read_ready); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + (* anyconst *) reg f_never_test; + (* anyconst *) reg [AW-1:0] f_never_addr; + (* anyconst *) reg [DW-1:0] f_never_data; + (* anyconst *) reg [DW/8-1:0] f_never_strb; + (* anyconst *) reg [2:0] f_never_prot; + + // Assume the never values are never received + // {{{ + always @(*) + if (f_never_test) + begin + assume(f_never_addr[AXILLSB-1:0] == 0); + + if (S_AXI_AWVALID) + begin + assume(S_AXI_AWADDR[AW-1:AXILLSB] != f_never_addr[AW-1:AXILLSB]); + assume(S_AXI_AWPROT != f_never_prot); + end + + if (S_AXI_WVALID) + begin + assume(S_AXI_WDATA != f_never_data); + assume(S_AXI_WSTRB != f_never_strb); + end + + if (S_AXI_ARVALID) + begin + assume(S_AXI_ARADDR[AW-1:AXILLSB] != f_never_addr[AW-1:AXILLSB]); + assume(S_AXI_ARPROT != f_never_prot); + end + + if (M_APB_PSEL && M_APB_PENABLE && M_APB_PREADY&& !M_APB_PWRITE) + assume(M_APB_PRDATA != f_never_data); + end + // }}} + + // Assert the never values are never in the incoming skid buffers + // {{{ + always @(*) + if (f_never_test) + begin + if (awskd_valid) + begin + assert(awskd_addr != f_never_addr[AW-1:AXILLSB]); + assert(awskd_prot != f_never_prot); + end + + if (wskd_valid) + begin + assert(wskd_data != f_never_data); + assert(wskd_strb != f_never_strb); + end + + if (arskd_valid) + begin + assert(arskd_addr != f_never_addr[AW-1:AXILLSB]); + assert(arskd_prot != f_never_prot); + end + + if (r_apb_rvalid) + assert(r_apb_data != f_never_data); + end + // }}} + + // Assert the never values are never output + // {{{ + always @(*) + if (f_never_test) + begin + if (M_APB_PSEL) + begin + assert(M_APB_PADDR != f_never_addr); + assert(M_APB_PPROT != f_never_prot); + if (M_APB_PWRITE) + begin + assert(M_APB_PWDATA != f_never_data); + assert(M_APB_PWSTRB != f_never_strb); + end + end + + if (S_AXI_RVALID) + assert(S_AXI_RDATA != f_never_data); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // None (yet) + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axil2axis.v b/rtl/wb2axip/axil2axis.v new file mode 100644 index 0000000..0e1d14f --- /dev/null +++ b/rtl/wb2axip/axil2axis.v @@ -0,0 +1,883 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axil2axis +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Demonstrates a simple AXI-Lite interface to drive an AXI-Stream +// channel. This can then be used to debug DSP processing. +// +// Registers: This AXI-lite to AXI-Stream core supports four word-sized +// addresses. Byte enables are ignored. +// +// 2'b00, ADDR_SINK +// Writes to this register will send data to the stream master, +// with TLAST clear. Data goes first through a FIFO. If the +// FIFO is full, the write will stall. If it stalls OPT_TIMEOUT +// cycles, the write will fail and return a bus error. +// +// Reads from this register will return data from the stream slave, +// but without consuming it. Values read here may still be read +// from the ADDR_SOURCE register later. +// +// 2'b01, ADDR_SOURCE +// Writes to this register will send data downstream to the stream +// master as well, but this time with TLAST set. +// +// Reads from this register will accept a value from the stream +// slave interface. The read value contains TDATA. TLAST is +// ignored in this read. If you want access to TLAST, you can get +// it from the ADDR_FIFO register. +// +// If there is no data to be read, the read will not and does not +// stall. It will instead return a bus error. +// +// 2'b10, ADDR_STATS +// Since we can, we'll handle some statistics here. The top half +// word contains two counters: a 4-bit counter of TLAST's issued +// from the stream master, and a 12-bit counter of TDATA values +// issued. Neither counter includes data still contained in the +// FIFO. If the OPT_SOURCE option is clear, these values will +// always be zero. +// +// The second (bottom, or least-significant) halfword contains the +// same regarding the stream slave. If OPT_SINK is set, these +// counters count values read from the core. If OPT_SINK is clear, +// so that the stream sink is not truly implemented, then TREADY +// will be held high and the counter will just count values coming +// into the core never going into the FIFO. +// +// 2'b11, ADDR_FIFO +// Working with the core can be a challenge. You want to make +// certain that writing to the core doesn't hang the design, and +// that reading from the core doesn't cause a bus error. +// +// Bits 31:16 contain the number of items in the write FIFO, and +// bits 14:0 contain the number of items in the read FIFO. +// +// Bit 15 contains whether or not the next item to be read is +// the last item in a packet, i.e. with TLAST set. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axil2axis #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + parameter C_AXI_ADDR_WIDTH = 4, + localparam C_AXI_DATA_WIDTH = 32, + parameter C_AXIS_DATA_WIDTH = 16, + // + // OPT_SOURCE enables the AXI stream master logic. If not + // enabled, M_AXI_TVALID will be held at zero, and the stream + // master logic may be ignored. + parameter [0:0] OPT_SOURCE = 1'b1, + // + // OPT_SINK enables the AXI stream slave logic. If not enabled, + // reads will always return zero, and S_AXIS_TREADY will be + // held high. + parameter [0:0] OPT_SINK = 1'b1, + // + // If OPT_SIGN_EXTEND is set, values received will be sign + // extended to fill the full data width on read. Otherwise + // the most significant of any unused bits will remain clear. + parameter [0:0] OPT_SIGN_EXTEND = 1'b0, + // + // Data written to this core will be placed into a FIFO before + // entering the AXI stream master. LGFIFO is the log, based + // two, of the number of words in this FIFO. Similarly, data + // consumed by AXI stream slave contained in this core will go + // first into a read FIFO. Reads from the core will then return + // data from this FIFO, or a bus error if none is available. + parameter LGFIFO = 5, + // + // OPT_TIMEOUT, if non-zero, will allow writes to the stream + // master, or reads from the stream slave, to stall the core + // for OPT_TIMEOUT cycles for the stream to be ready. If the + // stream isn't ready at this time (i.e. if the write FIFO is + // still full, or the read FIFO still empty), the result will + // be returned as a bus error. Likewise, if OPT_TIMEOUT==0, + // the core will always return a bus error if ever the write + // FIFO is full or the read FIFO empty. + parameter OPT_TIMEOUT = 5, + // + // OPT_LOWPOWER sets outputs to zero if not valid. This applies + // to the AXI-lite bus, however, and not the AXI stream FIFOs, + // since those don't have LOWPOWER support (currently). + parameter [0:0] OPT_LOWPOWER = 0 + // + // This design currently ignores WSTRB, beyond checking that it + // is not zero. I see no easy way to add it. (I'll leave that + // to you to implement, if you wish.) + // parameter [0:0] OPT_WSTRB = 0, + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI-lite signals + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP, + // }}} + // AXI stream slave (sink) signals + // {{{ + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire S_AXIS_TLAST, + // }}} + // AXI stream master (source) signals + // {{{ + output wire M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, + output reg M_AXIS_TLAST + // }}} + // }}} + ); + + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + localparam [1:0] ADDR_SINK = 2'b00, // Read from stream + ADDR_SOURCE = 2'b01, // Write, also sets TLAST + ADDR_STATS = 2'b10, + ADDR_FIFO = 2'b11; + localparam SW = C_AXIS_DATA_WIDTH; + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + wire i_reset = !S_AXI_ARESETN; + + wire axil_write_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] awskd_addr; + // + wire [C_AXI_DATA_WIDTH-1:0] wskd_data; + wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid, axil_berr; + // + wire axil_read_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] arskd_addr; + reg [C_AXI_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + + wire awskd_valid, wskd_valid; + wire wfifo_full, wfifo_write, wfifo_empty; + wire [LGFIFO:0] wfifo_fill; + reg write_timeout; + + wire read_timeout; + reg axil_rerr; + reg [3:0] read_bursts_completed; + reg [11:0] reads_completed; + + wire [3:0] write_bursts_completed; + wire [11:0] writes_completed; + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write signaling + // + // {{{ + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data(S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXI_BVALID || S_AXI_BREADY) + && ((awskd_addr[1] != ADDR_SOURCE[1]) + || (!wfifo_full || write_timeout)); + + // + // Write timeout generation + // + // {{{ + generate if ((OPT_TIMEOUT > 1) && OPT_SOURCE) + begin : GEN_WRITE_TIMEOUT + reg r_write_timeout; + reg [$clog2(OPT_TIMEOUT)-1:0] write_timer; + + initial write_timer = OPT_TIMEOUT-1; + initial r_write_timeout = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + write_timer <= OPT_TIMEOUT-1; + r_write_timeout<= 1'b0; + end else if (!awskd_valid || !wfifo_full || !wskd_valid + || (awskd_addr[1] != ADDR_SOURCE[1]) + || (S_AXI_BVALID && !S_AXI_BREADY)) + begin + write_timer <= OPT_TIMEOUT-1; + r_write_timeout<= 1'b0; + end else begin + if (write_timer > 0) + write_timer <= write_timer - 1; + r_write_timeout <= (write_timer <= 1); + end + + assign write_timeout = r_write_timeout; +`ifdef FORMAL + always @(*) + assert(write_timer <= OPT_TIMEOUT-1); + always @(*) + assert(write_timeout == (write_timer == 0)); +`endif + end else begin : NO_WRITE_TIMEOUT + + assign write_timeout = 1'b1; + + end endgenerate + // }}} + + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXI_BREADY) + axil_bvalid <= 0; + + assign S_AXI_BVALID = axil_bvalid; + + initial axil_berr = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && i_reset) + axil_berr <= 0; + else if (axil_write_ready) + axil_berr <= (wfifo_full)&&(awskd_addr[1]==ADDR_SOURCE[1]); + else if (OPT_LOWPOWER && S_AXI_BREADY) + axil_berr <= 1'b0; + + assign S_AXI_BRESP = { axil_berr, 1'b0 }; + // }}} + + // + // AXI-stream source (Write) FIFO + // + // {{{ + assign wfifo_write = axil_write_ready && awskd_addr[1]==ADDR_SOURCE[1] + && wskd_strb != 0 && !wfifo_full; + + generate if (OPT_SOURCE) + begin : GEN_SOURCE_FIFO + + sfifo #(.BW(SW+1), .LGFLEN(LGFIFO)) + source(.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(wfifo_write), + .i_data({awskd_addr[0]==ADDR_SOURCE[0], + wskd_data[SW-1:0]}), + .o_full(wfifo_full), .o_fill(wfifo_fill), + .i_rd(M_AXIS_TREADY), + .o_data({ M_AXIS_TLAST, M_AXIS_TDATA }), + .o_empty(wfifo_empty)); + + assign M_AXIS_TVALID = !wfifo_empty; + + end else begin : NO_SOURCE_FIFO + + assign M_AXIS_TVALID = 1'b0; + assign M_AXIS_TDATA = 0; + assign M_AXIS_TLAST = 0; + + assign wfifo_full = 1'b0; + assign wfifo_fill = 0; + + end endgenerate + // }}} + + // + // AXI-stream consumer/sink (Read) FIFO + // + // {{{ + wire rfifo_empty, rfifo_full, rfifo_last, read_rfifo; + wire [LGFIFO:0] rfifo_fill; + wire [SW-1:0] rfifo_data; + + generate if (OPT_SINK) + begin : GEN_SINK_FIFO + + sfifo #(.BW(SW+1), .LGFLEN(LGFIFO)) + sink(.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(S_AXIS_TVALID && S_AXIS_TREADY), + .i_data({S_AXIS_TLAST, S_AXIS_TDATA}), + .o_full(rfifo_full), .o_fill(rfifo_fill), + .i_rd(read_rfifo), + .o_data({ rfifo_last, rfifo_data }), + .o_empty(rfifo_empty)); + + assign S_AXIS_TREADY = !rfifo_full; + assign read_rfifo =(axil_read_ready && arskd_addr== ADDR_SINK) + && !rfifo_empty; + + end else begin : NO_SINK + + assign S_AXIS_TREADY = 1'b1; + + assign rfifo_empty = 1'b1; + assign rfifo_data = 0; + assign rfifo_last = 1'b1; + assign rfifo_fill = 0; + + end endgenerate + // }}} + + // + // Read timeout generation + // + // {{{ + generate if (OPT_SINK && OPT_TIMEOUT > 1) + begin : GEN_READ_TIMEOUT + reg [$clog2(OPT_TIMEOUT)-1:0] read_timer; + reg r_read_timeout; + + initial read_timer = OPT_TIMEOUT-1; + initial r_read_timeout = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + read_timer <= OPT_TIMEOUT-1; + r_read_timeout<= 1'b0; + end else if (!arskd_valid || (S_AXI_RVALID && !S_AXI_RREADY) + ||!rfifo_empty + ||(arskd_addr[1] != ADDR_SINK[1])) + begin + read_timer <= OPT_TIMEOUT-1; + r_read_timeout<= 1'b0; + end else begin + if (read_timer > 0) + read_timer <= read_timer - 1; + r_read_timeout <= (read_timer <= 1); + end + + assign read_timeout = r_read_timeout; + +`ifdef FORMAL + always @(*) + assert(read_timer <= OPT_TIMEOUT-1); + always @(*) + assert(read_timeout == (read_timer == 0)); +`endif + end else begin : NO_READ_TIMEOUT + + assign read_timeout = 1'b1; + + end endgenerate + // }}} + + // + // Read signaling + // + // {{{ + wire arskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data(S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!S_AXI_RVALID || S_AXI_RREADY) + && ((arskd_addr[1] != ADDR_SINK[1]) + || (!rfifo_empty || read_timeout)); + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXI_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXI_RVALID = axil_read_valid; + + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_rerr <= 1'b0; + else if (axil_read_ready) + axil_rerr <= rfifo_empty && (arskd_addr[1] == ADDR_SINK[1]); + else if (OPT_LOWPOWER && S_AXI_RREADY) + axil_rerr <= 1'b0; + + assign S_AXI_RRESP = { axil_rerr, 1'b0 }; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Read data counting : reads_completed and read_bursts_completed + // {{{ + initial reads_completed = 0; + initial read_bursts_completed = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + // {{{ + reads_completed <= 0; + read_bursts_completed <= 0; + // }}} + end else if (!OPT_SINK) + begin + // {{{ + reads_completed <= reads_completed + (S_AXIS_TVALID ? 1:0); + read_bursts_completed <= read_bursts_completed + + ((S_AXIS_TVALID && S_AXIS_TLAST) ? 1:0); + // }}} + end else if (read_rfifo && !rfifo_empty) + begin + // {{{ + reads_completed <= reads_completed + 1; + read_bursts_completed <= read_bursts_completed + (rfifo_last ? 1:0); + // }}} + end + // }}} + + // + // Write data counting + // {{{ + generate if (OPT_SOURCE) + begin : GEN_WRITES_COMPLETED + // {{{ + reg [3:0] r_write_bursts_completed; + reg [11:0] r_writes_completed; + + initial r_writes_completed = 0; + initial r_write_bursts_completed = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_writes_completed <= 0; + r_write_bursts_completed <= 0; + end else if (M_AXIS_TVALID && M_AXIS_TREADY) + begin + r_writes_completed <= writes_completed + 1; + r_write_bursts_completed <= write_bursts_completed + + (M_AXIS_TLAST ? 1:0); + end + + assign writes_completed = r_writes_completed; + assign write_bursts_completed = r_write_bursts_completed; + // }}} + end else begin : NO_COMPLETION_COUNTERS // No AXI-stream source logic + // {{{ + assign writes_completed = 0; + assign write_bursts_completed = 0; + // }}} + end endgenerate + // }}} + + // + // Read data register + // {{{ + initial axil_read_data = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_read_data <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + axil_read_data <= 0; + casez(arskd_addr) + { ADDR_SINK[1], 1'b? }: begin + if (OPT_SIGN_EXTEND && rfifo_data[SW-1]) + axil_read_data <= -1; + axil_read_data[SW-1:0] <= rfifo_data; + end + ADDR_STATS: begin + axil_read_data[31:28] <= write_bursts_completed; + axil_read_data[27:16] <= writes_completed; + axil_read_data[15:12] <= read_bursts_completed; + axil_read_data[11:0] <= reads_completed; + end + ADDR_FIFO: begin + // FIFO information + axil_read_data[16 +: LGFIFO+1] <= wfifo_fill; + axil_read_data[15] <= rfifo_last; + axil_read_data[LGFIFO:0] <= rfifo_fill; + end + endcase + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + + assign S_AXI_RDATA = axil_read_data; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_ARADDR[ADDRLSB-1:0], + S_AXI_AWADDR[ADDRLSB-1:0], + wskd_data[C_AXI_DATA_WIDTH-1:SW] }; + // Verilator lint_on UNUSED + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used in verfiying this core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Register definitions + // {{{ + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(OPT_TIMEOUT + 2), + .F_AXI_MAXDELAY(OPT_TIMEOUT + 2), + .F_AXI_MAXRSTALL(2), + .F_OPT_COVER_BURST(4) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0) + +(S_AXI_AWREADY ? 0:1)); + + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0) + +(S_AXI_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0) + +(S_AXI_ARREADY ? 0:1)); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Verifying the packet counters + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [11:0] f_reads, f_writes; + reg [3:0] f_read_pkts, f_write_pkts; + + // + // Mirror the read counter + // + initial f_reads = 0; + initial f_read_pkts = 0; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + f_reads <= 0; + f_read_pkts <= 0; + end else if (OPT_SINK && (axil_read_ready + && arskd_addr == ADDR_SINK && !rfifo_empty)) + begin + f_reads <= f_reads + 1; + f_read_pkts <= f_read_pkts + (rfifo_last ? 1:0); + end else if (!OPT_SINK && S_AXIS_TVALID) + begin + f_reads <= f_reads + 1; + f_read_pkts <= f_read_pkts + (S_AXIS_TLAST ? 1:0); + end + + always @(*) + assert(f_reads == reads_completed); + always @(*) + assert(f_read_pkts == read_bursts_completed); + + always @(*) + if (!OPT_SINK) + assert(S_AXIS_TREADY); + + // + // Mirror the write counter + // + initial f_writes = 0; + initial f_write_pkts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + f_writes <= 0; + f_write_pkts <= 0; + end else if (OPT_SOURCE && M_AXIS_TVALID && M_AXIS_TREADY) + begin + f_writes <= f_writes + 1; + f_write_pkts <= f_write_pkts + (M_AXIS_TLAST ? 1:0); + end + + always @(*) + if (!OPT_SOURCE) + begin + assert(f_writes == 0); + assert(f_write_pkts == 0); + end + + always @(*) + begin + assert(f_writes == writes_completed); + assert(f_write_pkts == write_bursts_completed); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Verify the read result + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN && axil_read_ready)) + begin + assert(S_AXI_RVALID); + case($past(arskd_addr)) + ADDR_SINK: begin + assert(S_AXI_RDATA[SW-1:0] == $past(rfifo_data)); + if (SW < C_AXI_DATA_WIDTH) + begin + if (OPT_SIGN_EXTEND && $past(rfifo_data[SW-1])) + assert(&S_AXI_RDATA[C_AXI_DATA_WIDTH-1:SW]); + else + assert(S_AXI_RDATA[C_AXI_DATA_WIDTH-1:SW] == 0); + end end + // 1: assert(S_AXI_RDATA == $past(r1)); + ADDR_STATS: begin + assert(S_AXI_RRESP == 2'b00); + assert(S_AXI_RDATA[31:28] + == $past(write_bursts_completed)); + assert(S_AXI_RDATA[27:16] == $past(writes_completed)); + + assert(S_AXI_RDATA[15:12] + == $past(read_bursts_completed)); + assert(S_AXI_RDATA[11:0] == $past(reads_completed)); + end + ADDR_FIFO: begin + assert(S_AXI_RRESP == 2'b00); + if (LGFIFO < 16) + assert(S_AXI_RDATA[31:16+LGFIFO+1] == 0); + assert(S_AXI_RDATA[16+: LGFIFO+1]==$past(wfifo_fill)); + assert(S_AXI_RDATA[15] == $past(rfifo_last)); + if (LGFIFO < 15) + assert(S_AXI_RDATA[14:LGFIFO+1] == 0); + assert(S_AXI_RDATA[ 0+: LGFIFO+1]==$past(rfifo_fill)); + end + default: begin end + endcase + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + if (OPT_LOWPOWER && !S_AXI_RVALID) + assert(S_AXI_RDATA == 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-stream interfaces + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Slave/consumer properties + always @(posedge S_AXI_ACLK) + if (!f_past_valid || !$past(S_AXI_ARESETN)) + begin + assume(!S_AXIS_TVALID); + end else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY)) + begin + assume(S_AXIS_TVALID); + assume($stable(S_AXIS_TDATA)); + assume($stable(S_AXIS_TLAST)); + end + + // Master/producer/source properties + always @(posedge S_AXI_ACLK) + if (!f_past_valid || !$past(S_AXI_ARESETN)) + begin + assert(!M_AXIS_TVALID); + end else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY)) + begin + assert(M_AXIS_TVALID); + assert($stable(M_AXIS_TDATA)); + assert($stable(M_AXIS_TLAST)); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + cover(S_AXI_ARESETN && writes_completed == 16); + + always @(*) + cover(S_AXI_ARESETN && reads_completed == 16); + + always @(*) + cover(S_AXI_ARESETN && writes_completed == 16 + && reads_completed == 16); + + always @(*) + cover(S_AXI_BVALID && S_AXI_BRESP != 2'b00); + + always @(*) + cover(S_AXI_RVALID && S_AXI_RRESP != 2'b00); + + // }}} + // }}} +`endif +endmodule diff --git a/rtl/wb2axip/axildouble.v b/rtl/wb2axip/axildouble.v new file mode 100644 index 0000000..ddaedb6 --- /dev/null +++ b/rtl/wb2axip/axildouble.v @@ -0,0 +1,734 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axildouble.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Create a special slave which can be used to reduce crossbar +// logic for multiple simplified slaves. This is a companion +// core to the similar axilsingle core, but allowing the slave to +// decode the clock between multiple possible addresses. +// +// To use this, the slave must follow specific (simplified AXI) rules: +// +// Write interface +// 1. The slave must guarantee that AWREADY == WREADY = 1 +// (This core doesn't have AWREADY or WREADY inputs) +// 2. The slave must also guarantee that BVALID == $past(AWVALID) +// (This core internally generates BVALID) +// 3. The controller will guarantee that AWVALID == WVALID +// (You can connect AWVALID to WVALID when connecting to your core) +// 4. The controller will also guarantee that BREADY = 1 +// (This core doesn't have a BVALID input) +// +// Read interface +// 1. The slave must guarantee that ARREADY == 1 +// (This core doesn't have an ARREADY input) +// 2. The slave must also guarantee that RVALID == $past(ARVALID) +// (This core doesn't have an RVALID input, trusting the slave +// instead) +// 3. The controller will guarantee that RREADY = 1 +// (This core doesn't have an RREADY output) +// +// +// Why? This simplifies slave logic. Slaves may interact with the bus +// using only the logic below: +// +// always @(posedge S_AXI_ACLK) +// if (AWVALID) case(AWADDR) +// R1: slvreg_1 <= WDATA; +// R2: slvreg_2 <= WDATA; +// R3: slvreg_3 <= WDATA; +// R4: slvreg_4 <= WDATA; +// endcase +// +// always @(*) +// BRESP = 2'b00; +// +// always @(posedge S_AXI_ACLK) +// if (ARVALID) +// case(ARADDR) +// R1: RDATA <= slvreg_1; +// R2: RDATA <= slvreg_2; +// R3: RDATA <= slvreg_3; +// R4: RDATA <= slvreg_4; +// endcase +// +// always @(*) +// RRESP = 2'b00; +// +// This core will then keep track of the more complex bus logic, +// simplifying both slaves and connection logic. Slaves with the more +// complicated (and proper/accurate) logic, that follow the rules above, +// should have no problems with this additional logic. +// +// Performance: +// +// This core can sustain one read/write per clock as long as the upstream +// AXI master keeps S_AXI_[BR]READY high. If S_AXI_[BR]READY ever drops, +// there's some flexibility provided by the return FIFO, so the master +// might not notice a drop in throughput until the FIFO fills. +// +// The more practical performance measure is the latency of this core. +// That I've measured at four clocks. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// `ifdef VERILATOR +// `define FORMAL +// `endif +// }}} +module axildouble #( + // {{{ + parameter integer C_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 32, + // + // NS is the number of slave interfaces + parameter NS = 8, + // + // + parameter [NS*C_AXI_ADDR_WIDTH-1:0] SLAVE_ADDR = { + { 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 3'b110, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 3'b101, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 3'b100, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 3'b011, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 3'b010, {(C_AXI_ADDR_WIDTH-3){1'b0}} }, + { 4'b0001,{(C_AXI_ADDR_WIDTH-4){1'b0}} }, + { 4'b0000,{(C_AXI_ADDR_WIDTH-4){1'b0}} } }, + // + // + parameter [NS*C_AXI_ADDR_WIDTH-1:0] SLAVE_MASK = + (NS <= 1) ? 0 + : { {(NS-2){ 3'b111,{(C_AXI_ADDR_WIDTH-3){1'b0}} }}, + {(2){ 4'b1111,{(C_AXI_ADDR_WIDTH-4){1'b0}} }} + }, + // + // + // LGFLEN specifies the log (based two) of the number of + // transactions that may need to be held outstanding internally. + // If you really want high throughput, and if you expect any + // back pressure at all, then increase LGFLEN. Otherwise the + // default value of 3 (FIFO size = 8) should be sufficient + // to maintain full loading + parameter LGFLEN=3, + // + // If set, OPT_LOWPOWER will set all unused registers, both + // internal and external, to zero anytime their corresponding + // *VALID bit is clear + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [3-1:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire [2-1:0] S_AXI_BRESP, + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + // + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [3-1:0] S_AXI_ARPROT, + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + // + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [2-1:0] S_AXI_RRESP, + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + // + // + // + output wire [NS-1:0] M_AXI_AWVALID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [3-1:0] M_AXI_AWPROT, + // + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + // + input wire [NS*2-1:0] M_AXI_BRESP, + // + output wire [NS-1:0] M_AXI_ARVALID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [3-1:0] M_AXI_ARPROT, + // + input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [NS*2-1:0] M_AXI_RRESP + // }}} + ); + + // + // AW, and DW, are short-hand abbreviations used locally. + localparam AW = C_AXI_ADDR_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + localparam LGNS = $clog2(NS); + // + localparam INTERCONNECT_ERROR = 2'b11; + + //////////////////////////////////////////////////////////////////////// + // + // Write logic: + // + //////////////////////////////////////////////////////////////////////// + // + // + wire awskid_valid, bffull, bempty, write_awskidready, + dcd_awvalid; + reg write_bvalid, write_response; + reg bfull, write_wready, write_no_index; + wire [NS:0] wdecode; + wire [AW-1:0] awskid_addr; + wire [AW-1:0] m_awaddr; + reg [LGNS-1:0] write_windex, write_bindex; + wire [3-1:0] awskid_prot, m_axi_awprot; + wire [LGFLEN:0] bfill; + reg [LGFLEN:0] write_count; + reg [1:0] write_resp; + integer k; + + skidbuffer #(.OPT_LOWPOWER(OPT_LOWPOWER), .OPT_OUTREG(0), + .DW(AW+3)) + awskid( .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), + .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWPROT, S_AXI_AWADDR }), + .o_valid(awskid_valid), .i_ready(write_awskidready), + .o_data({ awskid_prot, awskid_addr })); + + wire awskd_stall; + + addrdecode #(.AW(AW), .DW(3), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(1'b1)) + wraddr(.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(awskid_valid && write_awskidready), .o_stall(awskd_stall), + .i_addr(awskid_addr), + .i_data(awskid_prot), + .o_valid(dcd_awvalid), .i_stall(!S_AXI_WVALID), + .o_decode(wdecode), .o_addr(m_awaddr), + .o_data(m_axi_awprot)); + + always @(*) + write_wready = dcd_awvalid; + assign S_AXI_WREADY = write_wready; + assign M_AXI_AWVALID = (S_AXI_WVALID) ? wdecode[NS-1:0] : 0; + assign M_AXI_AWADDR = m_awaddr; + assign M_AXI_AWPROT = m_axi_awprot; + assign M_AXI_WDATA = S_AXI_WDATA; + assign M_AXI_WSTRB = S_AXI_WSTRB; + assign write_awskidready = (S_AXI_WVALID || !S_AXI_WREADY) && !bfull; + + always @(*) + begin + write_windex = 0; + for(k=0; k<NS; k=k+1) + if (wdecode[k]) + write_windex = write_windex | k[LGNS-1:0]; + end + + always @(posedge S_AXI_ACLK) + begin + write_bindex <= write_windex; + write_no_index <= wdecode[NS]; + end + + initial { write_response, write_bvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { write_response, write_bvalid } <= 0; + else + { write_response, write_bvalid } + <= { write_bvalid, (S_AXI_WVALID && S_AXI_WREADY) }; + + always @(posedge S_AXI_ACLK) + if (write_no_index) + write_resp <= INTERCONNECT_ERROR; + else + write_resp <= M_AXI_BRESP[2*write_bindex +: 2]; + + initial write_count = 0; + initial bfull = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + write_count <= 0; + bfull <= 0; + end else case({ (awskid_valid && write_awskidready), + (S_AXI_BVALID & S_AXI_BREADY) }) + 2'b01: begin + write_count <= write_count - 1; + bfull <= 1'b0; + end + 2'b10: begin + write_count <= write_count + 1; + bfull <= (&write_count[LGFLEN-1:0]); + end + default: begin end + endcase + +`ifdef FORMAL + always @(*) + assert(write_count <= { 1'b1, {(LGFLEN){1'b0}} }); + always @(*) + assert(bfull == (write_count == { 1'b1, {(LGFLEN){1'b0}} })); +`endif + + sfifo #(.BW(2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) + bfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(write_response), .i_data(write_resp), .o_full(bffull), + .o_fill(bfill), + .i_rd(S_AXI_BVALID && S_AXI_BREADY), .o_data(S_AXI_BRESP), + .o_empty(bempty)); + +`ifdef FORMAL + always @(*) + assert(write_count == bfill + + (write_response ? 1:0) + + (write_bvalid ? 1:0) + + (write_wready ? 1:0)); + +`ifdef VERIFIC + always @(*) + if (bfifo.f_first_in_fifo) + assert(bfifo.f_first_data != 2'b01); + always @(*) + if (bfifo.f_second_in_fifo) + assert(bfifo.f_second_data != 2'b01); + always @(*) + if (!bempty && (!bfifo.f_first_in_fifo + || bfifo.rd_addr != bfifo.f_first_addr) + &&(!bfifo.f_second_in_fifo + || bfifo.rd_addr != bfifo.f_second_addr)) + assume(S_AXI_BRESP != 2'b01); +`else + always @(*) + if (!bempty) + assume(S_AXI_BRESP != 2'b01); +`endif +`endif + + assign S_AXI_BVALID = !bempty; + +`ifdef FORMAL + always @(*) + assert(!bffull || !write_bvalid); +`endif + + //////////////////////////////////////////////////////////////////////// + // + // Read logic + // + //////////////////////////////////////////////////////////////////////// + // + // + wire rempty, rdfull; + wire [LGFLEN:0] rfill; + reg [LGNS-1:0] read_index, last_read_index; + reg [1:0] read_resp; + reg [DW-1:0] read_rdata; + wire read_rwait, arskd_stall; + reg read_rvalid, read_result, read_no_index; + wire [AW-1:0] m_araddr; + wire [3-1:0] m_axi_arprot; + wire [NS:0] rdecode; + + addrdecode #(.AW(AW), .DW(3), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(1'b1)) + rdaddr(.i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID && S_AXI_ARREADY), .o_stall(arskd_stall), + .i_addr(S_AXI_ARADDR), .i_data(S_AXI_ARPROT), + .o_valid(read_rwait), .i_stall(1'b0), + .o_decode(rdecode), .o_addr(m_araddr), + .o_data(m_axi_arprot)); + + assign M_AXI_ARVALID = rdecode[NS-1:0]; + assign M_AXI_ARADDR = m_araddr; + assign M_AXI_ARPROT = m_axi_arprot; + + initial { read_result, read_rvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_result, read_rvalid } <= 2'b00; + else + { read_result, read_rvalid } <= { read_rvalid, read_rwait }; + + always @(*) + begin + read_index = 0; + + for(k=0; k<NS; k=k+1) + if (rdecode[k]) + read_index = read_index | k[LGNS-1:0]; + end + + always @(posedge S_AXI_ACLK) + last_read_index <= read_index; + + always @(posedge S_AXI_ACLK) + read_no_index <= rdecode[NS]; + + always @(posedge S_AXI_ACLK) + read_rdata <= M_AXI_RDATA[DW*last_read_index +: DW]; + + always @(posedge S_AXI_ACLK) + if (read_no_index) + read_resp <= INTERCONNECT_ERROR; + else + read_resp <= M_AXI_RRESP[2*last_read_index +: 2]; + + reg read_full; + reg [LGFLEN:0] read_count; + + initial { read_count, read_full } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_count, read_full } <= 0; + else case({ S_AXI_ARVALID & S_AXI_ARREADY, S_AXI_RVALID & S_AXI_RREADY}) + 2'b10: begin + read_count <= read_count + 1; + read_full <= &read_count[LGFLEN-1:0]; + end + 2'b01: begin + read_count <= read_count - 1; + read_full <= 1'b0; + end + default: begin end + endcase + +`ifdef FORMAL + always @(*) + assert(read_count <= { 1'b1, {(LGFLEN){1'b0}} }); + always @(*) + assert(read_full == (read_count == { 1'b1, {(LGFLEN){1'b0}} })); +`endif + assign S_AXI_ARREADY = !read_full; + + sfifo #(.BW(DW+2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) + rfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(read_result), .i_data({ read_rdata, read_resp }), + .o_full(rdfull), .o_fill(rfill), + .i_rd(S_AXI_RVALID && S_AXI_RREADY), + .o_data({ S_AXI_RDATA, S_AXI_RRESP }),.o_empty(rempty)); + +`ifdef FORMAL + always @(*) + assert(read_count == rfill + read_result + read_rvalid + read_rwait); +`ifdef VERIFIC + always @(*) + if (rfifo.f_first_in_fifo) + assert(rfifo.f_first_data[1:0] != 2'b01); + always @(*) + if (rfifo.f_second_in_fifo) + assert(rfifo.f_second_data[1:0] != 2'b01); + always @(*) + if (!rempty && (!rfifo.f_first_in_fifo + || rfifo.rd_addr != rfifo.f_first_addr) + &&(!rfifo.f_second_in_fifo + || rfifo.rd_addr != rfifo.f_second_addr)) + assume(S_AXI_RRESP != 2'b01); +`else + always @(*) + if (!rempty) + assume(S_AXI_RRESP != 2'b01); +`endif +`endif + + assign S_AXI_RVALID = !rempty; + + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, + bffull, rdfull, bfill, rfill, + awskd_stall, arskd_stall }; + // verilator lint_on UNUSED +`ifdef FORMAL + localparam F_LGDEPTH = LGFLEN+1; + reg f_past_valid; + reg [F_LGDEPTH-1:0] count_awr_outstanding, count_wr_outstanding, + count_rd_outstanding; + + + wire [(F_LGDEPTH-1):0] f_axi_awr_outstanding, + f_axi_wr_outstanding, + f_axi_rd_outstanding; + + wire [1:0] fm_axi_awr_outstanding [0:NS-1]; + wire [1:0] fm_axi_wr_outstanding [0:NS-1]; + wire [1:0] fm_axi_rd_outstanding [0:NS-1]; + + reg [NS-1:0] m_axi_rvalid, m_axi_bvalid; + + faxil_slave #(// .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + // .F_OPT_NO_READS(1'b0), + // .F_OPT_NO_WRITES(1'b0), + .F_OPT_XILINX(1), + .F_LGDEPTH(F_LGDEPTH)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr(S_AXI_AWADDR), + .i_axi_awprot(S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp(S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arprot(S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + // + .f_axi_rd_outstanding(f_axi_rd_outstanding), + .f_axi_wr_outstanding(f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding)); + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + genvar M; + generate for(M=0; M<NS; M=M+1) + begin : CONSTRAIN_SLAVE_INTERACTIONS + + faxil_master #(// .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // .F_OPT_NO_READS(1'b0), + // .F_OPT_NO_WRITES(1'b0), + .F_OPT_NO_RESET(1'b1), + .F_LGDEPTH(2)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID[M]), + .i_axi_awready(1'b1), + .i_axi_awaddr(M_AXI_AWADDR), + .i_axi_awprot(M_AXI_AWPROT), + // + .i_axi_wvalid(M_AXI_AWVALID[M]), + .i_axi_wready(1'b1), + .i_axi_wdata(M_AXI_WDATA[C_AXI_DATA_WIDTH-1:0]), + .i_axi_wstrb(M_AXI_WSTRB[C_AXI_DATA_WIDTH/8-1:0]), + // + .i_axi_bvalid(m_axi_bvalid[M]), + .i_axi_bready(1'b1), + .i_axi_bresp(M_AXI_BRESP[2*M +: 2]), + // + .i_axi_arvalid(M_AXI_ARVALID[M]), + .i_axi_arready(1'b1), + .i_axi_araddr(M_AXI_ARADDR), + .i_axi_arprot(M_AXI_ARPROT), + // + .i_axi_rdata(M_AXI_RDATA[M*C_AXI_DATA_WIDTH +: C_AXI_DATA_WIDTH]), + .i_axi_rresp(M_AXI_RRESP[2*M +: 2]), + .i_axi_rvalid(m_axi_rvalid[M]), + .i_axi_rready(1'b1), + // + .f_axi_rd_outstanding(fm_axi_rd_outstanding[M]), + .f_axi_wr_outstanding(fm_axi_wr_outstanding[M]), + .f_axi_awr_outstanding(fm_axi_awr_outstanding[M])); + + initial m_axi_bvalid <= 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_bvalid[M] <= 1'b0; + else + m_axi_bvalid[M] <= M_AXI_AWVALID[M]; + + initial m_axi_rvalid[M] <= 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_rvalid[M] <= 1'b0; + else + m_axi_rvalid[M] <= M_AXI_ARVALID[M]; + + always @(*) + assert(fm_axi_awr_outstanding[M] == fm_axi_wr_outstanding[M]); + + always @(*) + assert(fm_axi_wr_outstanding[M]== (m_axi_bvalid[M] ? 1:0)); + + always @(*) + assert(fm_axi_rd_outstanding[M]== (m_axi_rvalid[M] ? 1:0)); + end endgenerate + + //////////////////////////////////////////////////////////////////////// + // + // Properties necessary to pass induction + // + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + assert(S_AXI_WREADY == (wdecode != 0)); +`ifdef VERIFIC + always @(*) + assert($onehot0(M_AXI_AWVALID)); + + always @(*) + assert($onehot0(m_axi_bvalid)); + + always @(*) + assert($onehot0(m_axi_rvalid)); +`endif + always @(*) + begin + count_awr_outstanding = 0; + if (!S_AXI_AWREADY) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_wready) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_bvalid) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_response) + count_awr_outstanding = count_awr_outstanding + 1; + count_awr_outstanding = count_awr_outstanding + bfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_awr_outstanding == count_awr_outstanding); + + always @(*) + begin + count_wr_outstanding = 0; + if (write_bvalid) + count_wr_outstanding = count_wr_outstanding + 1; + if (write_response) + count_wr_outstanding = count_wr_outstanding + 1; + count_wr_outstanding = count_wr_outstanding + bfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_wr_outstanding == count_wr_outstanding); + + // + // + // + always @(*) + begin + count_rd_outstanding = 0; + if (read_rwait) + count_rd_outstanding = count_rd_outstanding + 1; + if (read_rvalid) + count_rd_outstanding = count_rd_outstanding + 1; + if (read_result) + count_rd_outstanding = count_rd_outstanding + 1; + count_rd_outstanding = count_rd_outstanding + rfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_rd_outstanding == count_rd_outstanding); + + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + //////////////////////////////////////////////////////////////////////// + // + // + reg [3:0] cvr_arvalids, cvr_awvalids, cvr_reads, cvr_writes; + + initial cvr_awvalids = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_awvalids <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && !(&cvr_awvalids)) + cvr_awvalids <= cvr_awvalids + 1; + + initial cvr_arvalids = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_arvalids <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && !(&cvr_arvalids)) + cvr_arvalids <= cvr_arvalids + 1; + + initial cvr_writes = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_writes <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY && !(&cvr_writes)) + cvr_writes <= cvr_writes + 1; + + initial cvr_reads = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_reads <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && !(&cvr_arvalids)) + cvr_reads <= cvr_reads + 1; + + always @(*) + cover(cvr_awvalids > 4); + + always @(*) + cover(cvr_arvalids > 4); + + always @(*) + cover(cvr_reads > 4); + + always @(*) + cover(cvr_writes > 4); + + always @(*) + cover((cvr_writes > 4) && (cvr_reads > 4)); +`endif +endmodule +// `ifndef YOSYS +// `default_nettype wire +// `endif diff --git a/rtl/wb2axip/axilempty.v b/rtl/wb2axip/axilempty.v new file mode 100644 index 0000000..f429d69 --- /dev/null +++ b/rtl/wb2axip/axilempty.v @@ -0,0 +1,371 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilempty.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Modifies the simple AXI-lite interface to be an empty shell +// +// This is useful for a bus with masters but no slaves. When used, +// the interconnect can connect those masters to this slave to know +// that requests will still be properly handled--and get proper error +// returns. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilempty #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + // Verilator lint_off UNUSED + parameter C_AXI_ADDR_WIDTH = 4, + // Verilator lint_on UNUSED + localparam C_AXI_DATA_WIDTH = 32, + parameter [0:0] OPT_SKIDBUFFER = 1'b0, + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + wire i_reset = !S_AXI_ARESETN; + + wire axil_write_ready; + // + reg axil_bvalid; + // + wire axil_read_ready; + reg axil_read_valid; + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + // Write signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_WRITE + + wire awskd_valid, wskd_valid, awskd_unused, wskd_unused; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), .DW(1)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data(1'b0), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_unused)); + +`ifdef FORMAL + always @(*) + if (awskd_valid) + assert(awskd_unused == 0); +`endif + + skidbuffer #(.OPT_OUTREG(0), .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(1)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ 1'b0 }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data(wskd_unused)); +`ifdef FORMAL + always @(*) + if (wskd_valid) + assert(wskd_unused == 0); +`endif + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXI_BVALID || S_AXI_BREADY); + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, awskd_unused, wskd_unused }; + // Verilator lint_on UNUSED + end else begin : SIMPLE_WRITES + + reg axil_awready; + + initial axil_awready = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axil_awready <= 1'b0; + else + axil_awready <= !axil_awready + && (S_AXI_AWVALID && S_AXI_WVALID) + && (!S_AXI_BVALID || S_AXI_BREADY); + + assign S_AXI_AWREADY = axil_awready; + assign S_AXI_WREADY = axil_awready; + + assign axil_write_ready = axil_awready; + + end endgenerate + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXI_BREADY) + axil_bvalid <= 0; + + assign S_AXI_BVALID = axil_bvalid; + assign S_AXI_BRESP = 2'b11; + // }}} + + // + // Read signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_READ + + wire arskd_valid, arskd_unused; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(1)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data( 1'b0 ), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_unused)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXI_RREADY); + +`ifdef FORMAL + always @(*) + if (arskd_valid) + assert(arskd_unused == 0); +`endif + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, arskd_unused }; + // Verilator lint_on UNUSED + end else begin : SIMPLE_READS + + reg axil_arready; + + always @(*) + axil_arready = !S_AXI_RVALID; + + assign S_AXI_ARREADY = axil_arready; + assign axil_read_ready = (S_AXI_ARVALID && S_AXI_ARREADY); + + end endgenerate + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXI_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXI_RVALID = axil_read_valid; + assign S_AXI_RDATA = 0; + assign S_AXI_RRESP = 2'b11; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + + // }}} + + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // Formal properties used in verfiying this core + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3), + .F_OPT_COVER_BURST(0) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr({(C_AXI_ADDR_WIDTH){1'b0}}), + .i_axi_awprot( 3'h0), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( {(C_AXI_DATA_WIDTH){1'b0}}), + .i_axi_wstrb( {(C_AXI_DATA_WIDTH/8){1'b0}}), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( {(C_AXI_ADDR_WIDTH){1'b0}}), + .i_axi_arprot( 3'h0), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + if (OPT_SKIDBUFFER) + begin + assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0) + +(S_AXI_AWREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0) + +(S_AXI_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0) + +(S_AXI_ARREADY ? 0:1)); + end else begin + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0)); + assert(faxil_awr_outstanding == faxil_wr_outstanding); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0)); + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + assert(S_AXI_RDATA == 0); + always @(*) + assert(S_AXI_RRESP == 2'b11); + always @(*) + assert(S_AXI_BRESP == 2'b11); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // While there are already cover properties in the formal property + // set above, you'll probably still want to cover something + // application specific here + + // }}} + // }}} +`endif +endmodule diff --git a/rtl/wb2axip/axilfetch.v b/rtl/wb2axip/axilfetch.v new file mode 100644 index 0000000..7089b20 --- /dev/null +++ b/rtl/wb2axip/axilfetch.v @@ -0,0 +1,469 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilfetch.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: This is a very simple instruction fetch approach based around +// AXI-lite. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilfetch #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 64, + parameter INSN_WIDTH=32, + parameter FETCH_LIMIT=16, + parameter [0:0] SWAP_ENDIANNESS = 1'b1, + localparam AW=C_AXI_ADDR_WIDTH + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // CPU interaction wires + // {{{ + input wire i_cpu_reset, + input wire i_new_pc, + input wire i_clear_cache, + input wire i_ready, + input wire [AW-1:0] i_pc, // Ignd unls i_new_pc + output wire [INSN_WIDTH-1:0] o_insn, // Insn read from bus + output reg [AW-1:0] o_pc, // Addr of that insn + output reg o_valid, // If valid + output reg o_illegal, // Bus error + // }}} + // AXI-lite bus interface + // {{{ + output reg M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output reg [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [2:0] M_AXI_ARPROT, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [1:0] M_AXI_RRESP + // }}} + // }}} + ); + + // Declarations + // {{{ + localparam AXILLSB = $clog2(C_AXI_DATA_WIDTH/8); + localparam INSNS_PER_WORD = C_AXI_DATA_WIDTH / INSN_WIDTH; + localparam INSN_LSB = $clog2(INSN_WIDTH/8); + localparam LGDEPTH = $clog2(FETCH_LIMIT)+4; + localparam LGFIFO = $clog2(FETCH_LIMIT); + localparam W = LGDEPTH; + localparam FILLBITS = $clog2(INSNS_PER_WORD); + // ($clog2(INSNS_PER_WORD) > 0) + // ? $clog2(INSNS_PER_WORD) : 1); + + reg [W:0] new_flushcount, outstanding, + next_outstanding, flushcount; + reg flushing, flush_request, full_bus; + wire [((AXILLSB>INSN_LSB) ? (AXILLSB-INSN_LSB-1):0):0] shift; + wire fifo_reset, fifo_wr, fifo_rd; + wire ign_fifo_full, fifo_empty; + wire [LGFIFO:0] ign_fifo_fill; + wire [C_AXI_DATA_WIDTH:0] fifo_data; + reg pending_new_pc; + reg [C_AXI_ADDR_WIDTH-1:0] pending_pc; + reg [W-1:0] fill; + reg [FILLBITS:0] out_fill; + reg [C_AXI_DATA_WIDTH-1:0] out_data; + reg [C_AXI_DATA_WIDTH-1:0] endian_swapped_rdata; + // }}} + + assign fifo_reset = i_cpu_reset || i_clear_cache || i_new_pc; + assign fifo_wr = M_AXI_RVALID && !flushing; + + + // ARPROT = 3'b100 for an unprivileged, secure instruction access + // (not sure what unprivileged or secure mean--even after reading the + // spec) + assign M_AXI_ARPROT = 3'b100; + + // next_outstanding + // {{{ + always @(*) + begin + next_outstanding = outstanding; + + case({ M_AXI_ARVALID && M_AXI_ARREADY, M_AXI_RVALID }) + 2'b10: next_outstanding = outstanding + 1; + 2'b01: next_outstanding = outstanding - 1; + default: begin end + endcase + end + // }}} + + // outstanding, full_bus + // {{{ + initial outstanding = 0; + initial full_bus = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + outstanding <= 0; + full_bus <= 0; + end else begin + outstanding <= next_outstanding; + full_bus <= (next_outstanding + // + (((M_AXI_ARVALID && !M_AXI_ARREADY) ? 1:0) + >= (1<<LGDEPTH)-1); + end + // }}} + + // fill + // {{{ + initial fill = 0; + always @(posedge S_AXI_ACLK) + if (fifo_reset) + fill <= 0; + // else if (fo_reset || flushing) + // fill <= 0; + else case({ M_AXI_ARVALID && M_AXI_ARREADY && !flush_request, + fifo_rd && !fifo_empty }) + 2'b10: fill <= fill + 1; + 2'b01: fill <= fill - 1; + default: begin end + endcase + // }}} + + // new_flushcount + // {{{ + always @(*) + new_flushcount = outstanding + (M_AXI_ARVALID ? 1:0) + - (M_AXI_RVALID ? 1:0); + // }}} + + // flushcount, flushing, flush_request + // {{{ + initial flushcount = 0; + initial flushing = 0; + initial flush_request = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + flushcount <= 0; + flushing <= 0; + flush_request <= 0; + end else if (fifo_reset) + begin + flushcount <= new_flushcount; + flushing <= (new_flushcount > 0); + flush_request <= (M_AXI_ARVALID && !M_AXI_ARREADY); + end else begin + if (M_AXI_RVALID && flushcount > 0) + begin + flushcount <= flushcount - 1; + // Verilator lint_off CMPCONST + flushing <= (flushcount > 1); + // Verilator lint_on CMPCONST + end + + if (M_AXI_ARREADY) + flush_request <= 0; + end + // }}} + + // M_AXI_ARVALID + // {{{ + initial M_AXI_ARVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXI_ARVALID <= 1'b0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + M_AXI_ARVALID <= 1; + if (i_new_pc || pending_new_pc) + M_AXI_ARVALID <= 1'b1; + + // + // Throttle the number of requests we make + // Verilator lint_off CMPCONST + // Verilator lint_off WIDTH + // out_fill will only capture 0 or 1 if DATA_WIDTH == 32 + if (fill + (M_AXI_ARVALID ? 1:0) + + ((o_valid &&(!i_ready || out_fill > 1)) ? 1:0) + >= FETCH_LIMIT) + M_AXI_ARVALID <= 1'b0; + // Verilator lint_on WIDTH + // Verilator lint_on CMPCONST + if (i_cpu_reset || i_clear_cache || full_bus) + M_AXI_ARVALID <= 1'b0; + end + // }}} + + assign M_AXI_RREADY = 1'b1; + + // pending_new_pc + // {{{ + initial pending_new_pc = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || i_clear_cache) + pending_new_pc <= 1'b0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + pending_new_pc <= 1'b0; + else if (i_new_pc) + pending_new_pc <= 1'b1; + // }}} + + // pending_pc + // {{{ + initial pending_pc = 0; + always @(posedge S_AXI_ACLK) + if (i_new_pc) + pending_pc <= i_pc; + // }}} + + // M_AXI_ARADDR + // {{{ + always @(posedge S_AXI_ACLK) + if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + if (i_new_pc) + M_AXI_ARADDR <= i_pc; + else if (pending_new_pc) + M_AXI_ARADDR <= pending_pc; + else if (M_AXI_ARVALID) + begin + M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:AXILLSB] + <= M_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:AXILLSB] +1; + M_AXI_ARADDR[AXILLSB-1:0] <= 0; + end + end + // }}} + + // o_pc + // {{{ + initial o_pc = 0; + always @(posedge S_AXI_ACLK) + if (i_new_pc) + o_pc <= i_pc; + else if (o_valid && i_ready && !o_illegal) + begin + o_pc <= 0; + o_pc[AW-1:INSN_LSB] <= o_pc[AW-1:INSN_LSB] + 1; + end + // }}} + + generate if (AXILLSB > INSN_LSB) + begin : BIG_WORD + // {{{ + assign shift = o_pc[AXILLSB-1:INSN_LSB]; + // }}} + end else begin : NO_SHIFT + // {{{ + assign shift = 0; + // }}} + end endgenerate + + generate if (SWAP_ENDIANNESS) + begin : SWAPPED_ENDIANNESS + // {{{ + genvar gw, gb; // Word count, byte count + + for(gw=0; gw<C_AXI_DATA_WIDTH/INSN_WIDTH; gw=gw+1) // For each bus word + for(gb=0; gb<(INSN_WIDTH/8); gb=gb+1) // For each bus byte + always @(*) + endian_swapped_rdata[gw*INSN_WIDTH + + ((INSN_WIDTH/8)-1-gb)*8 +: 8] + = M_AXI_RDATA[gw*INSN_WIDTH+gb*8 +: 8]; + // }}} + end else begin : NO_ENDIAN_SWAP + // {{{ + always @(*) + endian_swapped_rdata = M_AXI_RDATA; + // }}} + end endgenerate + + generate if (FETCH_LIMIT <= 1) + begin : NOCACHE + // {{{ + // No cache + + // assign fifo_rd = fifo_wr; + // Verilator lint_off CMPCONST + assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1)); + // Verilator lint_on CMPCONST + assign fifo_empty = !fifo_wr; //(out_fill <= (i_aready ? 1:0)); + assign fifo_data = { M_AXI_RRESP[1], endian_swapped_rdata }; + + assign ign_fifo_fill = 1'b0; + assign ign_fifo_full = 1'b0; +`ifdef FORMAL + always @(*) + if (M_AXI_RVALID || M_AXI_ARVALID || outstanding > 0) + assert(!o_valid); +`endif + // }}} + end else if (FETCH_LIMIT == 2) + begin : DBLFETCH + // {{{ + // Single word cache + reg cache_valid; + reg [C_AXI_DATA_WIDTH:0] cache_data; + + assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1)); + assign fifo_empty =(!M_AXI_RVALID && !cache_valid) || flushing; + assign fifo_data = cache_valid ? cache_data + : ({ M_AXI_RRESP[1], endian_swapped_rdata }); + + + assign ign_fifo_fill = cache_valid ? 1 : 0; + assign ign_fifo_full = cache_valid; + + initial cache_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (fifo_reset) + cache_valid <= 1'b0; + else if (M_AXI_RVALID && o_valid && !fifo_rd) + cache_valid <= 1; + else if (fifo_rd) + cache_valid <= 1'b0; + + always @(posedge S_AXI_ACLK) + if (M_AXI_RVALID) + cache_data <= { M_AXI_RRESP[1], endian_swapped_rdata }; + + // Make Verilator happy + // {{{ + wire unused_dblfetch; + assign unused_dblfetch = &{ 1'b0, fifo_wr }; + // }}} + // }}} + end else begin : FIFO_FETCH + // {{{ + // FIFO cache + + // Verilator lint_off CMPCONST + // out_fill will only capture 0 or 1 if DATA_WIDTH == 32 + assign fifo_rd = !o_valid || (i_ready && (out_fill <= 1)); + // Verilator lint_on CMPCONST + + sfifo #( + // {{{ + .BW(1+C_AXI_DATA_WIDTH), .LGFLEN(LGFIFO) + // }}} + ) fcache( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(fifo_reset), + .i_wr(fifo_wr), + .i_data({M_AXI_RRESP[1], endian_swapped_rdata }), + .o_full(ign_fifo_full), .o_fill(ign_fifo_fill), + .i_rd(fifo_rd),.o_data(fifo_data),.o_empty(fifo_empty) + // }}} + ); + // }}} + end endgenerate + + // o_valid + // {{{ + initial o_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (fifo_reset) + o_valid <= 1'b0; + else if (!o_valid || i_ready) + o_valid <= (fifo_rd && !fifo_empty) + || out_fill > (o_valid ? 1:0); + // }}} + + // out_fill + // {{{ + // == number of instructions in the fifo_data word that have not (yet) + // been accepted by the CPU. + // == 0 when no data is available + // == INSN_PER_WORD on the first instruction of any word + // == 1 on the last instruction of any word + initial out_fill = 0; + always @(posedge S_AXI_ACLK) + if (fifo_reset) + out_fill <= 0; + else if (fifo_rd) + begin + if (fifo_empty) + out_fill <= 0; + else if (o_valid) + out_fill <= INSNS_PER_WORD[FILLBITS:0]; + else + // Verilator lint_off WIDTH + out_fill <= (INSNS_PER_WORD[FILLBITS:0] - shift); + // Verilator lint_on WIDTH + end else if (i_ready && out_fill > 0) + out_fill <= out_fill - 1; + // }}} + + // out_data + // {{{ + always @(posedge S_AXI_ACLK) + if (fifo_rd) + begin + if (o_valid || (INSN_WIDTH == C_AXI_DATA_WIDTH)) + out_data <= fifo_data[C_AXI_DATA_WIDTH-1:0]; + else + out_data <= fifo_data[C_AXI_DATA_WIDTH-1:0]>>(INSN_WIDTH*shift); + end else if (i_ready) + out_data <= out_data >> INSN_WIDTH; + // }}} + + assign o_insn = out_data[INSN_WIDTH-1:0]; + + // o_illegal + // {{{ + initial o_illegal = 1'b0; + always @(posedge S_AXI_ACLK) + if (fifo_reset) + o_illegal <= 1'b0; + else if (!o_illegal && fifo_rd && !fifo_empty) + o_illegal <= fifo_data[C_AXI_DATA_WIDTH]; + // }}} + + // Make verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = & { 1'b0, M_AXI_RRESP[0], ign_fifo_full, ign_fifo_fill }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Formal properties for this module are maintained elsewhere +`endif +endmodule diff --git a/rtl/wb2axip/axilgpio.v b/rtl/wb2axip/axilgpio.v new file mode 100644 index 0000000..0a6e90c --- /dev/null +++ b/rtl/wb2axip/axilgpio.v @@ -0,0 +1,808 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilgpio +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A simple and basic AXI-lite input and output module. +// Tristates are not supported internally, although output bits +// may be used externally to create a tristate input. For example, when +// driving an I2C controller, you might wish to do something like: +// +// assign i2c_scl = gpio_output[1] ? 1'bz : gpio_output[1]; +// assign i2c_sda = gpio_output[0] ? 1'bz : gpio_output[0]; +// +// Or, as another example: +// +// assign generic_io = gpio_output[3] ? 1'bz : gpio_output[2]; +// +// Registers: +// 0: LOAD +// Write to this register will overwrite the output data bits. +// 4: SET +// Writes to this register will set every output data bit where +// the written bit is set. +// +// OUTPUT[k] = OLD BIT[k] || NEW_BIT[k] +// +// 8: CLEAR +// Writes to this register will clear every output data bit where +// the written bit is set. +// +// OUTPUT[k] = OLD BIT[k] && (!NEW_BIT[k]) +// +// 12: TOGGLE +// Writes to this register will toggle every output bit where +// the bit written is set. +// +// OUTPUT[k] = OLD BIT[k] ^ NEW_BIT[k] +// +// The next four registers are present if (and only if) NIN > 0. If not, +// the prior four registers will be repeated. +// +// 16: Input data (if NIN > 0) +// This is the input data, following a two-register CDC. +// 20: Input data toggle detection +// A bit will be set in this register if ever the associated +// input data bit toggles. To clear, write a '1' to the toggled +// bit in this register. +// +// Bits from this register that are set will then create an +// outgoing interrupt--provided they are not masked. +// +// 24: Input data interrupt mask +// If any "mask" bit is set, then toggled data will not trigger +// an interrupt. +// 28: Input data interrupts active +// This is the AND of toggled data and a clear interrupt mask bit. +// +// An output interrupt is generated if any of the bits in the interrupt +// active register is high. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2021-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilgpio #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + parameter C_AXI_ADDR_WIDTH = 5, + localparam C_AXI_DATA_WIDTH = 32, + // OPT_SKIDBUFFER will increase throughput to 100% from 50% + parameter [0:0] OPT_SKIDBUFFER = 1'b1, + // OPT_LOWPOWER will force RDATA to zero if ever !RVALID + parameter [0:0] OPT_LOWPOWER = 0, + // NOUT : Number of output bits. Must be > 0 + parameter NOUT = 30, + // NIN: Number of input bits. May be zero if desired. + parameter NIN = 5, + parameter [NOUT-1:0] DEFAULT_OUTPUT = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI-lite interface + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP, + // }}} + output wire [NOUT-1:0] o_gpio, + input wire [((NIN>0) ? (NIN-1):0):0] i_gpio, + output wire o_int + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + localparam [2:0] // ADDR_LOAD = 3'b000, + // ADDR_SET = 3'b001, + // ADDR_CLEAR = 3'b010, + // ADDR_TOGGLE = 3'b011, + ADDR_INDATA = 3'b100, + ADDR_CHANGED= 3'b101, + ADDR_MASK = 3'b110, + ADDR_INT = 3'b111; + + + wire i_reset = !S_AXI_ARESETN; + + wire axil_write_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] awskd_addr; + // + wire [C_AXI_DATA_WIDTH-1:0] wskd_data; + wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire axil_read_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] arskd_addr; + reg [C_AXI_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + + reg [31:0] r_gpio; + wire [31:0] ck_gpio, ck_toggled, w_mask, int_toggled, wskd_gpio; + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_WRITE + // {{{ + wire awskd_valid, wskd_valid; + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB) + // }}} + ) axilawskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data(S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr) + // }}} + ); + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8) + // }}} + ) axilwskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb }) + // }}} + ); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXI_BVALID || S_AXI_BREADY); + // }}} + end else begin : SIMPLE_WRITES + // {{{ + reg axil_awready; + + initial axil_awready = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axil_awready <= 1'b0; + else + axil_awready <= !axil_awready + && (S_AXI_AWVALID && S_AXI_WVALID) + && (!S_AXI_BVALID || S_AXI_BREADY); + + assign S_AXI_AWREADY = axil_awready; + assign S_AXI_WREADY = axil_awready; + + assign awskd_addr = S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; + assign wskd_data = S_AXI_WDATA; + assign wskd_strb = S_AXI_WSTRB; + + assign axil_write_ready = axil_awready; + // }}} + end endgenerate + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXI_BREADY) + axil_bvalid <= 0; + + assign S_AXI_BVALID = axil_bvalid; + assign S_AXI_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_READ + // {{{ + wire arskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data(S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXI_RREADY); + // }}} + end else begin : SIMPLE_READS + // {{{ + reg axil_arready; + + always @(*) + axil_arready = !S_AXI_RVALID; + + assign arskd_addr = S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; + assign S_AXI_ARREADY = axil_arready; + assign axil_read_ready = (S_AXI_ARVALID && S_AXI_ARREADY); + // }}} + end endgenerate + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXI_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXI_RVALID = axil_read_valid; + assign S_AXI_RDATA = axil_read_data; + assign S_AXI_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + assign wskd_gpio = apply_wstrb(r_gpio, wskd_data, wskd_strb); + + // r_gpio, o_gpio + // {{{ + initial r_gpio = 0; + always @(posedge S_AXI_ACLK) + begin + if (axil_write_ready) + begin + case({ (awskd_addr[2] && (NIN > 0)), awskd_addr[1:0] }) + 3'b000: r_gpio <= wskd_gpio; + 3'b001: begin // SET + // {{{ + if (wskd_strb[0]) + r_gpio[ 7: 0]<= r_gpio[ 7: 0]| wskd_data[ 7: 0]; + if (wskd_strb[1]) + r_gpio[15: 8]<= r_gpio[15: 8]| wskd_data[15: 8]; + if (wskd_strb[2]) + r_gpio[23:16]<= r_gpio[23:16]| wskd_data[23:16]; + if (wskd_strb[3]) + r_gpio[31:24]<= r_gpio[31:24]| wskd_data[31:24]; + end + // }}} + 3'b010: begin // CLEAR + // {{{ + if (wskd_strb[0]) + r_gpio[ 7: 0]<= r_gpio[ 7: 0]&~wskd_data[ 7: 0]; + if (wskd_strb[1]) + r_gpio[15: 8]<= r_gpio[15: 8]&~wskd_data[15: 8]; + if (wskd_strb[2]) + r_gpio[23:16]<= r_gpio[23:16]&~wskd_data[23:16]; + if (wskd_strb[3]) + r_gpio[31:24]<= r_gpio[31:24]&~wskd_data[31:24]; + end + // }}} + 3'b011: begin // TOGGLE + // {{{ + if (wskd_strb[0]) + r_gpio[ 7: 0]<=r_gpio[ 7: 0] ^ wskd_data[ 7: 0]; + if (wskd_strb[1]) + r_gpio[15: 8]<=r_gpio[15: 8] ^ wskd_data[15: 8]; + if (wskd_strb[2]) + r_gpio[23:16]<=r_gpio[23:16] ^ wskd_data[23:16]; + if (wskd_strb[3]) + r_gpio[31:24]<=r_gpio[31:24] ^ wskd_data[31:24]; + end + // }}} + default: begin end // Input registers + endcase + end + + if (i_reset) + r_gpio[NOUT-1:0] <= DEFAULT_OUTPUT; + if (NOUT < 32) + r_gpio[31:((NOUT < 32) ? NOUT : 31)] <= 0; + end + + assign o_gpio = r_gpio[NOUT-1:0]; + // }}} + + // i_gpio -> ck_gpio, $changed(i_gpio) -> ck_toggled, r_mask + // {{{ + generate if (NIN > 0) + begin : INPUT_HANDLING + // {{{ + // Poss interrupts: Toggle, Rise, Fall + // Only toggle is implemented here + reg [NIN-1:0] last_gpio, qq_gpio, q_gpio, toggled, + r_mask; + wire [31:0] wstrb_mask; + reg r_int; + integer ik; + + // r_int + // {{{ + initial r_int = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + r_int <= 0; + else + r_int <= |(r_mask & toggled); + // }}} + + // Two clock CDC: last_gpio, qq_gpio, q_gpio + // {{{ + initial { last_gpio, qq_gpio, q_gpio } = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + { last_gpio, qq_gpio, q_gpio } <= 0; + else + { last_gpio, qq_gpio, q_gpio } + <= { qq_gpio, q_gpio, i_gpio }; + // }}} + + // Toggled + // {{{ + initial toggled = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + toggled <= 0; + else begin + for(ik=0; ik<NIN; ik=ik+1) + begin + if (axil_write_ready && awskd_addr == 3'b101 + && wskd_strb[ik/8] && wskd_data[ik]) + toggled[ik] <= 1'b0; + if (last_gpio[ik] ^ qq_gpio[ik]) + toggled[ik] <= 1'b1; + end + end + // }}} + + // r_mask + // {{{ + assign wstrb_mask = apply_wstrb(w_mask, wskd_data, wskd_strb); + + initial r_mask = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + r_mask <= 0; + else if (axil_write_ready && awskd_addr == 3'b110) + r_mask <= wstrb_mask[NIN-1:0]; + // }}} + + assign ck_gpio = { {(32-NIN){1'b0}}, qq_gpio }; + assign ck_toggled = { {(32-NIN){1'b0}}, toggled }; + assign w_mask = { {(32-NIN){1'b0}}, r_mask }; + assign int_toggled = { {(32-NIN){1'b0}}, (r_mask & toggled) }; + assign o_int = r_int; + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_inputs; + assign unused_inputs = &{ 1'b0, wstrb_mask[31:NIN] }; + // Verilator lint_on UNUSED + // }}} + // }}} + end else begin : NO_INPUTS + // {{{ + assign ck_gpio = 32'h0; + assign ck_toggled = 32'h0; + assign w_mask = 32'h0; + assign int_toggled = 32'h0; + assign o_int = 1'b0; + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_inputs; + assign unused_inputs = &{ 1'b0, i_gpio }; + // Verilator lint_on UNUSED + // }}} + // }}} + end endgenerate + // }}} + + // axil_read_data + // {{{ + initial axil_read_data = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_read_data <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + axil_read_data <= 0; + + casez({ ((NIN>0)&& arskd_addr[2]), arskd_addr[1:0] }) + 3'b0??: axil_read_data[NOUT-1:0] <= r_gpio[NOUT-1:0]; + ADDR_INDATA: axil_read_data <= ck_gpio; + ADDR_CHANGED: axil_read_data <= ck_toggled; + ADDR_MASK: axil_read_data <= w_mask; + ADDR_INT: axil_read_data <= int_toggled; + endcase + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + // }}} + + function [C_AXI_DATA_WIDTH-1:0] apply_wstrb; + // {{{ + input [C_AXI_DATA_WIDTH-1:0] prior_data; + input [C_AXI_DATA_WIDTH-1:0] new_data; + input [C_AXI_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_ARADDR[ADDRLSB-1:0], + S_AXI_AWADDR[ADDRLSB-1:0] }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(3), + .F_AXI_MAXDELAY(3), + .F_AXI_MAXRSTALL(5), + .F_OPT_COVER_BURST(4) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + if (OPT_SKIDBUFFER) + begin + assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0) + +(S_AXI_AWREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0) + +(S_AXI_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0) + +(S_AXI_ARREADY ? 0:1)); + end else begin + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0)); + assert(faxil_awr_outstanding == faxil_wr_outstanding); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0)); + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + if (OPT_LOWPOWER && !S_AXI_RVALID) + assert(S_AXI_RDATA == 0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Register return checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`define CHECK_REGISTERS +`ifdef CHECK_REGISTERS + (* anyconst *) reg [$clog2(NOUT)-1:0] f_obit; + + always @(*) + assume(f_obit < NOUT); + + // Verify o_gpio + // {{{ + always @(posedge S_AXI_ACLK) + if ($past(i_reset) || i_reset) + begin + if ($past(i_reset)) + assert(o_gpio[f_obit] == DEFAULT_OUTPUT[f_obit]); + end else if ($past(axil_write_ready && wskd_strb[f_obit/8])) + begin + case($past(awskd_addr[2:0])) + 3'b000: assert(o_gpio[f_obit] == $past(wskd_data[f_obit])); + 3'b001: assert(o_gpio[f_obit] == $past(o_gpio[f_obit] || wskd_data[f_obit])); + 3'b010: assert(o_gpio[f_obit] == $past(o_gpio[f_obit] && !wskd_data[f_obit])); + 3'b011: assert(o_gpio[f_obit] == $past(o_gpio[f_obit] ^ wskd_data[f_obit])); + default: begin end + endcase + end + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR({ 3'b000, {(ADDRLSB){1'b0}} }), + .MASK({(C_AXI_DATA_WIDTH){1'b0}}) + // }}} + ) foutputs ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr[2], {(ADDRLSB+2){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr[2], {(ADDRLSB+2){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(o_gpio) + // }}} + ); + + // }}} + + generate if (NIN > 0) + begin : CHECK_INPUT + (* anyconst *) reg [$clog2(NIN)-1:0] f_ibit; + + always @(*) + assume(f_ibit < NIN); + + always @(posedge S_AXI_ACLK) + if ($past(i_reset) || i_reset) + begin + if ($past(i_reset)) + assert(!ck_gpio[f_ibit]); + end else begin + if (!$past(i_reset, 2) + && $past(ck_gpio[f_ibit]) != $past(ck_gpio[f_ibit],2)) + assert(ck_toggled[f_ibit]); + else if ($past(axil_write_ready + && awskd_addr == ADDR_CHANGED + && wskd_strb[f_ibit/8] + && wskd_data[f_ibit])) + assert(!ck_toggled[f_ibit]); + end + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR({ ADDR_INDATA, {(ADDRLSB){1'b0}} }), + .MASK({(C_AXI_DATA_WIDTH){1'b0}}) + // }}} + ) finputs ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(ck_gpio) + // }}} + ); + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR({ ADDR_CHANGED, {(ADDRLSB){1'b0}} }), + .MASK({(C_AXI_DATA_WIDTH){1'b0}}) + // }}} + ) ftoggled ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(ck_toggled) + // }}} + ); + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR({ ADDR_MASK, {(ADDRLSB){1'b0}} }), + .MASK({ {(C_AXI_DATA_WIDTH-NIN){1'b0}}, {(NIN){1'b1}} }) + // }}} + ) fmask ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(w_mask) + // }}} + ); + end endgenerate +`endif + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Induction checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(posedge S_AXI_ACLK) + if (!i_reset) + cover(o_int); + + always @(posedge S_AXI_ACLK) + if (!i_reset) + cover($fell(o_int)); + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axilite2axi.v b/rtl/wb2axip/axilite2axi.v new file mode 100644 index 0000000..8b8adf3 --- /dev/null +++ b/rtl/wb2axip/axilite2axi.v @@ -0,0 +1,374 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilite2axi.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilite2axi #( + // {{{ + parameter C_AXI_ID_WIDTH = 4, + C_AXI_ADDR_WIDTH= 32, + C_AXI_DATA_WIDTH= 32, + parameter [C_AXI_ID_WIDTH-1:0] C_AXI_WRITE_ID = 0, + C_AXI_READ_ID = 0 + // }}} + ) ( + // {{{ + input wire ACLK, + input wire ARESETN, + // Slave AXI interface + // {{{ + // Slave write signals + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [3-1:0] S_AXI_AWPROT, + // Slave write data signals + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // Slave return write response + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [2-1:0] S_AXI_BRESP, + // Slave read signals + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [3-1:0] S_AXI_ARPROT, + // Slave read data signals + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [2-1:0] S_AXI_RRESP, + // }}} + // Master AXI interface + // {{{ + // Master interface write address + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [8-1:0] M_AXI_AWLEN, + output wire [3-1:0] M_AXI_AWSIZE, + output wire [2-1:0] M_AXI_AWBURST, + output wire M_AXI_AWLOCK, + output wire [4-1:0] M_AXI_AWCACHE, + output wire [3-1:0] M_AXI_AWPROT, + output wire [4-1:0] M_AXI_AWQOS, + // Master write data + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // Master write response + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // Master interface read address + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [8-1:0] M_AXI_ARLEN, + output wire [3-1:0] M_AXI_ARSIZE, + output wire [2-1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [4-1:0] M_AXI_ARCACHE, + output wire [3-1:0] M_AXI_ARPROT, + output wire [4-1:0] M_AXI_ARQOS, + // Master interface read data return + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [2-1:0] M_AXI_RRESP + // }}} + // }}} + ); + + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + assign M_AXI_AWID = C_AXI_WRITE_ID; + assign M_AXI_AWADDR = S_AXI_AWADDR; + assign M_AXI_AWLEN = 0; + assign M_AXI_AWSIZE = ADDRLSB[2:0]; + assign M_AXI_AWBURST = 0; + assign M_AXI_AWLOCK = 0; + assign M_AXI_AWCACHE = 4'b0011; // As recommended by Xilinx UG1037 + assign M_AXI_AWPROT = S_AXI_AWPROT; + assign M_AXI_AWQOS = 0; + assign M_AXI_AWVALID = S_AXI_AWVALID; + assign S_AXI_AWREADY = M_AXI_AWREADY; + // Master write data + assign M_AXI_WDATA = S_AXI_WDATA; + assign M_AXI_WLAST = 1; + assign M_AXI_WSTRB = S_AXI_WSTRB; + assign M_AXI_WVALID = S_AXI_WVALID; + assign S_AXI_WREADY = M_AXI_WREADY; + // Master write response + assign S_AXI_BRESP = M_AXI_BRESP; + assign S_AXI_BVALID = M_AXI_BVALID; + assign M_AXI_BREADY = S_AXI_BREADY; + // + // + assign M_AXI_ARID = C_AXI_READ_ID; + assign M_AXI_ARADDR = S_AXI_ARADDR; + assign M_AXI_ARLEN = 0; + assign M_AXI_ARSIZE = ADDRLSB[2:0]; + assign M_AXI_ARBURST = 0; + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE = 4'b0011; // As recommended by Xilinx UG1037 + assign M_AXI_ARPROT = S_AXI_ARPROT; + assign M_AXI_ARQOS = 0; + assign M_AXI_ARVALID = S_AXI_ARVALID; + assign S_AXI_ARREADY = M_AXI_ARREADY; + // + assign S_AXI_RDATA = M_AXI_RDATA; + assign S_AXI_RRESP = M_AXI_RRESP; + assign S_AXI_RVALID = M_AXI_RVALID; + assign M_AXI_RREADY = S_AXI_RREADY; + + // Make Verilator happy + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, ACLK, ARESETN, M_AXI_RLAST, M_AXI_RID, M_AXI_BID }; + // Verilator lint_on UNUSED + +`ifdef FORMAL + // + // The following are some of the properties used to verify this design + // + + localparam F_LGDEPTH = 10; + + wire [F_LGDEPTH-1:0] faxil_awr_outstanding, + faxil_wr_outstanding, faxil_rd_outstanding; + + faxil_slave #( + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .F_LGDEPTH(10), + .F_AXI_MAXRSTALL(4), + .F_AXI_MAXWAIT(9), + .F_AXI_MAXDELAY(0) + ) faxil(.i_clk(ACLK), .i_axi_reset_n(ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_awr_outstanding(faxil_awr_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_rd_outstanding(faxil_rd_outstanding)); + + // + // ... + // + + faxi_master #( + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .OPT_MAXBURST(0), + .OPT_EXCLUSIVE(1'b0), + .OPT_NARROW_BURST(1'b0), + .F_OPT_NO_RESET(1'b1), + .F_LGDEPTH(10), + .F_AXI_MAXSTALL(4), + .F_AXI_MAXRSTALL(0), + .F_AXI_MAXDELAY(4) + ) faxi(.i_clk(ACLK), .i_axi_reset_n(ARESETN), + // + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awid( M_AXI_AWID), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awlen( M_AXI_AWLEN), + .i_axi_awsize( M_AXI_AWSIZE), + .i_axi_awburst(M_AXI_AWBURST), + .i_axi_awlock( M_AXI_AWLOCK), + .i_axi_awcache(M_AXI_AWCACHE), + .i_axi_awprot( M_AXI_AWPROT), + .i_axi_awqos( M_AXI_AWQOS), + .i_axi_awvalid(M_AXI_AWVALID), + // + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + .i_axi_wlast( M_AXI_WLAST), + .i_axi_wvalid(M_AXI_WVALID), + // + .i_axi_bready(M_AXI_BREADY), + .i_axi_bid( M_AXI_BID), + .i_axi_bresp( M_AXI_BRESP), + .i_axi_bvalid(M_AXI_BVALID), + // + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + .i_axi_arvalid(M_AXI_ARVALID), + // + .i_axi_rid( M_AXI_RID), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rresp( M_AXI_RRESP), + .i_axi_rvalid(M_AXI_RVALID), + // + .f_axi_awr_nbursts(faxi_awr_nbursts), + .f_axi_wr_pending(faxi_wr_pending), + .f_axi_rd_nbursts(faxi_rd_nbursts), + .f_axi_rd_outstanding(faxi_rd_outstanding) + // + // ... + // + ); + + + //////////////////////////////////////////////////////////////////////// + // + // Induction rule checks + // + //////////////////////////////////////////////////////////////////////// + // + // + + // First rule: the AXI solver isn't permitted to have any more write + // bursts outstanding than the AXI-lite interface has. + always @(*) + assert(faxi_awr_nbursts == faxil_awr_outstanding); + + // + // Second: Since our bursts are limited to one value each, and since + // we can't send address bursts ahead of their data counterparts, + // (AXI property limitation) faxi_wr_pending can only ever be one or + // zero, and the counters must match + always @(*) + begin + // + // ... + // + + if (faxil_awr_outstanding != 0) + begin + assert((faxil_awr_outstanding == faxil_wr_outstanding) + ||(faxil_awr_outstanding-1 == faxil_wr_outstanding)); + end + + if (faxil_awr_outstanding == 0) + assert(faxil_wr_outstanding == 0); + end + + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Known "bugs" + // + //////////////////////////////////////////////////////////////////////// + // + // These are really more limitations in the AXI properties than + // bugs in the code, but they need to be documented here. + // + generate if (C_AXI_DATA_WIDTH > 8) + begin + // The AXI-lite property checker doesn't validate WSTRB + // values like it should. If we don't force the AWADDR + // LSBs to zero, the solver will pick an invalid WSTRB. + // + always @(*) + assume(S_AXI_AWADDR[$clog2(C_AXI_DATA_WIDTH)-3:0] == 0); + always @(*) + assert(faxi_wr_addr[$clog2(C_AXI_DATA_WIDTH)-3:0] == 0); + end endgenerate + + // + // The AXI solver can't handle write transactions without either a + // concurrent or proceeding write address burst. Here we make that + // plain. It's not likely to cause any problems, but still worth + // noting. + always @(*) + if (faxil_awr_outstanding == faxil_wr_outstanding) + assume(S_AXI_AWVALID || !S_AXI_WVALID); + else if (faxil_awr_outstanding > faxil_wr_outstanding) + assume(!S_AXI_AWVALID); + + // + // We need to make certain that our counters never overflow. This is + // an assertion within the property checkers, so we need an appropriate + // matching assumption here. In practice, F_LGDEPTH could be set + // so arbitrarily large that this assumption would never get hit, + // but the solver doesn't know that. + always @(*) + if (faxil_rd_outstanding >= {{(F_LGDEPTH-1){1'b1}}, 1'b0 }) + assume(!S_AXI_ARVALID); + + always @(*) + if (faxil_awr_outstanding >= {{(F_LGDEPTH-1){1'b1}}, 1'b0 }) + assume(!S_AXI_AWVALID); + +`endif +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axilrd2wbsp.v b/rtl/wb2axip/axilrd2wbsp.v new file mode 100644 index 0000000..569b5e8 --- /dev/null +++ b/rtl/wb2axip/axilrd2wbsp.v @@ -0,0 +1,601 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilrd2wbsp.v (AXI lite to wishbone slave, read channel) +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Bridge an AXI lite read channel pair to a single wishbone read +// channel. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2016-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axilrd2wbsp #( + // {{{ + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ADDR_WIDTH = 28, + parameter AXILLSB = $clog2(C_AXI_DATA_WIDTH/8), + localparam AW = C_AXI_ADDR_WIDTH-AXILLSB, + localparam DW = C_AXI_DATA_WIDTH, + parameter LGFIFO = 3 + // }}} + ) ( + // {{{ + input wire i_clk, + input wire i_axi_reset_n, + + // AXI read address channel signals + // {{{ + input wire i_axi_arvalid, + output reg o_axi_arready, + input wire [C_AXI_ADDR_WIDTH-1:0] i_axi_araddr, + input wire [2:0] i_axi_arprot, + // }}} + // AXI read data channel signals + // {{{ + output reg o_axi_rvalid, + input wire i_axi_rready, + output wire [C_AXI_DATA_WIDTH-1:0] o_axi_rdata, + output reg [1:0] o_axi_rresp, + // }}} + // Wishbone signals + // {{{ + // We'll share the clock and the reset + output reg o_wb_cyc, + output reg o_wb_stb, + output reg [(AW-1):0] o_wb_addr, + output wire [(DW/8-1):0] o_wb_sel, + input wire i_wb_stall, + input wire i_wb_ack, + input wire [(C_AXI_DATA_WIDTH-1):0] i_wb_data, + input wire i_wb_err +`ifdef FORMAL + // {{{ + // Output connections only used in formal mode + , output wire [LGFIFO:0] f_first, + output wire [LGFIFO:0] f_mid, + output wire [LGFIFO:0] f_last + // }}} +`endif + // }}} + // }}} + ); + + // Local declarations + // {{{ + localparam AXI_LSBS = $clog2(C_AXI_DATA_WIDTH)-3; + + wire w_reset; + assign w_reset = (!i_axi_reset_n); + + reg r_stb; + reg [AW-1:0] r_addr; + + localparam FLEN=(1<<LGFIFO); + + reg [DW-1:0] dfifo [0:(FLEN-1)]; + reg fifo_full, fifo_empty; + + reg [LGFIFO:0] r_first, r_mid, r_last; + wire [LGFIFO:0] next_first, next_last; + reg wb_pending; + reg [LGFIFO:0] wb_outstanding; + wire [DW-1:0] read_data; + reg err_state; + reg [LGFIFO:0] err_loc; + // }}} + + // o_wb_cyc, o_wb_stb + // {{{ + initial o_wb_cyc = 1'b0; + initial o_wb_stb = 1'b0; + always @(posedge i_clk) + if ((w_reset)||((o_wb_cyc)&&(i_wb_err))||(err_state)) + o_wb_stb <= 1'b0; + else if (r_stb || ((i_axi_arvalid)&&(o_axi_arready))) + o_wb_stb <= 1'b1; + else if ((o_wb_cyc)&&(!i_wb_stall)) + o_wb_stb <= 1'b0; + + always @(*) + o_wb_cyc = (wb_pending)||(o_wb_stb); + // }}} + + // o_wb_addr + // {{{ + always @(posedge i_clk) + if (r_stb && !i_wb_stall) + o_wb_addr <= r_addr; + else if ((o_axi_arready)&&((!o_wb_stb)||(!i_wb_stall))) + o_wb_addr <= i_axi_araddr[AW+1:AXI_LSBS]; + // }}} + + // o_wb_sel + // {{{ + assign o_wb_sel = {(DW/8){1'b1}}; + // }}} + + // r_stb, r_addr + // {{{ + // Shadow request + initial r_stb = 1'b0; + always @(posedge i_clk) + begin + if ((i_axi_arvalid)&&(o_axi_arready)&&(o_wb_stb)&&(i_wb_stall)) + begin + r_stb <= 1'b1; + r_addr <= i_axi_araddr[AW+1:AXI_LSBS]; + end else if ((!i_wb_stall)||(!o_wb_cyc)) + r_stb <= 1'b0; + + if ((w_reset)||(o_wb_cyc)&&(i_wb_err)||(err_state)) + r_stb <= 1'b0; + end + // }}} + + // wb_pending, wb_outstanding + // {{{ + initial wb_pending = 0; + initial wb_outstanding = 0; + always @(posedge i_clk) + if ((w_reset)||(!o_wb_cyc)||(i_wb_err)||(err_state)) + begin + wb_pending <= 1'b0; + wb_outstanding <= 0; + end else case({ (o_wb_stb)&&(!i_wb_stall), i_wb_ack }) + 2'b01: begin + wb_outstanding <= wb_outstanding - 1'b1; + wb_pending <= (wb_outstanding >= 2); + end + 2'b10: begin + wb_outstanding <= wb_outstanding + 1'b1; + wb_pending <= 1'b1; + end + default: begin end + endcase + // }}} + + assign next_first = r_first + 1'b1; + assign next_last = r_last + 1'b1; + + // fifo_full, fifo_empty + // {{{ + initial fifo_full = 1'b0; + initial fifo_empty = 1'b1; + always @(posedge i_clk) + if (w_reset) + begin + fifo_full <= 1'b0; + fifo_empty <= 1'b1; + end else case({ (o_axi_rvalid)&&(i_axi_rready), + (i_axi_arvalid)&&(o_axi_arready) }) + 2'b01: begin + fifo_full <= (next_first[LGFIFO-1:0] == r_last[LGFIFO-1:0]) + &&(next_first[LGFIFO]!=r_last[LGFIFO]); + fifo_empty <= 1'b0; + end + 2'b10: begin + fifo_full <= 1'b0; + fifo_empty <= 1'b0; + end + default: begin end + endcase + // }}} + + // o_axi_arready + // {{{ + initial o_axi_arready = 1'b1; + always @(posedge i_clk) + if (w_reset) + o_axi_arready <= 1'b1; + else if ((o_wb_cyc && i_wb_err) || err_state) + // On any error, drop the ready flag until it's been flushed + o_axi_arready <= 1'b0; + else if ((i_axi_arvalid)&&(o_axi_arready)&&(o_wb_stb)&&(i_wb_stall)) + // On any request where we are already busy, r_stb will get + // set and we drop arready + o_axi_arready <= 1'b0; + else if (!o_axi_arready && o_wb_stb && i_wb_stall) + // If we've already stalled on o_wb_stb, remain stalled until + // the bus clears + o_axi_arready <= 1'b0; + else if (fifo_full && (!o_axi_rvalid || !i_axi_rready)) + // If the FIFO is full, we must remain not ready until at + // least one acknowledgment is accepted + o_axi_arready <= 1'b0; + else if ( (!o_axi_rvalid || !i_axi_rready) + && (i_axi_arvalid && o_axi_arready)) + o_axi_arready <= (next_first[LGFIFO-1:0] != r_last[LGFIFO-1:0]) + ||(next_first[LGFIFO]==r_last[LGFIFO]); + else + o_axi_arready <= 1'b1; + // }}} + + // r_first + // {{{ + initial r_first = 0; + always @(posedge i_clk) + if (w_reset) + r_first <= 0; + else if ((i_axi_arvalid)&&(o_axi_arready)) + r_first <= r_first + 1'b1; + // }}} + + // r_mid + // {{{ + initial r_mid = 0; + always @(posedge i_clk) + if (w_reset) + r_mid <= 0; + else if ((o_wb_cyc)&&((i_wb_ack)||(i_wb_err))) + r_mid <= r_mid + 1'b1; + else if ((err_state)&&(r_mid != r_first)) + r_mid <= r_mid + 1'b1; + // }}} + + // r_last + // {{{ + initial r_last = 0; + always @(posedge i_clk) + if (w_reset) + r_last <= 0; + else if ((o_axi_rvalid)&&(i_axi_rready)) + r_last <= r_last + 1'b1; + // }}} + + // Write to dfifo on data return + // {{{ + always @(posedge i_clk) + if ((o_wb_cyc)&&((i_wb_ack)||(i_wb_err))) + dfifo[r_mid[(LGFIFO-1):0]] <= i_wb_data; + // }}} + + // err_loc -- FIFO address of any error + // {{{ + always @(posedge i_clk) + if ((o_wb_cyc)&&(i_wb_err)) + err_loc <= r_mid; + // }}} + + assign read_data = dfifo[r_last[LGFIFO-1:0]]; + assign o_axi_rdata = read_data[DW-1:0]; + + // o_axi_rresp + // {{{ + initial o_axi_rresp = 2'b00; + always @(posedge i_clk) + if (w_reset) + o_axi_rresp <= 0; + else if ((!o_axi_rvalid)||(i_axi_rready)) + begin + if ((!err_state)&&((!o_wb_cyc)||(!i_wb_err))) + o_axi_rresp <= 2'b00; + else if ((!err_state)&&(o_wb_cyc)&&(i_wb_err)) + begin + if (o_axi_rvalid) + o_axi_rresp <= (r_mid == next_last) ? 2'b10 : 2'b00; + else + o_axi_rresp <= (r_mid == r_last) ? 2'b10 : 2'b00; + end else if (err_state) + begin + if (next_last == err_loc) + o_axi_rresp <= 2'b10; + else if (o_axi_rresp[1]) + o_axi_rresp <= 2'b11; + end else + o_axi_rresp <= 0; + end + // }}} + + // err_state + // {{{ + initial err_state = 0; + always @(posedge i_clk) + if (w_reset) + err_state <= 0; + else if (r_first == r_last) + err_state <= 0; + else if ((o_wb_cyc)&&(i_wb_err)) + err_state <= 1'b1; + // }}} + + // o_axi_rvalid + // {{{ + initial o_axi_rvalid = 1'b0; + always @(posedge i_clk) + if (w_reset) + o_axi_rvalid <= 0; + else if ((o_wb_cyc)&&((i_wb_ack)||(i_wb_err))) + o_axi_rvalid <= 1'b1; + else if ((o_axi_rvalid)&&(i_axi_rready)) + begin + if (err_state) + o_axi_rvalid <= (next_last != r_first); + else + o_axi_rvalid <= (next_last != r_mid); + end + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, i_axi_arprot, + i_axi_araddr[AXI_LSBS-1:0], fifo_empty }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Local declarations + // {{{ + reg f_past_valid; + reg f_fifo_empty; + reg [LGFIFO:0] f_fifo_fill; + wire [LGFIFO:0] f_wb_nreqs, f_wb_nacks, f_wb_outstanding; + wire [LGFIFO:0] wb_fill; + wire [LGFIFO:0] f_axi_rd_outstanding, + f_axi_wr_outstanding, + f_axi_awr_outstanding; + wire [LGFIFO:0] f_mid_minus_err, f_err_minus_last, + f_first_minus_err; + // }}} + + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + always @(*) + begin + f_fifo_fill = (r_first - r_last); + f_fifo_empty = (f_fifo_fill == 0); + end + + always @(*) + if (!f_past_valid) + assume(w_reset); + + always @(*) + if (err_state) + assert(!o_axi_arready); + + always @(*) + if (err_state) + assert((!o_wb_cyc)&&(!o_axi_arready)); + + always @(*) + if ((f_fifo_empty)&&(!w_reset)) + assert((!fifo_full)&&(r_first == r_last)&&(r_mid == r_last)); + + always @(*) + if (fifo_full) + assert((!f_fifo_empty) + &&(r_first[LGFIFO-1:0] == r_last[LGFIFO-1:0]) + &&(r_first[LGFIFO] != r_last[LGFIFO])); + + always @(*) + assert(f_fifo_fill <= (1<<LGFIFO)); + + always @(*) + if (fifo_full) + assert(!o_axi_arready); + always @(*) + assert(fifo_full == (f_fifo_fill == (1<<LGFIFO))); + always @(*) + if (f_fifo_fill == (1<<LGFIFO)) + assert(!o_axi_arready); + always @(*) + assert(wb_pending == (wb_outstanding != 0)); + + assign f_first = r_first; + assign f_mid = r_mid; + assign f_last = r_last; + + fwb_master #( + // {{{ + .AW(AW), .DW(DW), .F_LGDEPTH(LGFIFO+1) + // }}} + ) fwb( + // {{{ + i_clk, w_reset, + o_wb_cyc, o_wb_stb, 1'b0, o_wb_addr, {(DW){1'b0}}, o_wb_sel, + i_wb_ack, i_wb_stall, i_wb_data, i_wb_err, + f_wb_nreqs,f_wb_nacks, f_wb_outstanding + // }}} + ); + + always @(*) + if (o_wb_cyc) + assert(f_wb_outstanding == wb_outstanding); + + always @(*) + if (o_wb_cyc) + assert(wb_outstanding <= (1<<LGFIFO)); + + assign wb_fill = r_first - r_mid; + always @(*) + assert(wb_fill <= f_fifo_fill); + always @(*) + if (o_wb_stb) + assert(wb_outstanding+1+((r_stb)?1:0) == wb_fill); + always @(*) + if (o_wb_stb) + begin + assert(&o_wb_sel); + end else if (o_wb_cyc) + assert(wb_outstanding == wb_fill); + + always @(*) + if (r_stb) + begin + assert(o_wb_stb); + assert(!o_axi_arready); + end + + faxil_slave #( + // {{{ + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(LGFIFO+1), + .F_OPT_READ_ONLY(1'b1), + .F_AXI_MAXWAIT(0), + .F_AXI_MAXDELAY(0) + // }}} + ) faxil( + // {{{ + .i_clk(i_clk), .i_axi_reset_n(i_axi_reset_n), + // + // AXI write address channel signals + .i_axi_awvalid(1'b0), .i_axi_awready(1'b0), + .i_axi_awaddr({(AW){1'b0}}), + // AXI write data channel signals + .i_axi_wvalid(1'b0), .i_axi_wready(1'b0), .i_axi_wdata(32'h0), + .i_axi_wstrb(4'h0), + // AXI write response channel signals + .i_axi_bvalid(1'b0), .i_axi_bready(1'b0), .i_axi_bresp(2'b00), + // AXI read address channel signals + .i_axi_arvalid(i_axi_arvalid), .i_axi_arready(o_axi_arready), + .i_axi_araddr(i_axi_araddr),.i_axi_arprot(i_axi_arprot), + // AXI read data channel signals + .i_axi_rvalid(o_axi_rvalid), .i_axi_rready(i_axi_rready), + .i_axi_rdata(o_axi_rdata), .i_axi_rresp(o_axi_rresp), + .f_axi_rd_outstanding(f_axi_rd_outstanding), + .f_axi_wr_outstanding(f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding) + // }}} + ); + + always @(*) + assert(f_axi_wr_outstanding == 0); + always @(*) + assert(f_axi_awr_outstanding == 0); + always @(*) + assert(f_axi_rd_outstanding == f_fifo_fill); + + assign f_mid_minus_err = f_mid - err_loc; + assign f_err_minus_last = err_loc - f_last; + assign f_first_minus_err = f_first - err_loc; + + always @(*) + if (o_axi_rvalid) + begin + if (!err_state) + begin + assert(!o_axi_rresp[1]); + end else if (err_loc == f_last) + begin + assert(o_axi_rresp == 2'b10); + end else if (f_err_minus_last < (1<<LGFIFO)) + begin + assert(!o_axi_rresp[1]); + end else + assert(o_axi_rresp[1]); + end + + always @(*) + if (err_state) + begin + assert(o_axi_rvalid == (r_first != r_last)); + end else + assert(o_axi_rvalid == (r_mid != r_last)); + + always @(*) + if (err_state) + assert(f_first_minus_err <= (1<<LGFIFO)); + + always @(*) + if (err_state) + assert(f_first_minus_err != 0); + + always @(*) + if (err_state) + assert(f_mid_minus_err <= f_first_minus_err); + + always @(*) + if ((f_past_valid)&&(i_axi_reset_n)&&(f_axi_rd_outstanding > 0)) + begin + if (err_state) + begin + assert((!o_wb_cyc)&&(f_wb_outstanding == 0)); + end else if (!o_wb_cyc) + assert((o_axi_rvalid)&&(f_axi_rd_outstanding>0) + &&(wb_fill == 0)); + end + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + cover(o_wb_cyc && o_wb_stb); + + always @(*) + if (LGFIFO > 2) + cover(o_wb_cyc && f_wb_outstanding > 2); + + always @(posedge i_clk) + cover(o_wb_cyc && i_wb_ack + && $past(o_wb_cyc && i_wb_ack) + && $past(o_wb_cyc && i_wb_ack,2)); + + // AXI covers + always @(*) + cover(o_axi_rvalid && i_axi_rready); + + always @(posedge i_clk) + cover(i_axi_arvalid && o_axi_arready + && $past(i_axi_arvalid && o_axi_arready) + && $past(i_axi_arvalid && o_axi_arready,2)); + + always @(posedge i_clk) + cover(o_axi_rvalid && i_axi_rready + && $past(o_axi_rvalid && i_axi_rready) + && $past(o_axi_rvalid && i_axi_rready,2)); + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_formal; + assign unused_formal = &{ 1'b0, f_wb_nreqs, f_wb_nacks }; + // Verilator lint_on UNUSED + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axilsafety.v b/rtl/wb2axip/axilsafety.v new file mode 100644 index 0000000..ff8b391 --- /dev/null +++ b/rtl/wb2axip/axilsafety.v @@ -0,0 +1,1449 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilsafety.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A AXI-Lite bus fault isolator. This core will isolate any +// downstream AXI-liite slave faults from the upstream channel. +// It sits as a bump in the wire between upstream and downstream channels, +// and so it will consume two clocks--slowing down the slave, but +// potentially allowing developer to recover in case of a fault. +// +// This core is configured by a couple parameters, which are key to its +// functionality. +// +// OPT_TIMEOUT Set this to a number to be roughly the longest time +// period you expect the slave to stall the bus, or likewise +// the longest time period you expect it to wait for a response. +// If the slave takes longer for either task, a fault will be +// detected and reported. +// +// OPT_SELF_RESET If set, this will send a reset signal to the downstream +// core so that you can attempt to restart it without reloading +// the FPGA. If set, the o_reset signal will be used to reset +// the downstream core. +// +// A second key feature of this core are the outgoing fault indicators, +// o_write_fault and o_read_fault. If either signal is ever raised, the +// slave has (somehow) violated protocol on either the write or the +// read channels respectively. Such a violation may (or may not) return an +// error upstream. For example, if the slave returns a response +// following no requests from the master, then no error will be returned +// up stream (doing so would be a protocol violation), but a fault will +// still be detected. Use this line to trigger any internal logic +// analyzers. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axilsafety #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 28, + parameter C_AXI_DATA_WIDTH = 32, + parameter OPT_TIMEOUT = 12, + parameter MAX_DEPTH = (OPT_TIMEOUT), + localparam AW = C_AXI_ADDR_WIDTH, + localparam DW = C_AXI_DATA_WIDTH, + localparam LGTIMEOUT = $clog2(OPT_TIMEOUT+1), + localparam LGDEPTH = $clog2(MAX_DEPTH+1), + parameter [0:0] OPT_SELF_RESET = 1'b1, + parameter OPT_MIN_RESET = 16 +`ifdef FORMAL + , parameter [0:0] F_OPT_WRITES = 1'b1, + parameter [0:0] F_OPT_READS = 1'b1, + parameter [0:0] F_OPT_FAULTLESS = 1'b1 +`endif + // }}} + ) ( + // {{{ + output reg o_write_fault, + output reg o_read_fault, + // + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + output reg M_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output reg S_AXI_AWREADY, + input wire [AW-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output reg S_AXI_WREADY, + input wire [DW-1:0] S_AXI_WDATA, + input wire [DW/8-1:0] S_AXI_WSTRB, + // + output reg S_AXI_BVALID, + input wire S_AXI_BREADY, + output reg [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output reg S_AXI_ARREADY, + input wire [AW-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output reg S_AXI_RVALID, + input wire S_AXI_RREADY, + output reg [DW-1:0] S_AXI_RDATA, + output reg [1:0] S_AXI_RRESP, + // + // + // + output reg M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output reg [AW-1:0] M_AXI_AWADDR, + output reg [2:0] M_AXI_AWPROT, + // + output reg M_AXI_WVALID, + input wire M_AXI_WREADY, + output reg [DW-1:0] M_AXI_WDATA, + output reg [DW/8-1:0] M_AXI_WSTRB, + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [1:0] M_AXI_BRESP, + // + output reg M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output reg [AW-1:0] M_AXI_ARADDR, + output reg [2:0] M_AXI_ARPROT, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [DW-1:0] M_AXI_RDATA, + input wire [1:0] M_AXI_RRESP + // }}} + ); + + // localparam, wire, and register declarations + // {{{ + localparam OPT_LOWPOWER = 1'b0; + localparam OKAY = 2'b00, + EXOKAY = 2'b01, + SLVERR = 2'b10; + + reg [LGDEPTH-1:0] aw_count, w_count, r_count; + reg aw_zero, w_zero, r_zero, + aw_full, w_full, r_full, + aw_w_greater, w_aw_greater; + reg [LGDEPTH-1:0] downstream_aw_count, downstream_w_count, downstream_r_count; + reg downstream_aw_zero, downstream_w_zero, downstream_r_zero; + // downstream_aw_w_greater, downstream_w_aw_greater; + + wire awskd_valid; + wire [2:0] awskd_prot; + wire [AW-1:0] awskd_addr; + reg awskd_ready; + + wire wskd_valid; + wire [DW-1:0] wskd_data; + wire [DW/8-1:0] wskd_strb; + reg wskd_ready; + + wire bskd_valid; + wire [1:0] bskd_resp; + reg bskd_ready; + + reg last_bvalid; + reg [1:0] last_bdata; + reg last_bchanged; + + wire arskd_valid; + wire [2:0] arskd_prot; + wire [AW-1:0] arskd_addr; + reg arskd_ready; + + reg last_rvalid; + reg [DW+1:0] last_rdata; + reg last_rchanged; + + wire rskd_valid; + wire [1:0] rskd_resp; + wire [DW-1:0] rskd_data; + reg rskd_ready; + + reg [LGTIMEOUT-1:0] aw_stall_counter, w_stall_counter, + r_stall_counter, w_ack_timer, r_ack_timer; + reg aw_stall_limit, w_stall_limit, r_stall_limit, + w_ack_limit, r_ack_limit; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write address channel + // {{{ + + skidbuffer #(.DW(AW+3) + // {{{ +`ifdef FORMAL + , .OPT_PASSTHROUGH(1'b1) +`endif + // }}} + ) awskd(S_AXI_ACLK, !S_AXI_ARESETN, + // {{{ + S_AXI_AWVALID, S_AXI_AWREADY, { S_AXI_AWPROT, S_AXI_AWADDR }, + awskd_valid, awskd_ready, { awskd_prot, awskd_addr}); + // }}} + + // awskd_ready + // {{{ + // awskd_ready is the critical piece here, since it determines when + // we accept a packet from the skid buffer. + // + always @(*) + if (!M_AXI_ARESETN || o_write_fault) + // On any fault, we'll always accept a request (and return an + // error). We always accept a value if there are already + // more writes than write addresses accepted. + awskd_ready = (w_aw_greater) + ||((aw_zero)&&(!S_AXI_BVALID || S_AXI_BREADY)); + else + // Otherwise, we accept if ever our counters aren't about to + // overflow, and there's a place downstream to accept it + awskd_ready = (!M_AXI_AWVALID || M_AXI_AWREADY)&& (!aw_full); + // }}} + + // M_AXI_AWVALID + // {{{ + initial M_AXI_AWVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN) + M_AXI_AWVALID <= 1'b0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + M_AXI_AWVALID <= awskd_valid && awskd_ready && !o_write_fault; + // }}} + + // M_AXI_AWADDR, M_AXI_AWPROT + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!M_AXI_ARESETN || o_write_fault)) + begin + M_AXI_AWADDR <= 0; + M_AXI_AWPROT <= 0; + end else if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + M_AXI_AWADDR <= awskd_addr; + M_AXI_AWPROT <= awskd_prot; + end + // }}} + // }}} + + // + // Write data channel + // {{{ + + skidbuffer #(.DW(DW+DW/8) + // {{{ +`ifdef FORMAL + , .OPT_PASSTHROUGH(1'b1) +`endif + // }}} + ) wskd(S_AXI_ACLK, !S_AXI_ARESETN, + // {{{ + S_AXI_WVALID, S_AXI_WREADY, { S_AXI_WDATA, S_AXI_WSTRB }, + wskd_valid, wskd_ready, { wskd_data, wskd_strb}); + // }}} + + // wskd_ready + // {{{ + // As with awskd_ready, this logic is the critical key. + // + always @(*) + if (!M_AXI_ARESETN || o_write_fault) + wskd_ready = (aw_w_greater) + || ((w_zero)&&(!S_AXI_BVALID || S_AXI_BREADY)); + else + wskd_ready = (!M_AXI_WVALID || M_AXI_WREADY) && (!w_full); + // }}} + + // M_AXI_WVALID + // {{{ + initial M_AXI_WVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN) + M_AXI_WVALID <= 1'b0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + M_AXI_WVALID <= wskd_valid && wskd_ready && !o_write_fault; + // }}} + + // M_AXI_WDATA, M_AXI_WSTRB + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!M_AXI_ARESETN || o_write_fault)) + begin + M_AXI_WDATA <= 0; + M_AXI_WSTRB <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + M_AXI_WDATA <= wskd_data; + M_AXI_WSTRB <= (o_write_fault) ? 0 : wskd_strb; + end + // }}} + // }}} + + // + // Write return channel + // {{{ + + // bskd_valid, M_AXI_BREADY, bskd_resp + // {{{ +`ifdef FORMAL + assign bskd_valid = M_AXI_BVALID; + assign M_AXI_BREADY= bskd_ready; + assign bskd_resp = M_AXI_BRESP; +`else + skidbuffer #(.DW(2) + ) bskd(S_AXI_ACLK, !S_AXI_ARESETN || !M_AXI_ARESETN, + M_AXI_BVALID, M_AXI_BREADY, M_AXI_BRESP, + bskd_valid, bskd_ready, bskd_resp); +`endif + // }}} + + // bskd_ready + // {{{ + always @(*) + if (o_write_fault) + bskd_ready = 1'b1; + else + bskd_ready = (!S_AXI_BVALID || S_AXI_BREADY); + // }}} + + // S_AXI_BVALID + // {{{ + initial S_AXI_BVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_BVALID <= 1'b0; + else if (!S_AXI_BVALID || S_AXI_BREADY) + begin + if (o_write_fault || !M_AXI_ARESETN) + S_AXI_BVALID <= (!S_AXI_BVALID&&(!aw_zero)&&(!w_zero)); + else + S_AXI_BVALID <= (!downstream_aw_zero) + &&(!downstream_w_zero)&&(bskd_valid); + end + // }}} + + // last_bvalid + // {{{ + initial last_bvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!M_AXI_ARESETN || o_write_fault) + last_bvalid <= 1'b0; + else + last_bvalid <= (M_AXI_BVALID && !M_AXI_BREADY); + // }}} + + // last_bdata + // {{{ + always @(posedge S_AXI_ACLK) + if (M_AXI_BVALID) + last_bdata <= M_AXI_BRESP; + // }}} + + // last_bchanged + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + last_bchanged <= 1'b0; + else + last_bchanged <= (last_bvalid && (!M_AXI_BVALID + || last_bdata != M_AXI_BRESP)); + // }}} + + // S_AXI_BRESP + // {{{ + initial S_AXI_BRESP = OKAY; + always @(posedge S_AXI_ACLK) + if (!S_AXI_BVALID || S_AXI_BREADY) + begin + if (o_write_fault) + S_AXI_BRESP <= SLVERR; + else if (bskd_resp == EXOKAY) + S_AXI_BRESP <= SLVERR; + else + S_AXI_BRESP <= bskd_resp; + end + // }}} + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Read address channel + // {{{ + + skidbuffer #(.DW(AW+3) + // {{{ +`ifdef FORMAL + , .OPT_PASSTHROUGH(1'b1) +`endif + // }}} + ) arskd(S_AXI_ACLK, !S_AXI_ARESETN, + // {{{ + S_AXI_ARVALID, S_AXI_ARREADY, { S_AXI_ARPROT, S_AXI_ARADDR }, + arskd_valid, arskd_ready, { arskd_prot, arskd_addr }); + // }}} + + // arskd_ready + // {{{ + always @(*) + if (!M_AXI_ARESETN || o_read_fault) + arskd_ready =((r_zero)&&(!S_AXI_RVALID || S_AXI_RREADY)); + else + arskd_ready = (!M_AXI_ARVALID || M_AXI_ARREADY) && (!r_full); + // }}} + + // M_AXI_ARVALID + // {{{ + initial M_AXI_ARVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN) + M_AXI_ARVALID <= 1'b0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + M_AXI_ARVALID <= arskd_valid && arskd_ready && !o_read_fault; + // }}} + + // M_AXI_ARADDR, M_AXI_ARPROT + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!M_AXI_ARESETN || o_read_fault)) + begin + M_AXI_ARADDR <= 0; + M_AXI_ARPROT <= 0; + end else if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + M_AXI_ARADDR <= arskd_addr; + M_AXI_ARPROT <= arskd_prot; + end + // }}} + // }}} + + // + // Read data channel + // {{{ + + // rskd_valid, rskd_resp, rskd_data skid buffer + // {{{ +`ifdef FORMAL + assign rskd_valid = M_AXI_RVALID; + assign M_AXI_RREADY = rskd_ready; + assign { rskd_resp, rskd_data } = { M_AXI_RRESP, M_AXI_RDATA }; +`else + skidbuffer #(.DW(DW+2) + ) rskd(S_AXI_ACLK, !S_AXI_ARESETN || !M_AXI_ARESETN, + M_AXI_RVALID, M_AXI_RREADY, { M_AXI_RRESP, M_AXI_RDATA }, + rskd_valid, rskd_ready, { rskd_resp, rskd_data }); +`endif + // ?}}} + + // rskd_ready + // {{{ + always @(*) + if (o_read_fault) + rskd_ready = 1; + else + rskd_ready = (!S_AXI_RVALID || S_AXI_RREADY); + // }}} + + // S_AXI_RVALID + // {{{ + initial S_AXI_RVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_RVALID <= 1'b0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (o_read_fault || !M_AXI_ARESETN) + S_AXI_RVALID <= (!S_AXI_RVALID && !r_zero) + || (arskd_valid && arskd_ready); + else + S_AXI_RVALID <= (!downstream_r_zero)&&(rskd_valid); + end + // }}} + + // S_AXI_RDATA, S_AXI_RRESP + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_RVALID || S_AXI_RREADY) + begin + if (o_read_fault || !M_AXI_ARESETN) + S_AXI_RDATA <= 0; + else + S_AXI_RDATA <= rskd_data; + + S_AXI_RRESP <= OKAY; + if (o_read_fault || rskd_resp == EXOKAY || !M_AXI_ARESETN) + S_AXI_RRESP <= SLVERR; + else if (!downstream_r_zero) + S_AXI_RRESP <= rskd_resp; + end + // }}} + + // last_rvalid + // {{{ + initial last_rvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + last_rvalid <= 1'b0; + else + last_rvalid <= (M_AXI_RVALID && !M_AXI_RREADY); + // }}} + + // last_rdata + // {{{ + always @(posedge S_AXI_ACLK) + if (M_AXI_RVALID) + last_rdata <= { M_AXI_RRESP, M_AXI_RDATA }; + // }}} + + // last_rchanged + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + last_rchanged <= 1'b0; + else + last_rchanged <= (last_rvalid && (!M_AXI_RVALID + || last_rdata != { M_AXI_RRESP, M_AXI_RDATA })); + // }}} + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Usage counters + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write address channel + // {{{ + + initial aw_count = 0; + initial aw_zero = 1; + initial aw_full = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + aw_count <= 0; + aw_zero <= 1; + aw_full <= 0; + end else case({(awskd_valid && awskd_ready),S_AXI_BVALID&&S_AXI_BREADY}) + 2'b10: begin + aw_count <= aw_count + 1; + aw_zero <= 0; + aw_full <= (aw_count == { {(LGDEPTH-1){1'b1}}, 1'b0 }); + end + 2'b01: begin + aw_count <= aw_count - 1; + aw_zero <= (aw_count <= 1); + aw_full <= 0; + end + default: begin end + endcase + // }}} + + // + // Write data channel + // {{{ + initial w_count = 0; + initial w_zero = 1; + initial w_full = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + w_count <= 0; + w_zero <= 1; + w_full <= 0; + end else case({(wskd_valid && wskd_ready), S_AXI_BVALID&& S_AXI_BREADY}) + 2'b10: begin + w_count <= w_count + 1; + w_zero <= 0; + w_full <= (w_count == { {(LGDEPTH-1){1'b1}}, 1'b0 }); + end + 2'b01: begin + w_count <= w_count - 1; + w_zero <= (w_count <= 1); + w_full <= 1'b0; + end + default: begin end + endcase + // }}} + + // aw_w_greater, w_aw_greater + // {{{ + initial aw_w_greater = 0; + initial w_aw_greater = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + aw_w_greater <= 0; + w_aw_greater <= 0; + end else case({(awskd_valid && awskd_ready), + (wskd_valid && wskd_ready)}) + 2'b10: begin + aw_w_greater <= (aw_count + 1 > w_count); + w_aw_greater <= ( w_count > aw_count + 1); + end + 2'b01: begin + aw_w_greater <= (aw_count > w_count + 1); + w_aw_greater <= ( w_count + 1 > aw_count); + end + default: begin end + endcase + // }}} + // + // Read channel + // {{{ + + // r_count, r_zero, r_full + initial r_count = 0; + initial r_zero = 1; + initial r_full = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_count <= 0; + r_zero <= 1; + r_full <= 0; + end else case({(arskd_valid&&arskd_ready), S_AXI_RVALID&&S_AXI_RREADY}) + 2'b10: begin + r_count <= r_count + 1; + r_zero <= 0; + r_full <= (r_count == { {(LGDEPTH-1){1'b1}}, 1'b0 }); + end + 2'b01: begin + r_count <= r_count - 1; + r_zero <= (r_count <= 1); + r_full <= 0; + end + default: begin end + endcase + // }}} + + // + // Downstream write address channel + // {{{ + + initial downstream_aw_count = 0; + initial downstream_aw_zero = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + begin + downstream_aw_count <= 0; + downstream_aw_zero <= 1; + end else case({(M_AXI_AWVALID && M_AXI_AWREADY), M_AXI_BVALID && M_AXI_BREADY}) + 2'b10: begin + downstream_aw_count <= downstream_aw_count + 1; + downstream_aw_zero <= 0; + end + 2'b01: begin + downstream_aw_count <= downstream_aw_count - 1; + downstream_aw_zero <= (downstream_aw_count <= 1); + end + default: begin end + endcase + // }}} + + // + // Downstream write data channel + // {{{ + initial downstream_w_count = 0; + initial downstream_w_zero = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + begin + downstream_w_count <= 0; + downstream_w_zero <= 1; + end else case({(M_AXI_WVALID && M_AXI_WREADY), M_AXI_BVALID && M_AXI_BREADY}) + 2'b10: begin + downstream_w_count <= downstream_w_count + 1; + downstream_w_zero <= 0; + end + 2'b01: begin + downstream_w_count <= downstream_w_count - 1; + downstream_w_zero <= (downstream_w_count <= 1); + end + default: begin end + endcase + // }}} + + // + // Downstream read channel + // {{{ + + initial downstream_r_count = 0; + initial downstream_r_zero = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + begin + downstream_r_count <= 0; + downstream_r_zero <= 1; + end else case({M_AXI_ARVALID && M_AXI_ARREADY, M_AXI_RVALID && M_AXI_RREADY}) + 2'b10: begin + downstream_r_count <= downstream_r_count + 1; + downstream_r_zero <= 0; + end + 2'b01: begin + downstream_r_count <= downstream_r_count - 1; + downstream_r_zero <= (downstream_r_count <= 1); + end + default: begin end + endcase + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Timeout checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // The key piece here is that we define the timeout depending upon + // what happens (or doesn't happen) *DOWNSTREAM*. These timeouts + // will need to propagate upstream before taking place. + + // + // Write address stall counter + // {{{ + initial aw_stall_counter = 0; + initial aw_stall_limit = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_write_fault || !M_AXI_ARESETN) + begin + aw_stall_counter <= 0; + aw_stall_limit <= 0; + end else if (!M_AXI_AWVALID || M_AXI_AWREADY || M_AXI_BVALID) + begin + aw_stall_counter <= 0; + aw_stall_limit <= 0; + end else if (aw_w_greater && !M_AXI_WVALID) + begin + aw_stall_counter <= 0; + aw_stall_limit <= 0; + end else // if (!S_AXI_BVALID || S_AXI_BREADY) + begin + aw_stall_counter <= aw_stall_counter + 1; + aw_stall_limit <= (aw_stall_counter+1 >= OPT_TIMEOUT); + end + // }}} + + // + // Write data stall counter + // {{{ + initial w_stall_counter = 0; + initial w_stall_limit = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + begin + w_stall_counter <= 0; + w_stall_limit <= 0; + end else if (!M_AXI_WVALID || M_AXI_WREADY || M_AXI_BVALID) + begin + w_stall_counter <= 0; + w_stall_limit <= 0; + end else if (w_aw_greater && !M_AXI_AWVALID) + begin + w_stall_counter <= 0; + w_stall_limit <= 0; + end else // if (!M_AXI_BVALID || M_AXI_BREADY) + begin + w_stall_counter <= w_stall_counter + 1; + w_stall_limit <= (w_stall_counter + 1 >= OPT_TIMEOUT); + end + // }}} + + // + // Write acknowledgment delay counter + // {{{ + initial w_ack_timer = 0; + initial w_ack_limit = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + begin + w_ack_timer <= 0; + w_ack_limit <= 0; + end else if (M_AXI_BVALID || downstream_aw_zero || downstream_w_zero) + begin + w_ack_timer <= 0; + w_ack_limit <= 0; + end else + begin + w_ack_timer <= w_ack_timer + 1; + w_ack_limit <= (w_ack_timer + 1 >= OPT_TIMEOUT); + end + // }}} + + // + // Read request stall counter + // {{{ + initial r_stall_counter = 0; + initial r_stall_limit = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + begin + r_stall_counter <= 0; + r_stall_limit <= 0; + end else if (!M_AXI_ARVALID || M_AXI_ARREADY || M_AXI_RVALID) + begin + r_stall_counter <= 0; + r_stall_limit <= 0; + end else begin + r_stall_counter <= r_stall_counter + 1; + r_stall_limit <= (r_stall_counter + 1 >= OPT_TIMEOUT); + end + // }}} + + // + // Read acknowledgement delay counter + // {{{ + initial r_ack_timer = 0; + initial r_ack_limit = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + begin + r_ack_timer <= 0; + r_ack_limit <= 0; + end else if (M_AXI_RVALID || downstream_r_zero) + begin + r_ack_timer <= 0; + r_ack_limit <= 0; + end else begin + r_ack_timer <= r_ack_timer + 1; + r_ack_limit <= (r_ack_timer + 1 >= OPT_TIMEOUT); + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Fault detection + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Determine if a write fault has taken place + // {{{ + initial o_write_fault =1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + o_write_fault <= 1'b0; + else if (OPT_SELF_RESET && o_write_fault) + begin + // + // Clear any fault on reset + if (!M_AXI_ARESETN) + o_write_fault <= 1'b0; + end else begin + // + // A write fault takes place if you respond without a prior + // bus request on both write address and write data channels. + if ((downstream_aw_zero || downstream_w_zero)&&(bskd_valid)) + o_write_fault <= 1'b1; + + // AXI-lite slaves are not allowed to return EXOKAY responses + // from the bus + if (bskd_valid && bskd_resp == EXOKAY) + o_write_fault <= 1'b1; + + // If the downstream core refuses to accept either a + // write address request, or a write data request, or for that + // matter if it doesn't return an acknowledgment in a timely + // fashion, then a fault has been detected. + if (aw_stall_limit || w_stall_limit || w_ack_limit) + o_write_fault <= 1'b1; + + // If the downstream core changes BRESP while VALID && !READY, + // then it isn't stalling the channel properly--that's a fault. + if (last_bchanged) + o_write_fault <= 1'b1; + end + // }}} + + // o_read_fault + // {{{ + initial o_read_fault =1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + o_read_fault <= 1'b0; + else if (OPT_SELF_RESET && o_read_fault) + begin + // + // Clear any fault on reset + if (!M_AXI_ARESETN) + o_read_fault <= 1'b0; + end else begin + // Responding without a prior request is a fault. Can only + // respond after a request has been made. + if (downstream_r_zero && rskd_valid) + o_read_fault <= 1'b1; + + // AXI-lite slaves are not allowed to return EXOKAY. This is + // an error. + if (rskd_valid && rskd_resp == EXOKAY) + o_read_fault <= 1'b1; + + // The slave cannot stall the bus forever, nor should the + // master wait forever for a response from the slave. + if (r_stall_limit || r_ack_limit) + o_read_fault <= 1'b1; + + // If the slave changes the data, or the RRESP on the wire, + // while the incoming bus is stalled, then that's also a fault. + if (last_rchanged) + o_read_fault <= 1'b1; + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Self reset handling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_SELF_RESET) + begin : SELF_RESET_GENERATION + // {{{ + wire min_reset; + + if (OPT_MIN_RESET > 1) + begin : MIN_RESET + // {{{ + reg r_min_reset; + reg [$clog2(OPT_MIN_RESET+1):0] reset_counter; + + // + // Optionally insist that any downstream reset have a + // minimum duration. Many Xilinx components require + // a 16-clock reset. This ensures such reset + // requirements are achieved. + // + + initial reset_counter = OPT_MIN_RESET-1; + initial r_min_reset = 1'b0; + always @(posedge S_AXI_ARESETN) + if (M_AXI_ARESETN) + begin + reset_counter <= OPT_MIN_RESET-1; + r_min_reset <= 1'b0; + end else if (!M_AXI_ARESETN) + begin + if (reset_counter > 0) + reset_counter <= reset_counter-1; + min_reset <= (reset_counter <= 1); + end + + assign min_reset = r_min_reset; +`ifdef FORMAL + always @(*) + assert(reset_counter < OPT_MIN_RESET); + always @(*) + assert(min_reset == (reset_counter == 0)); +`endif + // }}} + end else begin : NO_MIN_RESET + // {{{ + assign min_reset = 1'b1; + // }}} + end + + // M_AXI_ARESETN + // {{{ + // Reset the downstream bus on either a write or a read fault. + // Once the bus returns to idle, and any minimum reset durations + // have been achieved, then release the downstream from reset. + // + initial M_AXI_ARESETN = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXI_ARESETN <= 1'b0; + else if (o_write_fault || o_read_fault) + M_AXI_ARESETN <= 1'b0; + else if (aw_zero && w_zero && r_zero && min_reset + && !awskd_valid && !wskd_valid && !arskd_valid) + M_AXI_ARESETN <= 1'b1; + // }}} + // }}} + end else begin : SAME_RESET + // {{{ + // + // The downstream reset equals the upstream reset + // + always @(*) + M_AXI_ARESETN = S_AXI_ARESETN; + // }}} + end endgenerate + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // {{{ + // + // The following proof comes in several parts. + // + // 1. PROVE that the upstream properties will hold independent of + // what the downstream slave ever does. + // + // 2. PROVE that if the downstream slave follows protocol, then + // neither o_write_fault nor o_read_fault will never get raised. + // + // We then repeat these proofs again with both OPT_SELF_RESET set and + // clear. Which of the four proofs is accomplished is dependent upon + // parameters set by the formal engine. + // + // + wire [LGDEPTH:0] faxils_awr_outstanding, faxils_wr_outstanding, + faxils_rd_outstanding; + + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Upstream master Bus properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + if (!f_past_valid) + begin + assume(!S_AXI_ARESETN); + assert(!M_AXI_ARESETN); + end + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_OPT_ASSUME_RESET(1'b1), + .F_OPT_NO_RESET((OPT_MIN_RESET == 0) ? 1:0), +// .F_AXI_MAXWAIT((F_OPT_FAULTLESS) ? (2*OPT_TIMEOUT+2) : 0), + .F_AXI_MAXRSTALL(3), +// .F_AXI_MAXDELAY(OPT_TIMEOUT+OPT_TIMEOUT+5), + .F_LGDEPTH(LGDEPTH+1), + // + .F_AXI_MAXWAIT((F_OPT_FAULTLESS) ? (2*OPT_TIMEOUT+2) : 0), + .F_AXI_MAXDELAY(2*OPT_TIMEOUT+3) + // }}} + ) axils ( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_awr_outstanding(faxils_awr_outstanding), + .f_axi_wr_outstanding(faxils_wr_outstanding), + .f_axi_rd_outstanding(faxils_rd_outstanding) + // }}} + ); + + always @(*) + if (!F_OPT_WRITES) + begin + // {{{ + assume(!S_AXI_AWVALID); + assume(!S_AXI_WVALID); + assert(aw_count == 0); + assert(w_count == 0); + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + // }}} + end + + always @(*) + if (!F_OPT_READS) + begin + // {{{ + assume(!S_AXI_ARVALID); + assert(r_count == 0); + assert(!S_AXI_RVALID); + assert(!M_AXI_ARVALID); + // }}} + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // General Induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + always @(*) + begin + // {{{ + assert(aw_zero == (aw_count == 0)); + assert(w_zero == (w_count == 0)); + assert(r_zero == (r_count == 0)); + + // + assert(aw_full == (&aw_count)); + assert(w_full == (&w_count)); + assert(r_full == (&r_count)); + // + if (M_AXI_ARESETN && !o_write_fault) + begin + assert(downstream_aw_zero == (downstream_aw_count == 0)); + assert(downstream_w_zero == (downstream_w_count == 0)); + assert(downstream_aw_count + (M_AXI_AWVALID ? 1:0) + + (S_AXI_BVALID ? 1:0) == aw_count); + assert(downstream_w_count + (M_AXI_WVALID ? 1:0) + + (S_AXI_BVALID ? 1:0) == w_count); + end + + if (M_AXI_ARESETN && !o_read_fault) + begin + assert(downstream_r_zero == (downstream_r_count == 0)); + assert(downstream_r_count + (M_AXI_ARVALID ? 1:0) + + (S_AXI_RVALID ? 1:0) == r_count); + end + // + assert(aw_count == faxils_awr_outstanding); + assert(w_count == faxils_wr_outstanding); + assert(r_count == faxils_rd_outstanding); + + assert(aw_w_greater == (aw_count > w_count)); + assert(w_aw_greater == (w_count > aw_count)); + // }}} + end + // }}} + generate if (F_OPT_FAULTLESS) + begin : ASSUME_FAULTLESS + // {{{ + //////////////////////////////////////////////////////////////// + // + // Assume the downstream core is protocol compliant, and + // prove that o_fault stays low. + // {{{ + //////////////////////////////////////////////////////////////// + // + wire [LGDEPTH:0] faxilm_awr_outstanding, + faxilm_wr_outstanding, + faxilm_rd_outstanding; + + faxil_master #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_OPT_NO_RESET((OPT_MIN_RESET == 0) ? 1:0), + .F_AXI_MAXWAIT(OPT_TIMEOUT), + .F_AXI_MAXRSTALL(4), + .F_AXI_MAXDELAY(OPT_TIMEOUT), + .F_LGDEPTH(LGDEPTH+1) + // }}} + ) axilm ( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(M_AXI_ARESETN && S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awprot( M_AXI_AWPROT), + // + .i_axi_wvalid(M_AXI_WVALID), + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + // + .i_axi_bvalid(M_AXI_BVALID), + .i_axi_bready(M_AXI_BREADY), + .i_axi_bresp( M_AXI_BRESP), + // + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arprot( M_AXI_ARPROT), + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rresp( M_AXI_RRESP), + // + .f_axi_awr_outstanding(faxilm_awr_outstanding), + .f_axi_wr_outstanding(faxilm_wr_outstanding), + .f_axi_rd_outstanding(faxilm_rd_outstanding) + // }}} + ); + + // + // Here's the big proof + // {{{ + always @(*) + assert(!o_write_fault); + always @(*) + assert(!o_read_fault); + // }}} + // }}} + //////////////////////////////////////////////////////////////// + // + // The following properties are necessary for passing induction + // {{{ + //////////////////////////////////////////////////////////////// + // + // + always @(*) + begin + assert(!aw_stall_limit); + assert(!w_stall_limit); + assert(!w_ack_limit); + + assert(!r_stall_limit); + assert(!r_ack_limit); + + if (M_AXI_ARESETN) + begin + assert(downstream_aw_count == faxilm_awr_outstanding); + assert(downstream_w_count == faxilm_wr_outstanding); + assert(downstream_r_count == faxilm_rd_outstanding); + end + end + + if (OPT_SELF_RESET) + begin + always @(posedge S_AXI_ACLK) + if (f_past_valid) + assert(M_AXI_ARESETN == $past(S_AXI_ARESETN)); + end + +`ifdef VERIFIC + wire [LGDEPTH:0] f_axi_arstall; + wire [LGDEPTH:0] f_axi_awstall; + wire [LGDEPTH:0] f_axi_wstall; + + assign f_axi_awstall = axilm.CHECK_STALL_COUNT.f_axi_awstall; + assign f_axi_wstall = axilm.CHECK_STALL_COUNT.f_axi_wstall; + assign f_axi_arstall = axilm.CHECK_STALL_COUNT.f_axi_arstall; + + always @(*) + if (M_AXI_ARESETN && S_AXI_ARESETN && !o_write_fault) + assert(f_axi_awstall == aw_stall_counter); + + always @(*) + if (M_AXI_ARESETN && S_AXI_ARESETN && !o_write_fault) + assert(f_axi_wstall == w_stall_counter); + + always @(*) + if (M_AXI_ARESETN && S_AXI_ARESETN && !o_read_fault) + assert(f_axi_arstall == r_stall_counter); +`endif + // }}} + // }}} + end else begin : WILD_DOWNSTREAM + // {{{ + //////////////////////////////////////////////////////////////// + // + // cover() checks, checks that only make sense if faults are + // possible + // + + reg write_faulted, read_faulted, faulted; + reg [3:0] cvr_writes, cvr_reads; + + + if (OPT_SELF_RESET) + begin + //////////////////////////////////////////////////////// + // + // Prove that we can actually reset the downstream + // bus/core as desired + // {{{ + //////////////////////////////////////////////////////// + // + // + initial write_faulted = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + write_faulted <= 0; + else if (o_write_fault) + write_faulted <= 1; + + + initial faulted = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + read_faulted <= 0; + else if (o_read_fault) + read_faulted <= 1; + + always @(*) + faulted = (write_faulted || read_faulted); + + always @(posedge S_AXI_ACLK) + cover(write_faulted && $rose(M_AXI_ARESETN)); + + always @(posedge S_AXI_ACLK) + cover(read_faulted && $rose(M_AXI_ARESETN)); + + always @(posedge S_AXI_ACLK) + cover(faulted && M_AXI_ARESETN && S_AXI_BVALID); + + always @(posedge S_AXI_ACLK) + cover(faulted && M_AXI_ARESETN && S_AXI_RVALID); + + + initial cvr_writes = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_write_fault) + cvr_writes <= 0; + else if (write_faulted && S_AXI_BVALID && S_AXI_BREADY + && S_AXI_BRESP == OKAY + &&(!(&cvr_writes))) + cvr_writes <= cvr_writes + 1; + + always @(*) + cover(cvr_writes > 5); + + initial cvr_reads = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN || o_read_fault) + cvr_reads <= 0; + else if (read_faulted && S_AXI_RVALID && S_AXI_RREADY + && S_AXI_RRESP == OKAY + &&(!(&cvr_reads))) + cvr_reads <= cvr_reads + 1; + + always @(*) + cover(cvr_reads > 5); + // }}} + end + + // }}} + end endgenerate + //////////////////////////////////////////////////////////////////////// + // + // Never data properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + (* anyconst *) reg [C_AXI_DATA_WIDTH-1:0] fc_never_read_data; + (* anyconst *) reg [C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8-1:0] + fc_never_write_data; + + (* anyconst *) reg [C_AXI_ADDR_WIDTH-1:0] fc_never_read_addr, + fc_never_write_addr; + + // Write address checking + // {{{ + always @(*) + if (S_AXI_ARESETN && S_AXI_AWVALID) + assume(S_AXI_AWADDR != fc_never_write_addr); + + always @(*) + if (S_AXI_ARESETN && M_AXI_ARESETN && !o_write_fault && M_AXI_AWVALID) + assert(M_AXI_AWADDR != fc_never_write_addr); + // }}} + + // Write checking + // {{{ + always @(*) + if (S_AXI_ARESETN && S_AXI_WVALID) + assume({ S_AXI_WDATA, S_AXI_WSTRB } != fc_never_write_data); + + always @(*) + if (S_AXI_ARESETN && M_AXI_ARESETN && !o_write_fault && M_AXI_WVALID) + assert({ M_AXI_WDATA, M_AXI_WSTRB } != fc_never_write_data); + // }}} + + // Read address checking + // {{{ + always @(*) + if (S_AXI_ARESETN && S_AXI_ARVALID) + assume(S_AXI_ARADDR != fc_never_read_addr); + + always @(*) + if (S_AXI_ARESETN && M_AXI_ARESETN && !o_read_fault && M_AXI_ARVALID) + assert(M_AXI_ARADDR != fc_never_read_addr); + // }}} + + // Read checking + // {{{ + always @(*) + if (S_AXI_ARESETN && M_AXI_ARESETN && M_AXI_RVALID) + assume(M_AXI_RDATA != fc_never_read_data); + + always @(*) + if (S_AXI_ARESETN && S_AXI_RVALID && S_AXI_RRESP == 2'b00) + assert(S_AXI_RDATA != fc_never_read_data); + // }}} + + // }}} + +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axilsingle.v b/rtl/wb2axip/axilsingle.v new file mode 100644 index 0000000..e92db40 --- /dev/null +++ b/rtl/wb2axip/axilsingle.v @@ -0,0 +1,714 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilsingle.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Create a special AXI-lite slave which can be used to reduce +// crossbar logic for multiple simplified AXI-lite slaves. +// +// To use this, the slave must follow specific (simplified AXI-lite) rules: +// +// Write interface +// 1. The slave must guarantee that AWREADY == WREADY = 1 +// (This core doesn't have AWREADY or WREADY inputs) +// 2. The slave must also guarantee that BVALID == $past(AWVALID) +// (This core internally generates BVALID) +// 3. The controller will guarantee that AWVALID == WVALID +// (You can connect AWVALID to WVALID when connecting to your core) +// 4. The controller will also guarantee that BREADY = 1 +// (This core doesn't have a BVALID input) +// +// Read interface +// 1. The slave must guarantee that ARREADY == 1 +// (This core doesn't have an ARREADY input) +// 2. The slave must also guarantee that RVALID == $past(ARVALID) +// (This core doesn't have an RVALID input, trusting the slave +// instead) +// 3. The controller will guarantee that RREADY = 1 +// (This core doesn't have an RREADY output) +// +// In this simplified controller, the AxADDR lines have been dropped. +// Slaves may only have one address, and that one will be aligned with the +// bus width +// +// +// Why? This simplifies slave logic. Slaves may interact with the bus +// using only the logic below: +// +// always @(posedge S_AXI_ACLK) +// if (AWVALID) +// begin +// if (WSTRB[0]) +// slvreg[ 7: 0] <= WDATA[ 7: 0]; +// if (WSTRB[1]) +// slvreg[15: 8] <= WDATA[15: 8]; +// if (WSTRB[2]) +// slvreg[23:16] <= WDATA[23:16]; +// if (WSTRB[3]) +// slvreg[31:24] <= WDATA[31:24]; +// end +// +// always @(*) +// BRESP = 2'b00; // Or other constant, such as 2'b10 +// +// always @(posedge S_AXI_ACLK) +// if (ARVALID) +// RDATA <= slvreg; +// +// always @(*) +// RRESP = 2'b00; // Or other constant, such as 2'b10 +// +// This core will then keep track of the more complex bus logic, +// simplifying both slaves and connection logic. Slaves with the more +// complicated (and proper/accurate) logic, but with only one bus address, +// and that follow the rules above, should have no problems with this +// additional logic. +// +// Performance: +// +// This core can sustain one read/write per clock as long as the upstream +// AXI-Lite master keeps S_AXI_[BR]READY high. If S_AXI_[BR]READY ever +// drops, there's some flexibility provided by the return FIFO, so the +// master might not notice a drop in throughput until the FIFO fills. +// +// The more practical performance measure is the latency of this core. +// That is measured at four clocks contingent (again) on the master holding +// S_AXI_[BR]READY line high. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// `ifdef VERILATOR +// `define FORMAL +// `endif +// }}} +module axilsingle #( + // {{{ + // NS is the number of slave interfaces + parameter NS = 16, + // + parameter integer C_AXI_DATA_WIDTH = 32, + localparam integer C_AXI_ADDR_WIDTH = $clog2(NS)+$clog2(C_AXI_DATA_WIDTH)-3, + // + // LGFLEN specifies the log (based two) of the number of + // transactions that may need to be held outstanding internally. + // If you really want high throughput, and if you expect any + // back pressure at all, then increase LGFLEN. Otherwise the + // default value of 3 (FIFO size = 8) should be sufficient + // to maintain full loading + parameter LGFLEN=3, + // + // If set, OPT_LOWPOWER will set all unused registers, both + // internal and external, to zero anytime their corresponding + // *VALID bit is clear + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [3-1:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire [2-1:0] S_AXI_BRESP, + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + // + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [3-1:0] S_AXI_ARPROT, + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + // + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [2-1:0] S_AXI_RRESP, + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + // + // + // + output wire [NS-1:0] M_AXI_AWVALID, + output wire [3-1:0] M_AXI_AWPROT, + // + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + // + input wire [NS*2-1:0] M_AXI_BRESP, + // + output wire [NS-1:0] M_AXI_ARVALID, + output wire [3-1:0] M_AXI_ARPROT, + // + input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [NS*2-1:0] M_AXI_RRESP + // }}} + ); + + // + // AW, and DW, are short-hand abbreviations used locally. + localparam AW = C_AXI_ADDR_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + localparam LGNS = $clog2(NS); + // + localparam INTERCONNECT_ERROR = 2'b11; + localparam ADDR_LSBS = $clog2(DW)-3; + // + + //////////////////////////////////////////////////////////////////////// + // + // Write logic: + // + //////////////////////////////////////////////////////////////////////// + // + // + wire awskid_valid, bffull, bempty, write_awskidready; + reg write_bvalid, write_response; + reg bfull, write_wready; + wire [AW-ADDR_LSBS-1:0] awskid_addr; + reg [AW-ADDR_LSBS-1:0] write_windex, write_bindex; + wire [3-1:0] awskid_prot; + reg [3-1:0] m_axi_awprot; + wire [LGFLEN:0] bfill; + reg [LGFLEN:0] write_count; + reg [1:0] write_resp; + reg [NS-1:0] m_axi_awvalid; + + skidbuffer #(.OPT_LOWPOWER(OPT_LOWPOWER), .OPT_OUTREG(0), + .DW((AW-ADDR_LSBS)+3)) + awskid( .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), + .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWPROT, S_AXI_AWADDR[AW-1:ADDR_LSBS] }), + .o_valid(awskid_valid), .i_ready(write_awskidready), + .o_data({ awskid_prot, awskid_addr })); + + initial write_wready = 0; + initial m_axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { write_wready, m_axi_awvalid } <= 0; + else if (awskid_valid && write_awskidready) + begin + m_axi_awvalid <= 0; + m_axi_awvalid <= (1<<awskid_addr); + // if (awskid_addr >= NS) + // m_axi_awvalid[NS] <= 1'b1; + + write_wready <= 1; + end else if (S_AXI_WVALID) + { write_wready, m_axi_awvalid } <= 0; + + assign S_AXI_WREADY = write_wready; + + always @(posedge S_AXI_ACLK) + if (awskid_valid && write_awskidready) + begin + m_axi_awprot <= awskid_prot; + write_windex <= awskid_addr; + end + + assign M_AXI_AWVALID = (S_AXI_WVALID) ? m_axi_awvalid : {(NS){1'b0}}; + assign M_AXI_AWPROT = m_axi_awprot; + assign S_AXI_WREADY = write_wready; + assign M_AXI_WDATA = S_AXI_WDATA; + assign M_AXI_WSTRB = S_AXI_WSTRB; + + assign write_awskidready = (!write_wready || S_AXI_WVALID) && !bfull; + + initial { write_response, write_bvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { write_response, write_bvalid } <= 0; + else + { write_response, write_bvalid } + <= { write_bvalid, (S_AXI_WVALID && S_AXI_WREADY) }; + + always @(posedge S_AXI_ACLK) + write_bindex <= write_windex; + + generate if (LGNS == $clog2(NS+1)) + begin : GEN_WRITE_ERR + always @(posedge S_AXI_ACLK) + if (write_bindex >= NS) + write_resp <= INTERCONNECT_ERROR; + else + write_resp <= M_AXI_BRESP[2*write_bindex +: 2]; + end else begin : NO_WRITE_ERR + always @(posedge S_AXI_ACLK) + write_resp <= M_AXI_BRESP[2*write_bindex +: 2]; + end endgenerate + + initial write_count = 0; + initial bfull = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + write_count <= 0; + bfull <= 0; + end else case({ (awskid_valid && write_awskidready), + (S_AXI_BVALID & S_AXI_BREADY) }) + 2'b01: begin + write_count <= write_count - 1; + bfull <= 1'b0; + end + 2'b10: begin + write_count <= write_count + 1; + bfull <= (&write_count[LGFLEN-1:0]); + end + default: begin end + endcase + +`ifdef FORMAL + always @(*) + assert(write_count <= { 1'b1, {(LGFLEN){1'b0}} }); + always @(*) + assert(bfull == (write_count == { 1'b1, {(LGFLEN){1'b0}} })); +`endif + + sfifo #(.BW(2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) + bfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(write_response), .i_data(write_resp), .o_full(bffull), + .o_fill(bfill), + .i_rd(S_AXI_BVALID && S_AXI_BREADY), .o_data(S_AXI_BRESP), + .o_empty(bempty)); + +`ifdef FORMAL + always @(*) + assert(write_count == bfill + + (write_response ? 1:0) + + (write_bvalid ? 1:0) + + (write_wready ? 1:0)); + +`ifdef VERIFIC + always @(*) + if (bfifo.f_first_in_fifo) + assert(bfifo.f_first_data != 2'b01); + always @(*) + if (bfifo.f_second_in_fifo) + assert(bfifo.f_second_data != 2'b01); + always @(*) + if (!bempty && (bfifo.rd_addr != bfifo.f_first_addr) + &&(bfifo.rd_addr != bfifo.f_second_addr)) + assume(S_AXI_BRESP != 2'b01); +`else + always @(*) + if (!bempty) + assume(S_AXI_BRESP != 2'b01); +`endif +`endif + + assign S_AXI_BVALID = !bempty; + +`ifdef FORMAL + always @(*) + assert(!bffull || !write_bvalid); +`endif + + //////////////////////////////////////////////////////////////////////// + // + // Read logic + // + //////////////////////////////////////////////////////////////////////// + // + // + wire rempty, rdfull; + wire [LGFLEN:0] rfill; + reg [AW-ADDR_LSBS-1:0] read_index, last_read_index; + reg [1:0] read_resp; + reg [DW-1:0] read_rdata; + reg read_rwait, read_rvalid, read_result; + reg [NS-1:0] m_axi_arvalid; + reg [3-1:0] m_axi_arprot; + + initial { m_axi_arvalid, read_rwait } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { m_axi_arvalid, read_rwait } <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + begin + m_axi_arvalid <= (1 << S_AXI_ARADDR[AW-1:ADDR_LSBS]); + read_rwait <= 1'b1; + end else + { m_axi_arvalid, read_rwait } <= 0; + + always @(posedge S_AXI_ACLK) + begin + m_axi_arprot <= S_AXI_ARPROT; + read_index <= S_AXI_ARADDR[AW-1:ADDR_LSBS]; + end + + assign M_AXI_ARVALID = m_axi_arvalid; + assign M_AXI_ARPROT = m_axi_arprot; + + initial { read_result, read_rvalid } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_result, read_rvalid } <= 2'b00; + else + { read_result, read_rvalid } <= { read_rvalid, read_rwait }; + + always @(posedge S_AXI_ACLK) + last_read_index <= read_index; + + always @(posedge S_AXI_ACLK) + read_rdata <= M_AXI_RDATA[DW*last_read_index +: DW]; + + generate if (LGNS == $clog2(NS+1)) + begin : GEN_READ_ERR + always @(posedge S_AXI_ACLK) + if (last_read_index >= NS) + read_resp <= INTERCONNECT_ERROR; + else + read_resp <= M_AXI_RRESP[2*last_read_index +: 2]; + end else begin : NO_READ_ERR + always @(posedge S_AXI_ACLK) + read_resp <= M_AXI_RRESP[2*last_read_index +: 2]; + end endgenerate + + reg read_full; + reg [LGFLEN:0] read_count; + + initial { read_count, read_full } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + { read_count, read_full } <= 0; + else case({ S_AXI_ARVALID & S_AXI_ARREADY, S_AXI_RVALID & S_AXI_RREADY}) + 2'b10: begin + read_count <= read_count + 1; + read_full <= &read_count[LGFLEN-1:0]; + end + 2'b01: begin + read_count <= read_count - 1; + read_full <= 1'b0; + end + default: begin end + endcase + +`ifdef FORMAL + always @(*) + assert(read_count <= { 1'b1, {(LGFLEN){1'b0}} }); + always @(*) + assert(read_full == (read_count == { 1'b1, {(LGFLEN){1'b0}} })); +`endif + assign S_AXI_ARREADY = !read_full; + + sfifo #(.BW(DW+2), .OPT_ASYNC_READ(0), .LGFLEN(LGFLEN)) + rfifo ( .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(read_result), .i_data({ read_rdata, read_resp }), + .o_full(rdfull), .o_fill(rfill), + .i_rd(S_AXI_RVALID && S_AXI_RREADY), + .o_data({ S_AXI_RDATA, S_AXI_RRESP }),.o_empty(rempty)); + +`ifdef FORMAL + always @(*) + assert(read_count == rfill + read_result + read_rvalid + read_rwait); +`ifdef VERIFIC + always @(*) + if (rfifo.f_first_in_fifo) + assert(rfifo.f_first_data[1:0] != 2'b01); + always @(*) + if (rfifo.f_second_in_fifo) + assert(rfifo.f_second_data[1:0] != 2'b01); + always @(*) + if (!rempty && (rfifo.rd_addr != rfifo.f_first_addr) + &&(rfifo.rd_addr != rfifo.f_second_addr)) + assume(S_AXI_RRESP != 2'b01); +`else + always @(*) + if (!rempty) + assume(S_AXI_RRESP != 2'b01); +`endif +`endif + + assign S_AXI_RVALID = !rempty; + + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, + S_AXI_AWADDR[ADDR_LSBS-1:0], + S_AXI_ARADDR[ADDR_LSBS-1:0], + bffull, rdfull, bfill, rfill }; + // verilator lint_on UNUSED +`ifdef FORMAL + localparam F_LGDEPTH = LGFLEN+1; + reg f_past_valid; + reg [F_LGDEPTH-1:0] count_awr_outstanding, count_wr_outstanding, + count_rd_outstanding; + + + wire [(F_LGDEPTH-1):0] f_axi_awr_outstanding, + f_axi_wr_outstanding, + f_axi_rd_outstanding; + + wire [1:0] fm_axi_awr_outstanding [0:NS-1]; + wire [1:0] fm_axi_wr_outstanding [0:NS-1]; + wire [1:0] fm_axi_rd_outstanding [0:NS-1]; + + reg [NS-1:0] m_axi_rvalid, m_axi_bvalid; + + faxil_slave #(// .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + // .F_OPT_NO_READS(1'b0), + // .F_OPT_NO_WRITES(1'b0), + .F_OPT_XILINX(1), + .F_LGDEPTH(F_LGDEPTH)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr(S_AXI_AWADDR), + .i_axi_awprot(S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp(S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arprot(S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + // + .f_axi_rd_outstanding(f_axi_rd_outstanding), + .f_axi_wr_outstanding(f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding)); + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + genvar M; + generate for(M=0; M<NS; M=M+1) + begin : CONSTRAIN_SLAVE_INTERACTIONS + + faxil_master #(// .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(1), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // .F_OPT_NO_READS(1'b0), + // .F_OPT_NO_WRITES(1'b0), + .F_OPT_NO_RESET(1'b1), + .F_LGDEPTH(2)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID[M]), + .i_axi_awready(1'b1), + .i_axi_awaddr(1'b0), + .i_axi_awprot(M_AXI_AWPROT), + // + .i_axi_wvalid(M_AXI_AWVALID[M]), + .i_axi_wready(1'b1), + .i_axi_wdata(M_AXI_WDATA[C_AXI_DATA_WIDTH-1:0]), + .i_axi_wstrb(M_AXI_WSTRB[C_AXI_DATA_WIDTH/8-1:0]), + // + .i_axi_bvalid(m_axi_bvalid[M]), + .i_axi_bready(1'b1), + .i_axi_bresp(M_AXI_BRESP[2*M +: 2]), + // + .i_axi_arvalid(M_AXI_ARVALID[M]), + .i_axi_arready(1'b1), + .i_axi_araddr(1'b0), + .i_axi_arprot(M_AXI_ARPROT), + // + .i_axi_rvalid(m_axi_rvalid[M]), + .i_axi_rready(1'b1), + .i_axi_rdata(M_AXI_RDATA[M*C_AXI_DATA_WIDTH +: C_AXI_DATA_WIDTH]), + .i_axi_rresp(M_AXI_RRESP[2*M +: 2]), + // + .f_axi_rd_outstanding(fm_axi_rd_outstanding[M]), + .f_axi_wr_outstanding(fm_axi_wr_outstanding[M]), + .f_axi_awr_outstanding(fm_axi_awr_outstanding[M])); + + initial m_axi_bvalid <= 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_bvalid[M] <= 1'b0; + else + m_axi_bvalid[M] <= M_AXI_AWVALID[M]; + + initial m_axi_rvalid[M] <= 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_axi_rvalid[M] <= 1'b0; + else + m_axi_rvalid[M] <= M_AXI_ARVALID[M]; + + always @(*) + assert(fm_axi_awr_outstanding[M] == fm_axi_wr_outstanding[M]); + + always @(*) + assert(fm_axi_wr_outstanding[M]== (m_axi_bvalid[M] ? 1:0)); + + always @(*) + assert(fm_axi_rd_outstanding[M]== (m_axi_rvalid[M] ? 1:0)); + end endgenerate + + //////////////////////////////////////////////////////////////////////// + // + // Properties necessary to pass induction + // + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + assert(S_AXI_WREADY == (m_axi_awvalid != 0)); +`ifdef VERIFIC + always @(*) + assert($onehot0(M_AXI_AWVALID)); + + always @(*) + assert($onehot0(m_axi_bvalid)); + + always @(*) + assert($onehot0(m_axi_rvalid)); +`endif + always @(*) + begin + count_awr_outstanding = 0; + if (!S_AXI_AWREADY) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_wready) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_bvalid) + count_awr_outstanding = count_awr_outstanding + 1; + if (write_response) + count_awr_outstanding = count_awr_outstanding + 1; + count_awr_outstanding = count_awr_outstanding + bfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_awr_outstanding == count_awr_outstanding); + + always @(*) + begin + count_wr_outstanding = 0; + if (write_bvalid) + count_wr_outstanding = count_wr_outstanding + 1; + if (write_response) + count_wr_outstanding = count_wr_outstanding + 1; + count_wr_outstanding = count_wr_outstanding + bfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_wr_outstanding == count_wr_outstanding); + + // + // + // + always @(*) + begin + count_rd_outstanding = 0; + if (read_rwait) + count_rd_outstanding = count_rd_outstanding + 1; + if (read_rvalid) + count_rd_outstanding = count_rd_outstanding + 1; + if (read_result) + count_rd_outstanding = count_rd_outstanding + 1; + count_rd_outstanding = count_rd_outstanding + rfill; + end + + always @(*) + if (S_AXI_ARESETN) + assert(f_axi_rd_outstanding == count_rd_outstanding); + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + //////////////////////////////////////////////////////////////////////// + // + // + reg [3:0] cvr_arvalids, cvr_awvalids, cvr_reads, cvr_writes; + + initial cvr_awvalids = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_awvalids <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && !(&cvr_awvalids)) + cvr_awvalids <= cvr_awvalids + 1; + + initial cvr_arvalids = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_arvalids <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && !(&cvr_arvalids)) + cvr_arvalids <= cvr_arvalids + 1; + + initial cvr_writes = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_writes <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY && !(&cvr_writes)) + cvr_writes <= cvr_writes + 1; + + initial cvr_reads = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_reads <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && !(&cvr_arvalids)) + cvr_reads <= cvr_reads + 1; + + always @(*) + cover(cvr_awvalids > 4); + + always @(*) + cover(cvr_arvalids > 4); + + always @(*) + cover(cvr_reads > 4); + + always @(*) + cover(cvr_writes > 4); + + always @(*) + cover((cvr_writes > 4) && (cvr_reads > 4)); +`endif +endmodule +// `ifndef YOSYS +// `default_nettype wire +// `endif diff --git a/rtl/wb2axip/axilupsz.v b/rtl/wb2axip/axilupsz.v new file mode 100644 index 0000000..29dc41d --- /dev/null +++ b/rtl/wb2axip/axilupsz.v @@ -0,0 +1,603 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilupsz.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Converts AXI4-lite traffic of one data width to a similar +// AXI4-lite interface with a larger data path. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2021-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilupsz #( + // {{{ + parameter C_S_AXIL_DATA_WIDTH = 32, + parameter C_M_AXIL_DATA_WIDTH = 64, + parameter C_AXIL_ADDR_WIDTH = 32, + parameter LGFIFO = 5, + parameter [0:0] OPT_LOWPOWER = 1, + localparam SDW = C_S_AXIL_DATA_WIDTH, + localparam MDW = C_M_AXIL_DATA_WIDTH, + localparam AW = C_AXIL_ADDR_WIDTH + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + S_AXI_ARESETN, + // The slave interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [AW-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [SDW-1:0] S_AXIL_WDATA, + input wire [SDW/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [AW-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [SDW-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // The master interface + // {{{ + output wire M_AXIL_AWVALID, + input wire M_AXIL_AWREADY, + output wire [AW-1:0] M_AXIL_AWADDR, + output wire [2:0] M_AXIL_AWPROT, + // + output wire M_AXIL_WVALID, + input wire M_AXIL_WREADY, + output wire [MDW-1:0] M_AXIL_WDATA, + output wire [MDW/8-1:0] M_AXIL_WSTRB, + // + input wire M_AXIL_BVALID, + output wire M_AXIL_BREADY, + input wire [1:0] M_AXIL_BRESP, + // + output wire M_AXIL_ARVALID, + input wire M_AXIL_ARREADY, + output wire [AW-1:0] M_AXIL_ARADDR, + output wire [2:0] M_AXIL_ARPROT, + // + input wire M_AXIL_RVALID, + output wire M_AXIL_RREADY, + input wire [MDW-1:0] M_AXIL_RDATA, + input wire [1:0] M_AXIL_RRESP + // }}} + // }}} + ); + + localparam MLSB = $clog2(C_M_AXIL_DATA_WIDTH/8); + localparam SLSB = $clog2(C_S_AXIL_DATA_WIDTH/8); + localparam RPTS = MDW/SDW; + + generate if (SDW == MDW) + begin : NO_CHANGE + // {{{ + assign M_AXIL_AWVALID = S_AXIL_AWVALID; + assign S_AXIL_AWREADY = M_AXIL_AWREADY; + assign M_AXIL_AWADDR = S_AXIL_AWADDR; + assign M_AXIL_AWPROT = S_AXIL_AWPROT; + + assign M_AXIL_WVALID = S_AXIL_WVALID; + assign S_AXIL_WREADY = M_AXIL_WREADY; + assign M_AXIL_WDATA = S_AXIL_WDATA; + assign M_AXIL_WSTRB = S_AXIL_WSTRB; + + assign S_AXIL_BVALID = M_AXIL_BVALID; + assign M_AXIL_BREADY = S_AXIL_BREADY; + assign S_AXIL_BRESP = M_AXIL_BRESP; + + assign M_AXIL_ARVALID = S_AXIL_ARVALID; + assign S_AXIL_ARREADY = M_AXIL_ARREADY; + assign M_AXIL_ARADDR = S_AXIL_ARADDR; + assign M_AXIL_ARPROT = S_AXIL_ARPROT; + + assign S_AXIL_RVALID = M_AXIL_RVALID; + assign M_AXIL_RREADY = S_AXIL_RREADY; + assign S_AXIL_RDATA = M_AXIL_RDATA; + assign S_AXIL_RRESP = M_AXIL_RRESP; + // }}} + end else begin : UPSIZE_BUS_DATA_WIDTH + // {{{ + // Signal declarations + // {{{ + wire awskd_valid, wskd_valid, wskd_ready; + wire [AW-1:0] awskd_addr; + wire [2:0] awskd_prot; + + wire [SDW-1:0] wskd_data; + wire [SDW/8-1:0] wskd_strb; + + reg awvalid, wvalid; + reg [AW-1:0] awaddr; + reg [2:0] awprot; + reg [MDW-1:0] wdata; + reg [MDW/8-1:0] wstrb; + + reg rvalid; + reg [SDW-1:0] rdata; + reg [1:0] rresp; + wire rfifo_full, rfifo_empty; + wire [LGFIFO:0] rfifo_fill; + wire [MLSB-SLSB-1:0] rfifo_data; + wire [MDW-1:0] shift_rdata; + // }}} + //////////////////////////////////////////////////////////////// + // + // Write channel(s) + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // AW* skid bufer + // {{{ + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .DW(AW+3) + // }}} + ) awskd ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data({ S_AXIL_AWADDR, S_AXIL_AWPROT }), + .o_valid(awskd_valid), .i_ready(wskd_ready), + .o_data({ awskd_addr, awskd_prot }) + // }}} + ); + // }}} + + skidbuffer #( + // {{{ + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0), + .DW(SDW+SDW/8) + // }}} + ) wskd ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(wskd_ready), + .o_data({ wskd_data, wskd_strb }) + // }}} + ); + + // wskd_ready + // {{{ + // We *need* to synchronize the two channels here. We need the + // address informatiuon to know how to set the W* channel + // downstream + assign wskd_ready = (awskd_valid && wskd_valid) + && (!M_AXIL_AWVALID || M_AXIL_AWREADY) + && (!M_AXIL_WVALID || M_AXIL_WREADY); + // }}} + + // awvalid + // {{{ + initial awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + awvalid <= 0; + else if (!M_AXIL_AWVALID || M_AXIL_AWREADY) + awvalid <= wskd_ready; + // }}} + + // awaddr, awprot + // {{{ + initial { awaddr, awprot } = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + { awaddr, awprot } <= 0; + else if (!M_AXIL_AWVALID || M_AXIL_AWREADY) + begin + { awaddr, awprot } <= 0; + if (awskd_valid || !OPT_LOWPOWER) + {awaddr, awprot } <= { awskd_addr, awskd_prot }; + end + // }}} + + // wvalid + // {{{ + initial wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + wvalid <= 0; + else if (!M_AXIL_WVALID || M_AXIL_WREADY) + wvalid <= wskd_ready; + // }}} + + // wdata, wstrb + // {{{ + initial { wdata, wstrb } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + { wdata, wstrb } <= 0; + else if (!M_AXIL_WVALID || M_AXIL_WREADY) + begin + // Default values + wstrb <= 0; + wdata <= {(RPTS){ wskd_data }}; + + // Verilator lint_off WIDTH + if (OPT_LOWPOWER) + begin + wdata <= 0; + wdata <= (wskd_data) + << (awskd_addr[MLSB-1:SLSB] * SDW); + end + + if (!OPT_LOWPOWER || wskd_valid) + wstrb <= (wskd_strb) + << (awskd_addr[MLSB-1:SLSB] * SDW); + // Verilator lint_on WIDTH + end + // }}} + // }}} + //////////////////////////////////////////////////////////////// + // + // Read channel + // {{{ + //////////////////////////////////////////////////////////////// + // + // + wire rskd_valid, rskd_ready; + wire [MDW-1:0] rskd_data; + wire [1:0] rskd_resp; + + // Read LSB address FIFO + // {{{ + sfifo #( + // {{{ + .BW(MLSB-SLSB), + .LGFLEN(LGFIFO) + // }}} + ) rfifo ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_wr(S_AXIL_ARVALID && S_AXIL_ARREADY), + .i_data(M_AXIL_ARADDR[MLSB-1:SLSB]), + .o_full(rfifo_full), .o_fill(rfifo_fill), + .i_rd(M_AXIL_RVALID && M_AXIL_RREADY), + .o_data(rfifo_data), + .o_empty(rfifo_empty) + // }}} + ); + // }}} + + // Read return skid buffer + // {{{ + skidbuffer #( + // {{{ + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(0), + .DW(MDW+2) + // }}} + ) rskd ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(M_AXIL_RVALID), .o_ready(M_AXIL_RREADY), + .i_data({ M_AXIL_RDATA, M_AXIL_RRESP }), + .o_valid(rskd_valid), .i_ready(rskd_ready), + .o_data({ rskd_data, rskd_resp }) + // }}} + ); + + assign rskd_ready = !S_AXIL_RVALID || S_AXIL_RREADY; + // }}} + + assign shift_rdata = rskd_data + >> ({ {(MDW-(MLSB-SLSB)){1'b0}}, rfifo_data} * SDW); + + + // rvalid + // {{{ + initial rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rvalid <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + rvalid <= rskd_valid; + // }}} + + // rresp + // {{{ + initial rresp = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + rresp <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + begin + rresp <= rskd_resp; + if (OPT_LOWPOWER && !rskd_valid) + rresp <= 0; + end + // }}} + + // rdata + // {{{ + initial rdata = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + rdata <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + begin + rdata <= shift_rdata[SDW-1:0]; + + if (OPT_LOWPOWER && !rskd_valid) + rdata <= 0; + end +`ifdef FORMAL + // Low power check + // {{{ + always @(*) + if (S_AXI_ARESETN && OPT_LOWPOWER && !S_AXIL_RVALID) + begin + assert(rdata == 0); + assert(rresp == 0); + end + // }}} +`endif + // }}} + + // }}} + + // M_* values, S_B* + // {{{ + assign M_AXIL_AWVALID = awvalid; + assign M_AXIL_AWADDR = awaddr; + assign M_AXIL_AWPROT = awprot; + assign M_AXIL_WVALID = wvalid; + assign M_AXIL_WDATA = wdata; + assign M_AXIL_WSTRB = wstrb; + + assign M_AXIL_ARVALID = S_AXIL_ARVALID && !rfifo_full; + assign S_AXIL_ARREADY = M_AXIL_ARREADY && !rfifo_full; + assign M_AXIL_ARADDR = S_AXIL_ARADDR; + assign M_AXIL_ARPROT = S_AXIL_ARPROT; + + assign S_AXIL_RVALID = rvalid; + assign S_AXIL_RDATA = rdata; + assign S_AXIL_RRESP = rresp; + + assign S_AXIL_BVALID = M_AXIL_BVALID; + assign M_AXIL_BREADY = S_AXIL_BREADY; + assign S_AXIL_BRESP = M_AXIL_BRESP; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, shift_rdata[MDW-1:SDW], + rfifo_empty, rfifo_fill }; + // Verilator lint_on UNUSED + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // Formal properties + // {{{ + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = LGFIFO+2; + wire [F_LGDEPTH-1:0] fslv_rd_outstanding, + fmst_rd_outstanding, + fslv_wr_outstanding, + fmst_wr_outstanding, + fslv_awr_outstanding, + fmst_awr_outstanding; + //////////////////////////////////////////////////////////////// + // + // Interface properties + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(SDW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_COVER_BURST(4), + .F_LGDEPTH(F_LGDEPTH), + .F_AXI_MAXWAIT(8), + .F_AXI_MAXRSTALL(3), + .F_AXI_MAXDELAY(16) + // }}} + ) axil_slave ( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + .f_axi_rd_outstanding(fslv_rd_outstanding), + .f_axi_wr_outstanding(fslv_wr_outstanding), + .f_axi_awr_outstanding(fslv_awr_outstanding) + // }}} + ); + + faxil_master #( + // {{{ + .C_AXI_DATA_WIDTH(MDW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_COVER_BURST(1), + .F_LGDEPTH(F_LGDEPTH), + .F_OPT_NO_RESET(1), + .F_AXI_MAXWAIT(5), + .F_AXI_MAXRSTALL(3), + .F_AXI_MAXDELAY(5) + // }}} + ) axil_master ( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXIL_AWVALID), + .i_axi_awready(M_AXIL_AWREADY), + .i_axi_awaddr( M_AXIL_AWADDR), + .i_axi_awprot( M_AXIL_AWPROT), + // + .i_axi_wvalid(M_AXIL_WVALID), + .i_axi_wready(M_AXIL_WREADY), + .i_axi_wdata( M_AXIL_WDATA), + .i_axi_wstrb( M_AXIL_WSTRB), + // + .i_axi_bvalid(M_AXIL_BVALID), + .i_axi_bready(M_AXIL_BREADY), + .i_axi_bresp( M_AXIL_BRESP), + // + .i_axi_arvalid(M_AXIL_ARVALID), + .i_axi_arready(M_AXIL_ARREADY), + .i_axi_araddr( M_AXIL_ARADDR), + .i_axi_arprot( M_AXIL_ARPROT), + // + .i_axi_rvalid(M_AXIL_RVALID), + .i_axi_rready(M_AXIL_RREADY), + .i_axi_rdata( M_AXIL_RDATA), + .i_axi_rresp( M_AXIL_RRESP), + // + .f_axi_rd_outstanding(fmst_rd_outstanding), + .f_axi_wr_outstanding(fmst_wr_outstanding), + .f_axi_awr_outstanding(fmst_awr_outstanding) + // }}} + ); + + // Correlate slave and master write outstanding counters + // {{{ + always @(*) + begin + assume(fslv_awr_outstanding <= (1<<LGFIFO)); + assume(fslv_wr_outstanding <= (1<<LGFIFO)); + + assert(fslv_awr_outstanding + (S_AXIL_WREADY ? 0:1) + == fslv_wr_outstanding +(S_AXIL_AWREADY ? 0:1)); + + assert(fmst_awr_outstanding + (M_AXIL_AWVALID ? 1:0) + == fmst_wr_outstanding + (M_AXIL_WVALID ? 1:0)); + + assert(fslv_awr_outstanding == fmst_awr_outstanding + +(M_AXIL_AWVALID ? 1:0)+(S_AXIL_AWREADY ? 0:1)); + assert(fslv_wr_outstanding == fmst_wr_outstanding + +(M_AXIL_WVALID ? 1:0) + (S_AXIL_WREADY ? 0:1)); + end + // }}} + + // Correlate the slave and master read outstanding counters + // w/ FIFO fill + // {{{ + always @(*) + begin + assert(fslv_rd_outstanding == fmst_rd_outstanding + +(S_AXIL_RVALID ? 1:0) + (M_AXIL_RREADY ? 0:1)); + assert(rfifo_fill == fmst_rd_outstanding); + + if (M_AXIL_RVALID) + assert(!rfifo_empty); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////// + // + // Contract checks + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // Not implemented yet + + // }}} + //////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // (Currently) captured by the interface properties + // }}} + //////////////////////////////////////////////////////////////// + // + // "Careless" assumptions + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // None + + // }}} +`endif + // }}} + end endgenerate + + // Parameter checking + // {{{ + initial if (SDW > MDW) $stop; + // }}} +endmodule diff --git a/rtl/wb2axip/axilwr2wbsp.v b/rtl/wb2axip/axilwr2wbsp.v new file mode 100644 index 0000000..9b75c0e --- /dev/null +++ b/rtl/wb2axip/axilwr2wbsp.v @@ -0,0 +1,675 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilwr2wbsp.v (AXI lite to wishbone slave, read channel) +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Bridge an AXI lite write channel triplet to a single wishbone +// write channel. A full AXI lite to wishbone bridge will also +// require the read channel and an arbiter. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2016-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axilwr2wbsp #( + // {{{ + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ADDR_WIDTH = 28, + localparam AXI_LSBS = $clog2(C_AXI_DATA_WIDTH)-3, + localparam AW = C_AXI_ADDR_WIDTH-AXI_LSBS, + parameter LGFIFO = 3, + localparam DW = C_AXI_DATA_WIDTH + // }}} + ) ( + // {{{ + input wire i_clk, + input wire i_axi_reset_n, + // AXI write address channel signals + // {{{ + input wire i_axi_awvalid, + output reg o_axi_awready, + input wire [AW+1:0] i_axi_awaddr, + input wire [2:0] i_axi_awprot, + // }}} + // AXI write data channel signals + // {{{ + input wire i_axi_wvalid, + output reg o_axi_wready, + input wire [DW-1:0] i_axi_wdata, + input wire [DW/8-1:0] i_axi_wstrb, + // }}} + // AXI write response channel signals + // {{{ + output reg o_axi_bvalid, + input wire i_axi_bready, + output reg [1:0] o_axi_bresp, + // }}} + // Wishbone signals + // {{{ + // We'll share the clock and the reset + output reg o_wb_cyc, + output reg o_wb_stb, + output reg [(AW-1):0] o_wb_addr, + output reg [(DW-1):0] o_wb_data, + output reg [(DW/8-1):0] o_wb_sel, + input wire i_wb_ack, + input wire i_wb_stall, + input wire i_wb_err + // }}} +`ifdef FORMAL + // {{{ + // Output connections only used in formal mode + , output wire [LGFIFO:0] f_first, + output wire [LGFIFO:0] f_mid, + output wire [LGFIFO:0] f_last, + output wire [1:0] f_wpending + // }}} +`endif + // }}} + ); + + // Local declarations + // {{{ + wire w_reset; + assign w_reset = (!i_axi_reset_n); + + reg r_awvalid, r_wvalid; + reg [AW-1:0] r_addr; + reg [DW-1:0] r_data; + reg [DW/8-1:0] r_sel; + + reg fifo_full, fifo_empty; + + reg [LGFIFO:0] r_first, r_mid, r_last; + wire [LGFIFO:0] next_first, next_last; + reg wb_pending; + reg [LGFIFO:0] wb_outstanding; + + reg [LGFIFO:0] err_loc; + reg err_state; + + wire axi_write_accepted, pending_axi_write; + // }}} + + assign pending_axi_write = + ((r_awvalid) || (i_axi_awvalid && o_axi_awready)) + &&((r_wvalid)|| (i_axi_wvalid && o_axi_wready)); + + assign axi_write_accepted = + (!o_wb_stb || !i_wb_stall) && (!fifo_full) && (!err_state) + && (pending_axi_write); + + // o_wb_cyc, o_wb_stb + // {{{ + initial o_wb_cyc = 1'b0; + initial o_wb_stb = 1'b0; + always @(posedge i_clk) + if ((w_reset)||((o_wb_cyc)&&(i_wb_err))||(err_state)) + o_wb_stb <= 1'b0; + else if (axi_write_accepted) + o_wb_stb <= 1'b1; + else if ((o_wb_cyc)&&(!i_wb_stall)) + o_wb_stb <= 1'b0; + + always @(*) + o_wb_cyc = (wb_pending)||(o_wb_stb); + // }}} + + // o_wb_addr, o_wb_data, o_wb_sel + // {{{ + always @(posedge i_clk) + if (!o_wb_stb || !i_wb_stall) + begin + if (r_awvalid) + o_wb_addr <= r_addr; + else + o_wb_addr <= i_axi_awaddr[AW+1:AXI_LSBS]; + + if (r_wvalid) + begin + o_wb_data <= r_data; + o_wb_sel <= r_sel; + end else begin + o_wb_data <= i_axi_wdata; + o_wb_sel <= i_axi_wstrb; + end + end + // }}} + + // r_awvalid, r_addr + // {{{ + initial r_awvalid = 1'b0; + always @(posedge i_clk) + begin + if ((i_axi_awvalid)&&(o_axi_awready)) + begin + r_addr <= i_axi_awaddr[AW+1:AXI_LSBS]; + r_awvalid <= (!axi_write_accepted); + end else if (axi_write_accepted) + r_awvalid <= 1'b0; + + if (w_reset) + r_awvalid <= 1'b0; + end + // }}} + + // r_wvalid + // {{{ + initial r_wvalid = 1'b0; + always @(posedge i_clk) + begin + if ((i_axi_wvalid)&&(o_axi_wready)) + begin + r_data <= i_axi_wdata; + r_sel <= i_axi_wstrb; + r_wvalid <= (!axi_write_accepted); + end else if (axi_write_accepted) + r_wvalid <= 1'b0; + + if (w_reset) + r_wvalid <= 1'b0; + end + // }}} + + // o_axi_awready + // {{{ + initial o_axi_awready = 1'b1; + always @(posedge i_clk) + if (w_reset) + o_axi_awready <= 1'b1; + else if ((o_wb_stb && i_wb_stall) + &&(r_awvalid || (i_axi_awvalid && o_axi_awready))) + // Once a request has been received while the interface is + // stalled, we must stall and wait for it to clear + o_axi_awready <= 1'b0; + else if (err_state && (r_awvalid || (i_axi_awvalid && o_axi_awready))) + o_axi_awready <= 1'b0; + else if ((r_awvalid || (i_axi_awvalid && o_axi_awready)) + &&(!r_wvalid && (!i_axi_wvalid || !o_axi_wready))) + // If the write address is given without any corresponding + // write data, immediately stall and wait for the write data + o_axi_awready <= 1'b0; + else if (!o_axi_awready && o_wb_stb && i_wb_stall) + // Once stalled, remain stalled while the WB bus is stalled + o_axi_awready <= 1'b0; + else if (fifo_full && (r_awvalid || (!o_axi_bvalid || !i_axi_bready))) + // Once the FIFO is full, we must remain stalled until at + // least one acknowledgment has been accepted + o_axi_awready <= 1'b0; + else if ((!o_axi_bvalid || !i_axi_bready) + && (r_awvalid || (i_axi_awvalid && o_axi_awready))) + // If ever the FIFO becomes full, we must immediately drop + // the o_axi_awready signal + o_axi_awready <= (next_first[LGFIFO-1:0] != r_last[LGFIFO-1:0]) + &&(next_first[LGFIFO]==r_last[LGFIFO]); + else + o_axi_awready <= 1'b1; + // }}} + + // o_axi_wready + // {{{ + initial o_axi_wready = 1'b1; + always @(posedge i_clk) + if (w_reset) + o_axi_wready <= 1'b1; + else if ((o_wb_stb && i_wb_stall) + &&(r_wvalid || (i_axi_wvalid && o_axi_wready))) + // Once a request has been received while the interface is + // stalled, we must stall and wait for it to clear + o_axi_wready <= 1'b0; + else if (err_state && (r_wvalid || (i_axi_wvalid && o_axi_wready))) + o_axi_wready <= 1'b0; + else if ((r_wvalid || (i_axi_wvalid && o_axi_wready)) + &&(!r_awvalid && (!i_axi_awvalid || !o_axi_awready))) + // If the write address is given without any corresponding + // write data, immediately stall and wait for the write data + o_axi_wready <= 1'b0; + else if (!o_axi_wready && o_wb_stb && i_wb_stall) + // Once stalled, remain stalled while the WB bus is stalled + o_axi_wready <= 1'b0; + else if (fifo_full && (r_wvalid || (!o_axi_bvalid || !i_axi_bready))) + // Once the FIFO is full, we must remain stalled until at + // least one acknowledgment has been accepted + o_axi_wready <= 1'b0; + else if ((!o_axi_bvalid || !i_axi_bready) + && (i_axi_wvalid && o_axi_wready)) + // If ever the FIFO becomes full, we must immediately drop + // the o_axi_wready signal + o_axi_wready <= (next_first[LGFIFO-1:0] != r_last[LGFIFO-1:0]) + &&(next_first[LGFIFO]==r_last[LGFIFO]); + else + o_axi_wready <= 1'b1; + // }}} + + // wb_pending, wb_outstanding + // {{{ + initial wb_pending = 0; + initial wb_outstanding = 0; + always @(posedge i_clk) + if ((w_reset)||(!o_wb_cyc)||(i_wb_err)||(err_state)) + begin + wb_pending <= 1'b0; + wb_outstanding <= 0; + end else case({ (o_wb_stb)&&(!i_wb_stall), i_wb_ack }) + 2'b01: begin + wb_outstanding <= wb_outstanding - 1'b1; + wb_pending <= (wb_outstanding >= 2); + end + 2'b10: begin + wb_outstanding <= wb_outstanding + 1'b1; + wb_pending <= 1'b1; + end + default: begin end + endcase + // }}} + + assign next_first = r_first + 1'b1; + assign next_last = r_last + 1'b1; + + // fifo_full, fifo_empty + // {{{ + initial fifo_full = 1'b0; + initial fifo_empty = 1'b1; + always @(posedge i_clk) + if (w_reset) + begin + fifo_full <= 1'b0; + fifo_empty <= 1'b1; + end else case({ (o_axi_bvalid)&&(i_axi_bready), + (axi_write_accepted) }) + 2'b01: begin + fifo_full <= (next_first[LGFIFO-1:0] == r_last[LGFIFO-1:0]) + &&(next_first[LGFIFO]!=r_last[LGFIFO]); + fifo_empty <= 1'b0; + end + 2'b10: begin + fifo_full <= 1'b0; + fifo_empty <= 1'b0; + end + default: begin end + endcase + // }}} + + // r_first + // {{{ + initial r_first = 0; + always @(posedge i_clk) + if (w_reset) + r_first <= 0; + else if (axi_write_accepted) + r_first <= r_first + 1'b1; + // }}} + + // r_mid + // {{{ + initial r_mid = 0; + always @(posedge i_clk) + if (w_reset) + r_mid <= 0; + else if ((o_wb_cyc)&&((i_wb_ack)||(i_wb_err))) + r_mid <= r_mid + 1'b1; + else if ((err_state)&&(r_mid != r_first)) + r_mid <= r_mid + 1'b1; + // }}} + + // r_last + // {{{ + initial r_last = 0; + always @(posedge i_clk) + if (w_reset) + r_last <= 0; + else if ((o_axi_bvalid)&&(i_axi_bready)) + r_last <= r_last + 1'b1; + // }}} + + // err_loc + // {{{ + always @(posedge i_clk) + if ((o_wb_cyc)&&(i_wb_err)) + err_loc <= r_mid; + // }}} + + // o_axi_bresp + // {{{ + initial o_axi_bresp = 2'b00; + always @(posedge i_clk) + if (w_reset) + o_axi_bresp <= 0; + else if ((!o_axi_bvalid)||(i_axi_bready)) + begin + if ((!err_state)&&((!o_wb_cyc)||(!i_wb_err))) + o_axi_bresp <= 2'b00; + else if ((!err_state)&&(o_wb_cyc)&&(i_wb_err)) + begin + if (o_axi_bvalid) + o_axi_bresp <= (r_mid == next_last) ? 2'b10 : 2'b00; + else + o_axi_bresp <= (r_mid == r_last) ? 2'b10 : 2'b00; + end else if (err_state) + begin + if (next_last == err_loc) + o_axi_bresp <= 2'b10; + else if (o_axi_bresp[1]) + o_axi_bresp <= 2'b11; + end else + o_axi_bresp <= 0; + end + // }}} + + // err_state + // {{{ + initial err_state = 0; + always @(posedge i_clk) + if (w_reset) + err_state <= 0; + else if (r_first == r_last) + err_state <= 0; + else if ((o_wb_cyc)&&(i_wb_err)) + err_state <= 1'b1; + // }}} + + // o_axi_bvalid + // {{{ + initial o_axi_bvalid = 1'b0; + always @(posedge i_clk) + if (w_reset) + o_axi_bvalid <= 0; + else if ((o_wb_cyc)&&((i_wb_ack)||(i_wb_err))) + o_axi_bvalid <= 1'b1; + else if ((o_axi_bvalid)&&(i_axi_bready)) + begin + if (err_state) + o_axi_bvalid <= (next_last != r_first); + else + o_axi_bvalid <= (next_last != r_mid); + end + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, i_axi_awprot, + fifo_empty, i_axi_awaddr[AXI_LSBS-1:0] }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + wire f_axi_stalled; + wire [LGFIFO:0] f_wb_nreqs, f_wb_nacks, f_wb_outstanding; + wire [LGFIFO:0] wb_fill; + wire [LGFIFO:0] f_axi_rd_outstanding, + f_axi_wr_outstanding, + f_axi_awr_outstanding; + wire [LGFIFO:0] f_mid_minus_err, f_err_minus_last, + f_first_minus_err; + wire [LGFIFO:0] f_fifo_fill; + + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + +`ifdef AXILWR2WBSP +`define ASSUME assume +`else +`define ASSUME assert +`endif + + always @(*) + if (!f_past_valid) + `ASSUME(w_reset); + + assign f_fifo_fill = (r_first - r_last); + + always @(*) + if (err_state) + begin + assert(!r_awvalid || !o_axi_awready); + assert(!r_wvalid || !o_axi_wready); + + assert(!o_wb_cyc); + end + + always @(*) + if ((fifo_empty)&&(!w_reset)) + assert((!fifo_full)&&(r_first == r_last)&&(r_mid == r_last)); + + always @(*) + if (fifo_full) + begin + assert(!fifo_empty); + assert(r_first[LGFIFO-1:0] == r_last[LGFIFO-1:0]); + assert(r_first[LGFIFO] != r_last[LGFIFO]); + end + + always @(*) + assert(f_fifo_fill <= (1<<LGFIFO)); + + always @(*) + if (fifo_full) + begin + assert(!r_awvalid || !o_axi_awready); + assert(!r_wvalid || !o_axi_wready); + end + + always @(*) + assert(fifo_full == (f_fifo_fill >= (1<<LGFIFO))); + always @(*) + assert(wb_pending == (wb_outstanding != 0)); + + + assign f_first = r_first; + assign f_mid = r_mid; + assign f_last = r_last; + assign f_wpending = { r_awvalid, r_wvalid }; + + fwb_master #( + .AW(AW), .DW(DW), .F_LGDEPTH(LGFIFO+1) + ) fwb(i_clk, w_reset, + o_wb_cyc, o_wb_stb, 1'b1, o_wb_addr, o_wb_data, o_wb_sel, + i_wb_ack, i_wb_stall, {(DW){1'b0}}, i_wb_err, + f_wb_nreqs,f_wb_nacks, f_wb_outstanding); + + always @(*) + if (o_wb_cyc) + assert(f_wb_outstanding == wb_outstanding); + + always @(*) + if (o_wb_cyc) + assert(wb_outstanding <= (1<<LGFIFO)); + + assign wb_fill = r_first - r_mid; + always @(*) + assert(wb_fill <= f_fifo_fill); + always @(*) + if (!w_reset) + begin + if (o_wb_stb) + begin + assert(wb_outstanding+1 == wb_fill); + end else if (o_wb_cyc) + begin + assert(wb_outstanding == wb_fill); + end else if (!err_state) + assert((wb_fill == 0)&&(wb_outstanding == 0)); + end + + faxil_slave #( + // {{{ + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(LGFIFO+1), + .F_OPT_WRITE_ONLY(1), + .F_AXI_MAXWAIT(0), + .F_AXI_MAXDELAY(0) + // }}} + ) faxil( + // {{{ + .i_clk(i_clk), .i_axi_reset_n(i_axi_reset_n), + // + // AXI write address channel signals + .i_axi_awvalid(i_axi_awvalid), .i_axi_awready(o_axi_awready), + .i_axi_awaddr(i_axi_awaddr),.i_axi_awprot(i_axi_awprot), + // AXI write data channel signals + .i_axi_wvalid(i_axi_wvalid), .i_axi_wready(o_axi_wready), + .i_axi_wdata(i_axi_wdata), .i_axi_wstrb(i_axi_wstrb), + // AXI write response channel signals + .i_axi_bvalid(o_axi_bvalid), .i_axi_bready(i_axi_bready), + .i_axi_bresp(o_axi_bresp), + // AXI read address channel signals + .i_axi_arvalid(1'b0), .i_axi_arready(1'b0), + .i_axi_araddr(i_axi_awaddr),.i_axi_arprot(i_axi_awprot), + // AXI read data channel signals + .i_axi_rvalid(1'b0), .i_axi_rready(1'b0), + .i_axi_rdata({(DW){1'b0}}), + .i_axi_rresp(2'b00), + .f_axi_rd_outstanding(f_axi_rd_outstanding), + .f_axi_wr_outstanding(f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding) + // }}} + ); + + always @(*) + assert(f_axi_wr_outstanding - (r_wvalid ? 1:0) + == f_axi_awr_outstanding - (r_awvalid ? 1:0)); + always @(*) + assert(f_axi_rd_outstanding == 0); + always @(*) + assert(f_axi_wr_outstanding - (r_wvalid ? 1:0) == f_fifo_fill); + always @(*) + assert(f_axi_awr_outstanding - (r_awvalid ? 1:0) == f_fifo_fill); + always @(*) + if (r_wvalid) + assert(f_axi_wr_outstanding > 0); + + always @(*) + if (r_awvalid) + assert(f_axi_awr_outstanding > 0); + + assign f_mid_minus_err = f_mid - err_loc; + assign f_err_minus_last = err_loc - f_last; + assign f_first_minus_err = f_first - err_loc; + always @(*) + if (o_axi_bvalid) + begin + if (!err_state) + begin + assert(!o_axi_bresp[1]); + end else if (err_loc == f_last) + begin + assert(o_axi_bresp == 2'b10); + end else if (f_err_minus_last < (1<<LGFIFO)) + begin + assert(!o_axi_bresp[1]); + end else + assert(o_axi_bresp[1]); + end + + always @(*) + if (err_state) + begin + assert(o_axi_bvalid == (r_first != r_last)); + end else + assert(o_axi_bvalid == (r_mid != r_last)); + + always @(*) + if (err_state) + assert(f_first_minus_err <= (1<<LGFIFO)); + + always @(*) + if (err_state) + assert(f_first_minus_err != 0); + + always @(*) + if (err_state) + assert(f_mid_minus_err <= f_first_minus_err); + + assign f_axi_stalled = (fifo_full)||(err_state) + ||((o_wb_stb)&&(i_wb_stall)); + + always @(*) + if ((r_awvalid)&&(f_axi_stalled)) + assert(!o_axi_awready); + always @(*) + if ((r_wvalid)&&(f_axi_stalled)) + assert(!o_axi_wready); + + //////////////////////////////////////////////////////////////////////// + // + // Cover property checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + cover(o_wb_cyc && o_wb_stb && !i_wb_stall); + always @(*) + cover(o_wb_cyc && i_wb_ack); + + always @(posedge i_clk) + cover(o_wb_cyc && $past(o_wb_cyc && o_wb_stb && !i_wb_stall));// + + always @(posedge i_clk) + cover(o_wb_cyc && o_wb_stb && !i_wb_stall + && $past(o_wb_cyc && o_wb_stb && !i_wb_stall,2) + && $past(o_wb_cyc && o_wb_stb && !i_wb_stall,4)); // + + always @(posedge i_clk) + cover(o_wb_cyc && o_wb_stb && !i_wb_stall + && $past(o_wb_cyc && o_wb_stb && !i_wb_stall) + && $past(o_wb_cyc && o_wb_stb && !i_wb_stall)); // + + always @(posedge i_clk) + cover(o_wb_cyc && i_wb_ack + && $past(o_wb_cyc && i_wb_ack) + && $past(o_wb_cyc && i_wb_ack)); // + + // AXI covers + always @(posedge i_clk) + cover(o_axi_bvalid && i_axi_bready + && $past(o_axi_bvalid && i_axi_bready,1) + && $past(o_axi_bvalid && i_axi_bready,2)); // + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_formal; + assign unused_formal = &{ 1'b0, f_wb_nreqs, f_wb_nacks }; + // Verilator lint_on UNUSED + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axilxbar.v b/rtl/wb2axip/axilxbar.v new file mode 100644 index 0000000..4a1ed43 --- /dev/null +++ b/rtl/wb2axip/axilxbar.v @@ -0,0 +1,2421 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axilxbar.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Create a full crossbar between NM AXI-lite sources (masters), +// and NS AXI-lite slaves. Every master can talk to any slave, +// provided it isn't already busy. +// +// Performance: This core has been designed with the goal of being able to push +// one transaction through the interconnect, from any master to +// any slave, per clock cycle. This may perhaps be its most unique +// feature. While throughput is good, latency is something else. +// +// The arbiter requires a clock to switch, then another clock to send data +// downstream. This creates a minimum two clock latency up front. The +// return path suffers another clock of latency as well, placing the +// minimum latency at four clocks. The minimum write latency is at +// least one clock longer, since the write data must wait for the write +// address before proceeeding. +// +// Usage: To use, you must first set NM and NS to the number of masters +// and the number of slaves you wish to connect to. You then need to +// adjust the addresses of the slaves, found SLAVE_ADDR array. Those +// bits that are relevant in SLAVE_ADDR to then also be set in SLAVE_MASK. +// Adjusting the data and address widths go without saying. +// +// Lower numbered masters are given priority in any "fight". +// +// Channel grants are given on the condition that 1) they are requested, +// 2) no other channel has a grant, 3) all of the responses have been +// received from the current channel, and 4) the internal counters are +// not overflowing. +// +// The core limits the number of outstanding transactions on any channel to +// 1<<LGMAXBURST-1. +// +// Channel grants are lost 1) after OPT_LINGER clocks of being idle, or +// 2) when another master requests an idle (but still lingering) channel +// assignment, or 3) once all the responses have been returned to the +// current channel, and the current master is requesting another channel. +// +// A special slave is allocated for the case of no valid address. +// +// Since the write channel has no address information, the write data +// channel always be delayed by at least one clock from the write address +// channel. +// +// If OPT_LOWPOWER is set, then unused values will be set to zero. +// This can also be used to help identify relevant values within any +// trace. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axilxbar #( + // {{{ + parameter integer C_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 32, + // + // NM is the number of master interfaces this core supports + parameter NM = 4, + // + // NS is the number of slave interfaces + parameter NS = 8, + // + // AW, and DW, are short-hand abbreviations used locally. + localparam AW = C_AXI_ADDR_WIDTH, + localparam DW = C_AXI_DATA_WIDTH, + // SLAVE_ADDR is a bit vector containing AW bits for each of the + // slaves indicating the base address of the slave. This + // goes with SLAVE_MASK below. + parameter [NS*AW-1:0] SLAVE_ADDR = { + 3'b111, {(AW-3){1'b0}}, + 3'b110, {(AW-3){1'b0}}, + 3'b101, {(AW-3){1'b0}}, + 3'b100, {(AW-3){1'b0}}, + 3'b011, {(AW-3){1'b0}}, + 3'b010, {(AW-3){1'b0}}, + 4'b0001, {(AW-4){1'b0}}, + 4'b0000, {(AW-4){1'b0}} }, + // + // SLAVE_MASK indicates which bits in the SLAVE_ADDR bit vector + // need to be checked to determine if a given address request + // maps to the given slave or not + // Verilator lint_off WIDTH + parameter [NS*AW-1:0] SLAVE_MASK = + (NS <= 1) ? { 4'b1111, {(AW-4){1'b0}} } + : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, + {(2){ 4'b1111, {(AW-4){1'b0}} }} }, + // Verilator lint_on WIDTH + // + // If set, OPT_LOWPOWER will set all unused registers, both + // internal and external, to zero anytime their corresponding + // *VALID bit is clear + parameter [0:0] OPT_LOWPOWER = 1, + // + // OPT_LINGER is the number of cycles to wait, following a + // transaction, before tearing down the bus grant. + parameter OPT_LINGER = 4, + // + // LGMAXBURST is the log (base two) of the maximum number of + // requests that can be outstanding on any given channel at any + // given time. It is used within this core to control the + // counters that are used to determine if a particular channel + // grant must stay open, or if it may be closed. + parameter LGMAXBURST = 5 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // Incoming AXI4-lite slave port(s) + // {{{ + input wire [NM-1:0] S_AXI_AWVALID, + output wire [NM-1:0] S_AXI_AWREADY, + input wire [NM*C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + // Verilator coverage_off + input wire [NM*3-1:0] S_AXI_AWPROT, + // Verilator coverage_on + // + input wire [NM-1:0] S_AXI_WVALID, + output wire [NM-1:0] S_AXI_WREADY, + input wire [NM*C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [NM*C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire [NM-1:0] S_AXI_BVALID, + input wire [NM-1:0] S_AXI_BREADY, + output wire [NM*2-1:0] S_AXI_BRESP, + // + input wire [NM-1:0] S_AXI_ARVALID, + output wire [NM-1:0] S_AXI_ARREADY, + input wire [NM*C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + // Verilator coverage_off + input wire [NM*3-1:0] S_AXI_ARPROT, + // Verilator coverage_on + // + output wire [NM-1:0] S_AXI_RVALID, + input wire [NM-1:0] S_AXI_RREADY, + output wire [NM*C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [NM*2-1:0] S_AXI_RRESP, + // }}} + // Outgoing AXI4-lite master port(s) + // {{{ + output wire [NS*C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [NS*3-1:0] M_AXI_AWPROT, + output wire [NS-1:0] M_AXI_AWVALID, + input wire [NS-1:0] M_AXI_AWREADY, + // + output wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [NS*C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire [NS-1:0] M_AXI_WVALID, + input wire [NS-1:0] M_AXI_WREADY, + // + input wire [NS*2-1:0] M_AXI_BRESP, + input wire [NS-1:0] M_AXI_BVALID, + output wire [NS-1:0] M_AXI_BREADY, + // + output wire [NS*C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [NS*3-1:0] M_AXI_ARPROT, + output wire [NS-1:0] M_AXI_ARVALID, + input wire [NS-1:0] M_AXI_ARREADY, + // + input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [NS*2-1:0] M_AXI_RRESP, + input wire [NS-1:0] M_AXI_RVALID, + output wire [NS-1:0] M_AXI_RREADY + // }}} + // }}} + ); + // + // Local parameters, derived from those above + // {{{ + localparam LGLINGER = (OPT_LINGER>1) ? $clog2(OPT_LINGER+1) : 1; + // + localparam LGNM = (NM>1) ? $clog2(NM) : 1; + localparam LGNS = (NS>1) ? $clog2(NS+1) : 1; + // + // In order to use indexes, and hence fully balanced mux trees, it helps + // to make certain that we have a power of two based lookup. NMFULL + // is the number of masters in this lookup, with potentially some + // unused extra ones. NSFULL is defined similarly. + localparam NMFULL = (NM>1) ? (1<<LGNM) : 1; + localparam NSFULL = (NS>1) ? (1<<LGNS) : 2; + // + localparam [1:0] INTERCONNECT_ERROR = 2'b11; + localparam [0:0] OPT_SKID_INPUT = 0; + localparam [0:0] OPT_BUFFER_DECODER = 1; + + genvar N,M; + integer iN, iM; + // }}} + + // {{{ + reg [NSFULL-1:0] wrequest [0:NM-1]; + reg [NSFULL-1:0] rrequest [0:NM-1]; + reg [NSFULL-1:0] wrequested [0:NM]; + reg [NSFULL-1:0] rrequested [0:NM]; + reg [NS:0] wgrant [0:NM-1]; + reg [NS:0] rgrant [0:NM-1]; + reg [NM-1:0] swgrant; + reg [NM-1:0] srgrant; + reg [NS-1:0] mwgrant; + reg [NS-1:0] mrgrant; + + // verilator lint_off UNUSED + wire [LGMAXBURST-1:0] w_sawpending [0:NM-1]; +`ifdef FORMAL + wire [LGMAXBURST-1:0] w_swpending [0:NM-1]; +`endif + wire [LGMAXBURST-1:0] w_srpending [0:NM-1]; + // verilator lint_on UNUSED + reg [NM-1:0] swfull; + reg [NM-1:0] srfull; + reg [NM-1:0] swempty; + reg [NM-1:0] srempty; + // + wire [LGNS-1:0] swindex [0:NMFULL-1]; + wire [LGNS-1:0] srindex [0:NMFULL-1]; + wire [LGNM-1:0] mwindex [0:NSFULL-1]; + wire [LGNM-1:0] mrindex [0:NSFULL-1]; + + wire [NM-1:0] wdata_expected; + + // The shadow buffers + wire [NMFULL-1:0] m_awvalid, m_wvalid, m_arvalid; + wire [NM-1:0] dcd_awvalid, dcd_arvalid; + + wire [C_AXI_ADDR_WIDTH-1:0] m_awaddr [0:NMFULL-1]; + wire [2:0] m_awprot [0:NMFULL-1]; + wire [C_AXI_DATA_WIDTH-1:0] m_wdata [0:NMFULL-1]; + wire [C_AXI_DATA_WIDTH/8-1:0] m_wstrb [0:NMFULL-1]; + + wire [C_AXI_ADDR_WIDTH-1:0] m_araddr [0:NMFULL-1]; + wire [2:0] m_arprot [0:NMFULL-1]; + // + + wire [NM-1:0] skd_awvalid, skd_awstall, skd_wvalid; + wire [NM-1:0] skd_arvalid, skd_arstall; + wire [AW-1:0] skd_awaddr [0:NM-1]; + wire [3-1:0] skd_awprot [0:NM-1]; + wire [AW-1:0] skd_araddr [0:NM-1]; + wire [3-1:0] skd_arprot [0:NM-1]; + + reg [NM-1:0] r_bvalid; + reg [1:0] r_bresp [0:NM-1]; + + reg [NSFULL-1:0] m_axi_awvalid; + reg [NSFULL-1:0] m_axi_awready; + reg [NSFULL-1:0] m_axi_wvalid; + reg [NSFULL-1:0] m_axi_wready; + reg [NSFULL-1:0] m_axi_bvalid; +`ifdef FORMAL + reg [NSFULL-1:0] m_axi_bready; +`endif + reg [1:0] m_axi_bresp [0:NSFULL-1]; + + reg [NSFULL-1:0] m_axi_arvalid; + // Verilator lint_off UNUSED + reg [NSFULL-1:0] m_axi_arready; + // Verilator lint_on UNUSED + reg [NSFULL-1:0] m_axi_rvalid; + // Verilator lint_off UNUSED + reg [NSFULL-1:0] m_axi_rready; + // Verilator lint_on UNUSED + + reg [NM-1:0] r_rvalid; + reg [1:0] r_rresp [0:NM-1]; + reg [DW-1:0] r_rdata [0:NM-1]; + + reg [DW-1:0] m_axi_rdata [0:NSFULL-1]; + reg [1:0] m_axi_rresp [0:NSFULL-1]; + + reg [NM-1:0] slave_awaccepts; + reg [NM-1:0] slave_waccepts; + reg [NM-1:0] slave_raccepts; + // }}} + + // m_axi_[aw|w|b]* + // {{{ + always @(*) + begin + m_axi_awvalid = -1; + m_axi_awready = -1; + m_axi_wvalid = -1; + m_axi_wready = -1; + m_axi_bvalid = 0; + + m_axi_awvalid[NS-1:0] = M_AXI_AWVALID; + m_axi_awready[NS-1:0] = M_AXI_AWREADY; + m_axi_wvalid[NS-1:0] = M_AXI_WVALID; + m_axi_wready[NS-1:0] = M_AXI_WREADY; + m_axi_bvalid[NS-1:0] = M_AXI_BVALID; + + for(iM=0; iM<NS; iM=iM+1) + begin + m_axi_bresp[iM] = M_AXI_BRESP[iM* 2 +: 2]; + + m_axi_rdata[iM] = M_AXI_RDATA[iM*DW +: DW]; + m_axi_rresp[iM] = M_AXI_RRESP[iM* 2 +: 2]; + end + for(iM=NS; iM<NSFULL; iM=iM+1) + begin + m_axi_bresp[iM] = INTERCONNECT_ERROR; + + m_axi_rdata[iM] = 0; + m_axi_rresp[iM] = INTERCONNECT_ERROR; + end + +`ifdef FORMAL + m_axi_bready = -1; + m_axi_bready[NS-1:0] = M_AXI_BREADY; +`endif + end + // }}} + + generate for(N=0; N<NM; N=N+1) + begin : DECODE_WRITE_REQUEST + // {{{ + wire [NS:0] wdecode; + reg r_mawvalid, r_mwvalid; + + // awskid + // {{{ + skidbuffer #( + // {{{ + .DW(AW+3), .OPT_OUTREG(OPT_SKID_INPUT) + // }}} + ) awskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID[N]), .o_ready(S_AXI_AWREADY[N]), + .i_data({ S_AXI_AWADDR[N*AW +: AW], S_AXI_AWPROT[N*3 +: 3] }), + .o_valid(skd_awvalid[N]), .i_ready(!skd_awstall[N]), + .o_data({ skd_awaddr[N], skd_awprot[N] }) + // }}} + ); + // }}} + + // write address decoding + // {{{ + addrdecode #( + // {{{ + .AW(AW), .DW(3), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(OPT_BUFFER_DECODER) + // }}} + ) wraddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(skd_awvalid[N]), .o_stall(skd_awstall[N]), + .i_addr(skd_awaddr[N]), .i_data(skd_awprot[N]), + .o_valid(dcd_awvalid[N]), + .i_stall(!dcd_awvalid[N]||!slave_awaccepts[N]), + .o_decode(wdecode), .o_addr(m_awaddr[N]), + .o_data(m_awprot[N]) + // }}} + ); + // }}} + + // wskid + // {{{ + skidbuffer #( + // {{{ + .DW(DW+DW/8), .OPT_OUTREG(OPT_SKID_INPUT) + // }}} + ) wskid ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_WVALID[N]), .o_ready(S_AXI_WREADY[N]), + .i_data({ S_AXI_WDATA[N*DW +: DW], + S_AXI_WSTRB[N*DW/8 +: DW/8]}), + .o_valid(skd_wvalid[N]), + .i_ready(m_wvalid[N] && slave_waccepts[N]), + .o_data({ m_wdata[N], m_wstrb[N] }) + // }}} + ); + // }}} + + // slave_awaccepts + // {{{ + always @(*) + begin + slave_awaccepts[N] = 1'b1; + if (!swgrant[N]) + slave_awaccepts[N] = 1'b0; + if (swfull[N]) + slave_awaccepts[N] = 1'b0; + if (!wrequest[N][swindex[N]]) + slave_awaccepts[N] = 1'b0; + if (!wgrant[N][NS]&&(m_axi_awvalid[swindex[N]] && !m_axi_awready[swindex[N]])) + slave_awaccepts[N] = 1'b0; + // ERRORs are always accepted + // back pressure is handled in the write side + end + // }}} + + // slave_waccepts + // {{{ + always @(*) + begin + slave_waccepts[N] = 1'b1; + if (!swgrant[N]) + slave_waccepts[N] = 1'b0; + if (!wdata_expected[N]) + slave_waccepts[N] = 1'b0; + if (!wgrant[N][NS] &&(m_axi_wvalid[swindex[N]] + && !m_axi_wready[swindex[N]])) + slave_waccepts[N] = 1'b0; + if (wgrant[N][NS]&&(S_AXI_BVALID[N]&& !S_AXI_BREADY[N])) + slave_waccepts[N] = 1'b0; + end + // }}} + + // r_mawvalid, r_mwvalid + // {{{ + always @(*) + begin + r_mawvalid= dcd_awvalid[N] && !swfull[N]; + r_mwvalid = skd_wvalid[N]; + wrequest[N]= 0; + if (!swfull[N]) + wrequest[N][NS:0] = wdecode; + end + + assign m_awvalid[N] = r_mawvalid; + assign m_wvalid[N] = r_mwvalid; + // }}} + + // }}} + end for (N=NM; N<NMFULL; N=N+1) + begin : UNUSED_WSKID_BUFFERS + // {{{ + assign m_awvalid[N] = 0; + assign m_awaddr[N] = 0; + assign m_awprot[N] = 0; + assign m_wdata[N] = 0; + assign m_wstrb[N] = 0; + // }}} + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin : DECODE_READ_REQUEST + // {{{ + wire [NS:0] rdecode; + reg r_marvalid; + + // arskid + // {{{ + skidbuffer #( + // {{{ + .DW(AW+3), .OPT_OUTREG(OPT_SKID_INPUT) + // }}} + ) arskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID[N]), .o_ready(S_AXI_ARREADY[N]), + .i_data({ S_AXI_ARADDR[N*AW +: AW], S_AXI_ARPROT[N*3 +: 3] }), + .o_valid(skd_arvalid[N]), .i_ready(!skd_arstall[N]), + .o_data({ skd_araddr[N], skd_arprot[N] }) + // }}} + ); + // }}} + + // Read address decoding + // {{{ + addrdecode #( + // {{{ + .AW(AW), .DW(3), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(OPT_BUFFER_DECODER) + // }}} + ) rdaddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(skd_arvalid[N]), .o_stall(skd_arstall[N]), + .i_addr(skd_araddr[N]), .i_data(skd_arprot[N]), + .o_valid(dcd_arvalid[N]), + .i_stall(!m_arvalid[N] || !slave_raccepts[N]), + .o_decode(rdecode), .o_addr(m_araddr[N]), + .o_data(m_arprot[N]) + // }}} + ); + // }}} + + // r_marvalid -> m_arvalid[N] + // {{{ + always @(*) + begin + r_marvalid = dcd_arvalid[N] && !srfull[N]; + rrequest[N] = 0; + if (!srfull[N]) + rrequest[N][NS:0] = rdecode; + end + + assign m_arvalid[N] = r_marvalid; + // }}} + + // slave_raccepts + // {{{ + always @(*) + begin + slave_raccepts[N] = 1'b1; + if (!srgrant[N]) + slave_raccepts[N] = 1'b0; + if (srfull[N]) + slave_raccepts[N] = 1'b0; + // verilator lint_off WIDTH + if (!rrequest[N][srindex[N]]) + slave_raccepts[N] = 1'b0; + // verilator lint_on WIDTH + if (!rgrant[N][NS]) + begin + if (m_axi_arvalid[srindex[N]] && !m_axi_arready[srindex[N]]) + slave_raccepts[N] = 1'b0; + end else if (S_AXI_RVALID[N] && !S_AXI_RREADY[N]) + slave_raccepts[N] = 1'b0; + end + // }}} + + // }}} + end for (N=NM; N<NMFULL; N=N+1) + begin : UNUSED_RSKID_BUFFERS + // {{{ + assign m_arvalid[N] = 0; + assign m_araddr[N] = 0; + assign m_arprot[N] = 0; + // }}} + end endgenerate + + // wrequested + // {{{ + always @(*) + begin : DECONFLICT_WRITE_REQUESTS + + for(iN=1; iN<NM ; iN=iN+1) + wrequested[iN] = 0; + + // Vivado may complain about too many bits for wrequested. + // This is (currrently) expected. swindex is used to index + // into wrequested, and swindex has LGNS bits, where LGNS + // is $clog2(NS+1) rather than $clog2(NS). The extra bits + // are defined to be zeros, but the point is there are defined. + // Therefore, no matter what swindex is, it will always + // reference something valid. + wrequested[NM] = 0; + + for(iM=0; iM<NS; iM=iM+1) + begin + wrequested[0][iM] = 1'b0; + for(iN=1; iN<NM ; iN=iN+1) + begin + // Continue to request any channel with + // a grant and pending operations + if (wrequest[iN-1][iM] && wgrant[iN-1][iM]) + wrequested[iN][iM] = 1; + if (wrequest[iN-1][iM] && (!swgrant[iN-1]||swempty[iN-1])) + wrequested[iN][iM] = 1; + // Otherwise, if it's already claimed, then + // it can't be claimed again + if (wrequested[iN-1][iM]) + wrequested[iN][iM] = 1; + end + wrequested[NM][iM] = wrequest[NM-1][iM] || wrequested[NM-1][iM]; + end + end + // }}} + + // rrequested + // {{{ + always @(*) + begin : DECONFLICT_READ_REQUESTS + + for(iN=0; iN<NM ; iN=iN+1) + rrequested[iN] = 0; + + // See the note above for wrequested. This applies to + // rrequested as well. + rrequested[NM] = 0; + + for(iM=0; iM<NS; iM=iM+1) + begin + rrequested[0][iM] = 0; + for(iN=1; iN<NM ; iN=iN+1) + begin + // Continue to request any channel with + // a grant and pending operations + if (rrequest[iN-1][iM] && rgrant[iN-1][iM]) + rrequested[iN][iM] = 1; + if (rrequest[iN-1][iM] && (!srgrant[iN-1] || srempty[iN-1])) + rrequested[iN][iM] = 1; + // Otherwise, if it's already claimed, then + // it can't be claimed again + if (rrequested[iN-1][iM]) + rrequested[iN][iM] = 1; + end + rrequested[NM][iM] = rrequest[NM-1][iM] || rrequested[NM-1][iM]; + end + end + // }}} + + // mwgrant, mrgrant + // {{{ + generate for(M=0; M<NS; M=M+1) + begin : GEN_GRANT + // {{{ + initial mwgrant[M] = 0; + always @(*) + begin + mwgrant[M] = 0; + for(iN=0; iN<NM; iN=iN+1) + if (wgrant[iN][M]) + mwgrant[M] = 1; + end + + always @(*) + begin + mrgrant[M] = 0; + for(iN=0; iN<NM; iN=iN+1) + if (rgrant[iN][M]) + mrgrant[M] = 1; + end + // }}} + end endgenerate + // }}} + + generate for(N=0; N<NM; N=N+1) + begin : ARBITRATE_WRITE_REQUESTS + // {{{ + // Declarations + // {{{ + reg stay_on_channel; + reg requested_channel_is_available; + reg leave_channel; + reg [LGNS-1:0] requested_index; + wire linger; + // }}} + + // stay_on_channel + // {{{ + always @(*) + begin + stay_on_channel = |(wrequest[N][NS:0] & wgrant[N]); + + if (swgrant[N] && !swempty[N]) + stay_on_channel = 1; + end + // }}} + + // requested_channel_is_available + // {{{ + always @(*) + begin + requested_channel_is_available = + |(wrequest[N][NS-1:0] & ~mwgrant + & ~wrequested[N][NS-1:0]); + if (wrequest[N][NS]) + requested_channel_is_available = 1; + + if (NM < 2) + requested_channel_is_available = m_awvalid[N]; + end + // }}} + + if (OPT_LINGER == 0) + begin : NO_LINGER + // {{{ + assign linger = 0; + // }}} + end else begin : WRITE_LINGER + // {{{ + reg [LGLINGER-1:0] linger_counter; + reg r_linger; + + initial r_linger = 0; + initial linger_counter = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || wgrant[N][NS]) + begin + r_linger <= 0; + linger_counter <= 0; + end else if (!swempty[N] || S_AXI_BVALID[N]) + begin + linger_counter <= OPT_LINGER; + r_linger <= 1; + end else if (linger_counter > 0) + begin + r_linger <= (linger_counter > 1); + linger_counter <= linger_counter - 1; + end else + r_linger <= 0; + + assign linger = r_linger; + +`ifdef FORMAL + // {{{ + always @(*) + assert(linger == (linger_counter != 0)); + // }}} +`endif + // }}} + end + + // leave_channel + // {{{ + always @(*) + begin + leave_channel = 0; + if (!m_awvalid[N] + && (!linger || wrequested[NM][swindex[N]])) + // Leave the channel after OPT_LINGER counts + // of the channel being idle, or when someone + // else asks for the channel + leave_channel = 1; + if (m_awvalid[N] && !wrequest[N][swindex[N]]) + // Need to leave this channel to connect + // to any other channel + leave_channel = 1; + end + // }}} + + // wgrant, swgrant + // {{{ + initial wgrant[N] = 0; + initial swgrant[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + wgrant[N] <= 0; + swgrant[N] <= 0; + end else if (!stay_on_channel) + begin + if (requested_channel_is_available) + begin + // Switching channels + swgrant[N] <= 1'b1; + wgrant[N] <= wrequest[N][NS:0]; + end else if (leave_channel) + begin + swgrant[N] <= 1'b0; + wgrant[N] <= 0; + end + end + // }}} + + // requested_index + // {{{ + always @(wrequest[N]) + begin + requested_index = 0; + for(iM=0; iM<=NS; iM=iM+1) + if (wrequest[N][iM]) + requested_index= requested_index | iM[LGNS-1:0]; + end + // }}} + + // Now for swindex + // {{{ + reg [LGNS-1:0] r_swindex; + + initial r_swindex = 0; + always @(posedge S_AXI_ACLK) + if (!stay_on_channel && requested_channel_is_available) + r_swindex <= requested_index; + + assign swindex[N] = r_swindex; + // }}} + // }}} + end for (N=NM; N<NMFULL; N=N+1) + begin : EMPTY_WRITE_REQUEST + // {{{ + assign swindex[N] = 0; + // }}} + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin : ARBITRATE_READ_REQUESTS + // {{{ + // Declarations + // {{{ + reg stay_on_channel; + reg requested_channel_is_available; + reg leave_channel; + reg [LGNS-1:0] requested_index; + wire linger; + // }}} + + // stay_on_channel + // {{{ + always @(*) + begin + stay_on_channel = |(rrequest[N][NS:0] & rgrant[N]); + + if (srgrant[N] && !srempty[N]) + stay_on_channel = 1; + end + // }}} + + // requested_channel_is_available + // {{{ + always @(*) + begin + requested_channel_is_available = + |(rrequest[N][NS-1:0] & ~mrgrant + & ~rrequested[N][NS-1:0]); + if (rrequest[N][NS]) + requested_channel_is_available = 1; + + if (NM < 2) + requested_channel_is_available = m_arvalid[N]; + end + // }}} + + if (OPT_LINGER == 0) + begin : NO_LINGER + // {{{ + assign linger = 0; + // }}} + end else begin : READ_LINGER + // {{{ + reg [LGLINGER-1:0] linger_counter; + reg r_linger; + + initial r_linger = 0; + initial linger_counter = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || rgrant[N][NS]) + begin + r_linger <= 0; + linger_counter <= 0; + end else if (!srempty[N] || S_AXI_RVALID[N]) + begin + linger_counter <= OPT_LINGER; + r_linger <= 1; + end else if (linger_counter > 0) + begin + r_linger <= (linger_counter > 1); + linger_counter <= linger_counter - 1; + end else + r_linger <= 0; + + assign linger = r_linger; +`ifdef FORMAL + // {{{ + always @(*) + assert(linger == (linger_counter != 0)); + // }}} +`endif + // }}} + end + + // leave_channel + // {{{ + always @(*) + begin + leave_channel = 0; + if (!m_arvalid[N] + && (!linger || rrequested[NM][srindex[N]])) + // Leave the channel after OPT_LINGER counts + // of the channel being idle, or when someone + // else asks for the channel + leave_channel = 1; + if (m_arvalid[N] && !rrequest[N][srindex[N]]) + // Need to leave this channel to connect + // to any other channel + leave_channel = 1; + end + // }}} + + // rgrant, srgrant + // {{{ + initial rgrant[N] = 0; + initial srgrant[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rgrant[N] <= 0; + srgrant[N] <= 0; + end else if (!stay_on_channel) + begin + if (requested_channel_is_available) + begin + // Switching channels + srgrant[N] <= 1'b1; + rgrant[N] <= rrequest[N][NS:0]; + end else if (leave_channel) + begin + srgrant[N] <= 1'b0; + rgrant[N] <= 0; + end + end + // }}} + + // requested_index + // {{{ + always @(rrequest[N]) + begin + requested_index = 0; + for(iM=0; iM<=NS; iM=iM+1) + if (rrequest[N][iM]) + requested_index = requested_index|iM[LGNS-1:0]; + end + // }}} + + // Now for srindex + // {{{ + reg [LGNS-1:0] r_srindex; + + initial r_srindex = 0; + always @(posedge S_AXI_ACLK) + if (!stay_on_channel && requested_channel_is_available) + r_srindex <= requested_index; + + assign srindex[N] = r_srindex; + // }}} + // }}} + end for (N=NM; N<NMFULL; N=N+1) + begin : EMPTY_READ_REQUEST + // {{{ + assign srindex[N] = 0; + // }}} + end endgenerate + + // Calculate mwindex + generate for (M=0; M<NS; M=M+1) + begin : SLAVE_WRITE_INDEX + // {{{ + if (NM <= 1) + begin : ONE_MASTER + // {{{ + assign mwindex[M] = 0; + // }}} + end else begin : MULTIPLE_MASTERS + // {{{ + reg [LGNM-1:0] reswindex; + reg [LGNM-1:0] r_mwindex; + + always @(*) + begin + reswindex = 0; + for(iN=0; iN<NM; iN=iN+1) + if ((!swgrant[iN] || swempty[iN]) + &&(wrequest[iN][M] && !wrequested[iN][M])) + reswindex = reswindex | iN[LGNM-1:0]; + end + + always @(posedge S_AXI_ACLK) + if (!mwgrant[M]) + r_mwindex <= reswindex; + + assign mwindex[M] = r_mwindex; + // }}} + end + // }}} + end for (M=NS; M<NSFULL; M=M+1) + begin : NO_WRITE_INDEX + // {{{ + assign mwindex[M] = 0; + // }}} + end endgenerate + + // Calculate mrindex + generate for (M=0; M<NS; M=M+1) + begin : SLAVE_READ_INDEX + // {{{ + if (NM <= 1) + begin : ONE_MASTER + // {{{ + assign mrindex[M] = 0; + // }}} + end else begin : MULTIPLE_MASTERS + // {{{ + reg [LGNM-1:0] resrindex; + reg [LGNM-1:0] r_mrindex; + + always @(*) + begin + resrindex = 0; + for(iN=0; iN<NM; iN=iN+1) + if ((!srgrant[iN] || srempty[iN]) + &&(rrequest[iN][M] && !rrequested[iN][M])) + resrindex = resrindex | iN[LGNM-1:0]; + end + + always @(posedge S_AXI_ACLK) + if (!mrgrant[M]) + r_mrindex <= resrindex; + + assign mrindex[M] = r_mrindex; + // }}} + end + // }}} + end for (M=NS; M<NSFULL; M=M+1) + begin : NO_READ_INDEX + // {{{ + assign mrindex[M] = 0; + // }}} + end endgenerate + + // Assign outputs to the various slaves + generate for(M=0; M<NS; M=M+1) + begin : WRITE_SLAVE_OUTPUTS + // {{{ + + // Declarations + // {{{ + reg axi_awvalid; + reg [AW-1:0] axi_awaddr; + reg [2:0] axi_awprot; + + reg axi_wvalid; + reg [DW-1:0] axi_wdata; + reg [DW/8-1:0] axi_wstrb; + // + reg axi_bready; + + wire sawstall, swstall, mbstall; + // }}} + assign sawstall= (M_AXI_AWVALID[M]&& !M_AXI_AWREADY[M]); + assign swstall = (M_AXI_WVALID[M] && !M_AXI_WREADY[M]); + assign mbstall = (S_AXI_BVALID[mwindex[M]] && !S_AXI_BREADY[mwindex[M]]); + + // axi_awvalid + // {{{ + initial axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !mwgrant[M]) + axi_awvalid <= 0; + else if (!sawstall) + begin + axi_awvalid <= m_awvalid[mwindex[M]] + &&(slave_awaccepts[mwindex[M]]); + end + // }}} + + // axi_awaddr, axi_awprot + // {{{ + initial axi_awaddr = 0; + initial axi_awprot = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_awaddr <= 0; + axi_awprot <= 0; + end else if (OPT_LOWPOWER && !mwgrant[M]) + begin + axi_awaddr <= 0; + axi_awprot <= 0; + end else if (!sawstall) + begin + if (!OPT_LOWPOWER||(m_awvalid[mwindex[M]]&&slave_awaccepts[mwindex[M]])) + begin + axi_awaddr <= m_awaddr[mwindex[M]]; + axi_awprot <= m_awprot[mwindex[M]]; + end else begin + axi_awaddr <= 0; + axi_awprot <= 0; + end + end + // }}} + + // axi_wvalid + // {{{ + initial axi_wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !mwgrant[M]) + axi_wvalid <= 0; + else if (!swstall) + begin + axi_wvalid <= (m_wvalid[mwindex[M]]) + &&(slave_waccepts[mwindex[M]]); + end + // }}} + + // axi_wdata, axi_wstrb + // {{{ + initial axi_wdata = 0; + initial axi_wstrb = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + end else if (OPT_LOWPOWER && !mwgrant[M]) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + end else if (!swstall) + begin + if (!OPT_LOWPOWER || (m_wvalid[mwindex[M]]&&slave_waccepts[mwindex[M]])) + begin + axi_wdata <= m_wdata[mwindex[M]]; + axi_wstrb <= m_wstrb[mwindex[M]]; + end else begin + axi_wdata <= 0; + axi_wstrb <= 0; + end + end + // }}} + + // axi_bready + // {{{ + initial axi_bready = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !mwgrant[M]) + axi_bready <= 1; + else if (!mbstall) + axi_bready <= 1; + else if (M_AXI_BVALID[M]) // && mbstall + axi_bready <= 0; + // }}} + + // + assign M_AXI_AWVALID[M] = axi_awvalid; + assign M_AXI_AWADDR[M*AW +: AW] = axi_awaddr; + assign M_AXI_AWPROT[M*3 +: 3] = axi_awprot; + // + // + assign M_AXI_WVALID[M] = axi_wvalid; + assign M_AXI_WDATA[M*DW +: DW] = axi_wdata; + assign M_AXI_WSTRB[M*DW/8 +: DW/8] = axi_wstrb; + // + // + assign M_AXI_BREADY[M] = axi_bready; + // +`ifdef FORMAL + // {{{ + if (OPT_LOWPOWER) + begin + always @(*) + if (!axi_awvalid) + begin + assert(axi_awaddr == 0); + assert(axi_awprot == 0); + end + + always @(*) + if (!axi_wvalid) + begin + assert(axi_wdata == 0); + assert(axi_wstrb == 0); + end + end + // }}} +`endif + // }}} + end endgenerate + + + generate for(M=0; M<NS; M=M+1) + begin : READ_SLAVE_OUTPUTS + // {{{ + // Declarations + // {{{ + reg axi_arvalid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [2:0] axi_arprot; + // + reg axi_rready; + + wire arstall, srstall; + // }}} + assign arstall= (M_AXI_ARVALID[M]&& !M_AXI_ARREADY[M]); + assign srstall = (S_AXI_RVALID[mrindex[M]] + && !S_AXI_RREADY[mrindex[M]]); + + // axi_arvalid + // {{{ + initial axi_arvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !mrgrant[M]) + axi_arvalid <= 0; + else if (!arstall) + begin + axi_arvalid <= m_arvalid[mrindex[M]] && slave_raccepts[mrindex[M]]; + end + // }}} + + // axi_araddr, axi_arprot + // {{{ + initial axi_araddr = 0; + initial axi_arprot = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_araddr <= 0; + axi_arprot <= 0; + end else if (OPT_LOWPOWER && !mrgrant[M]) + begin + axi_araddr <= 0; + axi_arprot <= 0; + end else if (!arstall) + begin + if (!OPT_LOWPOWER || (m_arvalid[mrindex[M]] && slave_raccepts[mrindex[M]])) + begin + if (NM == 1) + begin + axi_araddr <= m_araddr[0]; + axi_arprot <= m_arprot[0]; + end else begin + axi_araddr <= m_araddr[mrindex[M]]; + axi_arprot <= m_arprot[mrindex[M]]; + end + end else begin + axi_araddr <= 0; + axi_arprot <= 0; + end + end + // }}} + + // axi_rready + // {{{ + initial axi_rready = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !mrgrant[M]) + axi_rready <= 1; + else if (!srstall) + axi_rready <= 1; + else if (M_AXI_RVALID[M] && M_AXI_RREADY[M]) // && srstall + axi_rready <= 0; + // }}} + + // + assign M_AXI_ARVALID[M] = axi_arvalid; + assign M_AXI_ARADDR[M*AW +: AW] = axi_araddr; + assign M_AXI_ARPROT[M*3 +: 3] = axi_arprot; + // + assign M_AXI_RREADY[M] = axi_rready; + // +`ifdef FORMAL + // {{{ + if (OPT_LOWPOWER) + begin + always @(*) + if (!axi_arvalid) + begin + assert(axi_araddr == 0); + assert(axi_arprot == 0); + end + end + // }}} +`endif + // }}} + end endgenerate + + // Return values + generate for (N=0; N<NM; N=N+1) + begin : WRITE_RETURN_CHANNEL + // {{{ + reg axi_bvalid; + reg [1:0] axi_bresp; + reg i_axi_bvalid; + wire [1:0] i_axi_bresp; + wire mbstall; + + initial i_axi_bvalid = 1'b0; + always @(*) + if (wgrant[N][NS]) + i_axi_bvalid = m_wvalid[N] && slave_waccepts[N]; + else + i_axi_bvalid = m_axi_bvalid[swindex[N]]; + + assign i_axi_bresp = m_axi_bresp[swindex[N]]; + + assign mbstall = S_AXI_BVALID[N] && !S_AXI_BREADY[N]; + + // r_bvalid + // {{{ + initial r_bvalid[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_bvalid[N] <= 0; + else if (mbstall && !r_bvalid[N] && !wgrant[N][NS]) + r_bvalid[N] <= swgrant[N] && i_axi_bvalid; + else if (!mbstall) + r_bvalid[N] <= 1'b0; + // }}} + + // r_bresp + // {{{ + initial r_bresp[N] = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + r_bresp[N] <= 0; + else if (OPT_LOWPOWER && (!swgrant[N] || S_AXI_BREADY[N])) + r_bresp[N] <= 0; + else if (!r_bvalid[N]) + begin + if (!OPT_LOWPOWER ||(i_axi_bvalid && !wgrant[N][NS] && mbstall)) + begin + r_bresp[N] <= i_axi_bresp; + end else + r_bresp[N] <= 0; + end + // }}} + + // axi_bvalid + // {{{ + initial axi_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_bvalid <= 0; + else if (!mbstall) + axi_bvalid <= swgrant[N] && (r_bvalid[N] || i_axi_bvalid); + // }}} + + // axi_bresp + // {{{ + initial axi_bresp = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axi_bresp <= 0; + else if (OPT_LOWPOWER && !swgrant[N]) + axi_bresp <= 0; + else if (!mbstall) + begin + if (r_bvalid[N]) + axi_bresp <= r_bresp[N]; + else if (!OPT_LOWPOWER || i_axi_bvalid) + axi_bresp <= i_axi_bresp; + else + axi_bresp <= 0; + + if (wgrant[N][NS] && (!OPT_LOWPOWER || i_axi_bvalid)) + axi_bresp <= INTERCONNECT_ERROR; + end + // }}} + + // + assign S_AXI_BVALID[N] = axi_bvalid; + assign S_AXI_BRESP[N*2 +: 2] = axi_bresp; +`ifdef FORMAL + // {{{ + always @(*) + if (r_bvalid[N]) + assert(r_bresp[N] != 2'b01); + always @(*) + if (swgrant[N]) + assert(m_axi_bready[swindex[N]] == !r_bvalid[N]); + else + assert(!r_bvalid[N]); + always @(*) + if (OPT_LOWPOWER && !r_bvalid[N]) + assert(r_bresp[N] == 0); + + always @(*) + if (OPT_LOWPOWER && !axi_bvalid) + assert(axi_bresp == 0); + // }}} +`endif + // }}} + end endgenerate + + // m_axi_?r* values + // {{{ + always @(*) + begin + m_axi_arvalid = 0; + m_axi_arready = 0; + m_axi_rvalid = 0; + m_axi_rready = 0; + + m_axi_arvalid[NS-1:0] = M_AXI_ARVALID; + m_axi_arready[NS-1:0] = M_AXI_ARREADY; + m_axi_rvalid[NS-1:0] = M_AXI_RVALID; + m_axi_rready[NS-1:0] = M_AXI_RREADY; + end + // }}} + + // Return values + generate for (N=0; N<NM; N=N+1) + begin : READ_RETURN_CHANNEL + // {{{ + reg axi_rvalid; + reg [1:0] axi_rresp; + reg [DW-1:0] axi_rdata; + wire srstall; + reg i_axi_rvalid; + + initial i_axi_rvalid = 1'b0; + always @(*) + if (rgrant[N][NS]) + i_axi_rvalid = m_arvalid[N] && slave_raccepts[N]; + else + i_axi_rvalid = m_axi_rvalid[srindex[N]]; + + assign srstall = S_AXI_RVALID[N] && !S_AXI_RREADY[N]; + + initial r_rvalid[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_rvalid[N] <= 0; + else if (srstall && !r_rvalid[N]) + r_rvalid[N] <= srgrant[N] && !rgrant[N][NS]&&i_axi_rvalid; + else if (!srstall) + r_rvalid[N] <= 0; + + initial r_rresp[N] = 0; + initial r_rdata[N] = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + r_rresp[N] <= 0; + r_rdata[N] <= 0; + end else if (OPT_LOWPOWER && (!srgrant[N] || S_AXI_RREADY[N])) + begin + r_rresp[N] <= 0; + r_rdata[N] <= 0; + end else if (!r_rvalid[N]) + begin + if (!OPT_LOWPOWER || (i_axi_rvalid && !rgrant[N][NS] && srstall)) + begin + if (NS == 1) + begin + r_rresp[N] <= m_axi_rresp[0]; + r_rdata[N] <= m_axi_rdata[0]; + end else begin + r_rresp[N] <= m_axi_rresp[srindex[N]]; + r_rdata[N] <= m_axi_rdata[srindex[N]]; + end + end else begin + r_rresp[N] <= 0; + r_rdata[N] <= 0; + end + end + + initial axi_rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_rvalid <= 0; + else if (!srstall) + axi_rvalid <= srgrant[N] && (r_rvalid[N] || i_axi_rvalid); + + initial axi_rresp = 0; + initial axi_rdata = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_rresp <= 0; + axi_rdata <= 0; + end else if (OPT_LOWPOWER && !srgrant[N]) + begin + axi_rresp <= 0; + axi_rdata <= 0; + end else if (!srstall) + begin + if (r_rvalid[N]) + begin + axi_rresp <= r_rresp[N]; + axi_rdata <= r_rdata[N]; + end else if (!OPT_LOWPOWER || i_axi_rvalid) + begin + if (NS == 1) + begin + axi_rresp <= m_axi_rresp[0]; + axi_rdata <= m_axi_rdata[0]; + end else begin + axi_rresp <= m_axi_rresp[srindex[N]]; + axi_rdata <= m_axi_rdata[srindex[N]]; + end + + if (rgrant[N][NS]) + axi_rresp <= INTERCONNECT_ERROR; + end else begin + axi_rresp <= 0; + axi_rdata <= 0; + end + end + + assign S_AXI_RVALID[N] = axi_rvalid; + assign S_AXI_RRESP[N*2 +: 2] = axi_rresp; + assign S_AXI_RDATA[N*DW +: DW]= axi_rdata; +`ifdef FORMAL + // {{{ + always @(*) + if (r_rvalid[N]) + assert(r_rresp[N] != 2'b01); + always @(*) + if (srgrant[N] && !rgrant[N][NS]) + assert(m_axi_rready[srindex[N]] == !r_rvalid[N]); + else + assert(!r_rvalid[N]); + always @(*) + if (OPT_LOWPOWER && !r_rvalid[N]) + begin + assert(r_rresp[N] == 0); + assert(r_rdata[N] == 0); + end + + always @(*) + if (OPT_LOWPOWER && !axi_rvalid) + begin + assert(axi_rresp == 0); + assert(axi_rdata == 0); + end + // }}} +`endif + // }}} + end endgenerate + + // Count pending transactions + generate for (N=0; N<NM; N=N+1) + begin : COUNT_PENDING + // {{{ + reg [LGMAXBURST-1:0] awpending, rpending, + missing_wdata; + //reg rempty, awempty; // wempty; + reg r_wdata_expected; + + initial awpending = 0; + initial swempty[N] = 1; + initial swfull[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + awpending <= 0; + swempty[N] <= 1; + swfull[N] <= 0; + end else case ({(m_awvalid[N] && slave_awaccepts[N]), + (S_AXI_BVALID[N] && S_AXI_BREADY[N])}) + 2'b01: begin + awpending <= awpending - 1; + swempty[N] <= (awpending <= 1); + swfull[N] <= 0; + end + 2'b10: begin + awpending <= awpending + 1; + swempty[N] <= 0; + swfull[N] <= &awpending[LGMAXBURST-1:1]; + end + default: begin end + endcase + + initial missing_wdata = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + missing_wdata <= 0; + else begin + missing_wdata <= missing_wdata + +((m_awvalid[N] && slave_awaccepts[N])? 1:0) + -((m_wvalid[N] && slave_waccepts[N])? 1:0); + end + + initial r_wdata_expected = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_wdata_expected <= 0; + else case({ m_awvalid[N] && slave_awaccepts[N], + m_wvalid[N] && slave_waccepts[N] }) + 2'b10: r_wdata_expected <= 1; + 2'b01: r_wdata_expected <= (missing_wdata > 1); + default: begin end + endcase + + + initial rpending = 0; + initial srempty[N] = 1; + initial srfull[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rpending <= 0; + srempty[N]<= 1; + srfull[N] <= 0; + end else case ({(m_arvalid[N] && slave_raccepts[N]), + (S_AXI_RVALID[N] && S_AXI_RREADY[N])}) + 2'b01: begin + rpending <= rpending - 1; + srempty[N] <= (rpending == 1); + srfull[N] <= 0; + end + 2'b10: begin + rpending <= rpending + 1; + srfull[N] <= &rpending[LGMAXBURST-1:1]; + srempty[N] <= 0; + end + default: begin end + endcase + + assign w_sawpending[N] = awpending; + assign w_srpending[N] = rpending; + + assign wdata_expected[N] = r_wdata_expected; + +`ifdef FORMAL + // {{{ + reg [LGMAXBURST-1:0] wpending; + reg [LGMAXBURST-1:0] f_missing_wdata; + + initial wpending = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + wpending <= 0; + else case ({(m_wvalid[N] && slave_waccepts[N]), + (S_AXI_BVALID[N] && S_AXI_BREADY[N])}) + 2'b01: wpending <= wpending - 1; + 2'b10: wpending <= wpending + 1; + default: begin end + endcase + + assign w_swpending[N] = wpending; + + always @(*) + assert(missing_wdata == awpending - wpending); + always @(*) + assert(r_wdata_expected == (missing_wdata > 0)); + always @(*) + assert(awpending >= wpending); + // }}} +`endif + // }}} + end endgenerate + + // Property validation + // {{{ + initial begin + if (NM == 0) begin + $display("At least one master must be defined"); + $stop; + end + + if (NS == 0) begin + $display("At least one slave must be defined"); + $stop; + end + end + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used to verify this core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Local declarations + // {{{ + localparam F_LGDEPTH = LGMAXBURST+1; + wire [F_LGDEPTH-1:0] fm_rd_outstanding [0:NM-1]; + wire [F_LGDEPTH-1:0] fm_wr_outstanding [0:NM-1]; + wire [F_LGDEPTH-1:0] fm_awr_outstanding [0:NM-1]; + + wire [F_LGDEPTH-1:0] fs_rd_outstanding [0:NS-1]; + wire [F_LGDEPTH-1:0] fs_wr_outstanding [0:NS-1]; + wire [F_LGDEPTH-1:0] fs_awr_outstanding [0:NS-1]; + + initial assert(NS >= 1); + initial assert(NM >= 1); + // }}} + +`ifdef VERIFIC + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // Initial value checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`ifdef VERIFIC +`define INITIAL_CHECK assume +`else +`define INITIAL_CHECK assert +`endif // VERIFIC + always @(*) + if (!f_past_valid) + begin + `INITIAL_CHECK(!S_AXI_ARESETN); + `INITIAL_CHECK(S_AXI_BVALID == 0); + `INITIAL_CHECK(S_AXI_RVALID == 0); + `INITIAL_CHECK(swgrant == 0); + `INITIAL_CHECK(srgrant == 0); + `INITIAL_CHECK(swfull == 0); + `INITIAL_CHECK(srfull == 0); + `INITIAL_CHECK(&swempty); + `INITIAL_CHECK(&srempty); + for(iN=0; iN<NM; iN=iN+1) + begin + `INITIAL_CHECK(wgrant[iN] == 0); + assume(swindex[iN] == 0); + + `INITIAL_CHECK(rgrant[iN] == 0); + assume(srindex[iN] == 0); + + `INITIAL_CHECK(r_bvalid[iN] == 0); + `INITIAL_CHECK(r_rvalid[iN] == 0); + // + `INITIAL_CHECK(r_bresp[iN] == 0); + // + `INITIAL_CHECK(r_rresp[iN] == 0); + `INITIAL_CHECK(r_rdata[iN] == 0); + end + + `INITIAL_CHECK(M_AXI_AWVALID == 0); + `INITIAL_CHECK(M_AXI_WVALID == 0); + `INITIAL_CHECK(M_AXI_RVALID == 0); + end +`endif + // }}} + + generate for(N=0; N<NM; N=N+1) + begin : CHECK_MASTER_GRANTS + // {{{ + + //////////////////////////////////////////////////////////////// + // Write grant checks + // {{{ + always @(*) + for(iM=0; iM<=NS; iM=iM+1) + begin + if (wgrant[N][iM]) + begin + assert((wgrant[N] ^ (1<<iM))==0); + assert(swgrant[N]); + assert(swindex[N] == iM); + if (iM < NS) + begin + assert(mwgrant[iM]); + assert(mwindex[iM] == N); + end + end + end + + always @(*) + if (swgrant[N]) + assert(wgrant[N] != 0); + + always @(*) + if (wrequest[N][NS]) + assert(wrequest[N][NS-1:0] == 0); + + // }}} + //////////////////////////////////////////////////////////////// + // + // Read grant checking + // {{{ + always @(*) + for(iM=0; iM<=NS; iM=iM+1) + begin + if (rgrant[N][iM]) + begin + assert((rgrant[N] ^ (1<<iM))==0); + assert(srgrant[N]); + assert(srindex[N] == iM); + if (iM < NS) + begin + assert(mrgrant[iM]); + assert(mrindex[iM] == N); + end + end + end + + always @(*) + if (srgrant[N]) + assert(rgrant[N] != 0); + + always @(*) + if (rrequest[N][NS]) + assert(rrequest[N][NS-1:0] == 0); + // }}} + // }}} + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin : CHECK_MASTERS + // {{{ + faxil_slave #( + .C_AXI_DATA_WIDTH(DW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_ASSUME_RESET(1'b1), + .F_AXI_MAXWAIT(0), + .F_AXI_MAXDELAY(0), + .F_LGDEPTH(F_LGDEPTH)) + mstri(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID[N]), + .i_axi_awready(S_AXI_AWREADY[N]), + .i_axi_awaddr(S_AXI_AWADDR[N*AW +: AW]), + .i_axi_awprot(S_AXI_AWPROT[N*3 +: 3]), + // + .i_axi_wvalid(S_AXI_WVALID[N]), + .i_axi_wready(S_AXI_WREADY[N]), + .i_axi_wdata( S_AXI_WDATA[N*DW +: DW]), + .i_axi_wstrb( S_AXI_WSTRB[N*DW/8 +: DW/8]), + // + .i_axi_bvalid(S_AXI_BVALID[N]), + .i_axi_bready(S_AXI_BREADY[N]), + .i_axi_bresp( S_AXI_BRESP[N*2 +: 2]), + // + .i_axi_arvalid(S_AXI_ARVALID[N]), + .i_axi_arready(S_AXI_ARREADY[N]), + .i_axi_araddr( S_AXI_ARADDR[N*AW +: AW]), + .i_axi_arprot( S_AXI_ARPROT[N*3 +: 3]), + // + // + .i_axi_rvalid(S_AXI_RVALID[N]), + .i_axi_rready(S_AXI_RREADY[N]), + .i_axi_rdata( S_AXI_RDATA[N*DW +: DW]), + .i_axi_rresp( S_AXI_RRESP[N*2 +: 2]), + // + .f_axi_rd_outstanding( fm_rd_outstanding[N]), + .f_axi_wr_outstanding( fm_wr_outstanding[N]), + .f_axi_awr_outstanding(fm_awr_outstanding[N])); + + // + // Check write counters + // + always @(*) + if (S_AXI_ARESETN) + assert(fm_awr_outstanding[N] == { 1'b0, w_sawpending[N] } + +((OPT_BUFFER_DECODER & dcd_awvalid[N]) ? 1:0) + + (S_AXI_AWREADY[N] ? 0:1)); + + always @(*) + if (S_AXI_ARESETN) + assert(fm_wr_outstanding[N] == { 1'b0, w_swpending[N] } + + (S_AXI_WREADY[N] ? 0:1)); + + always @(*) + if (S_AXI_ARESETN) + assert(fm_awr_outstanding[N] >= + (S_AXI_AWREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER & dcd_awvalid[N]) ? 1:0) + + (S_AXI_BVALID[N] ? 1:0)); + + always @(*) + if (S_AXI_ARESETN) + assert(fm_wr_outstanding[N] >= + (S_AXI_WREADY[N] ? 0:1) + + (S_AXI_BVALID[N]? 1:0)); + + always @(*) + if (S_AXI_ARESETN) + assert(fm_wr_outstanding[N]-(S_AXI_WREADY[N] ? 0:1) + <= fm_awr_outstanding[N]-(S_AXI_AWREADY[N] ? 0:1)); + + always @(*) + if (S_AXI_ARESETN && wgrant[N][NS]) + assert(fm_wr_outstanding[N] == (S_AXI_WREADY[N] ? 0:1) + + (S_AXI_BVALID[N] ? 1:0)); + + always @(*) + if (S_AXI_ARESETN && !swgrant[N]) + begin + assert(!S_AXI_BVALID[N]); + + assert(fm_awr_outstanding[N]==(S_AXI_AWREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER & dcd_awvalid[N]) ? 1:0)); + assert(fm_wr_outstanding[N] == (S_AXI_WREADY[N] ? 0:1)); + assert(w_sawpending[N] == 0); + assert(w_swpending[N] == 0); + end + + + // + // Check read counters + // + always @(*) + if (S_AXI_ARESETN) + assert(fm_rd_outstanding[N] >= + (S_AXI_ARREADY[N] ? 0:1) + +(S_AXI_RVALID[N] ? 1:0)); + + always @(*) + if (S_AXI_ARESETN && (!srgrant[N] || rgrant[N][NS])) + assert(fm_rd_outstanding[N] == + (S_AXI_ARREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER & dcd_arvalid[N]) ? 1:0) + +(S_AXI_RVALID[N] ? 1:0)); + + always @(*) + if (S_AXI_ARESETN) + assert(fm_rd_outstanding[N] == { 1'b0, w_srpending[N] } + +((OPT_BUFFER_DECODER & dcd_arvalid[N]) ? 1:0) + + (S_AXI_ARREADY[N] ? 0:1)); + + always @(*) + if (S_AXI_ARESETN && rgrant[N][NS]) + assert(fm_rd_outstanding[N] == (S_AXI_ARREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER & dcd_arvalid[N]) ? 1:0) + +(S_AXI_RVALID[N] ? 1:0)); + + always @(*) + if (S_AXI_ARESETN && !srgrant[N]) + begin + assert(!S_AXI_RVALID[N]); + assert(fm_rd_outstanding[N]== (S_AXI_ARREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER && dcd_arvalid[N])? 1:0)); + assert(w_srpending[N] == 0); + end + + // + // Check full/empty flags + // + localparam [LGMAXBURST-1:0] NEAR_THRESHOLD = -2; + + always @(*) + begin + assert(swfull[N] == &w_sawpending[N]); + assert(swempty[N] == (w_sawpending[N] == 0)); + end + + always @(*) + begin + assert(srfull[N] == &w_srpending[N]); + assert(srempty[N] == (w_srpending[N] == 0)); + end + // }}} + end endgenerate + + generate for(M=0; M<NS; M=M+1) + begin : CHECK_SLAVES + // {{{ + faxil_master #( + .C_AXI_DATA_WIDTH(DW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_ASSUME_RESET(1'b1), + .F_AXI_MAXRSTALL(0), + .F_LGDEPTH(F_LGDEPTH)) + slvi(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID[M]), + .i_axi_awready(M_AXI_AWREADY[M]), + .i_axi_awaddr(M_AXI_AWADDR[M*AW +: AW]), + .i_axi_awprot(M_AXI_AWPROT[M*3 +: 3]), + // + .i_axi_wvalid(M_AXI_WVALID[M]), + .i_axi_wready(M_AXI_WREADY[M]), + .i_axi_wdata( M_AXI_WDATA[M*DW +: DW]), + .i_axi_wstrb( M_AXI_WSTRB[M*DW/8 +: DW/8]), + // + .i_axi_bvalid(M_AXI_BVALID[M]), + .i_axi_bready(M_AXI_BREADY[M]), + .i_axi_bresp( M_AXI_BRESP[M*2 +: 2]), + // + .i_axi_arvalid(M_AXI_ARVALID[M]), + .i_axi_arready(M_AXI_ARREADY[M]), + .i_axi_araddr( M_AXI_ARADDR[M*AW +: AW]), + .i_axi_arprot( M_AXI_ARPROT[M*3 +: 3]), + // + // + .i_axi_rvalid(M_AXI_RVALID[M]), + .i_axi_rready(M_AXI_RREADY[M]), + .i_axi_rdata( M_AXI_RDATA[M*DW +: DW]), + .i_axi_rresp( M_AXI_RRESP[M*2 +: 2]), + // + .f_axi_rd_outstanding( fs_rd_outstanding[M]), + .f_axi_wr_outstanding( fs_wr_outstanding[M]), + .f_axi_awr_outstanding(fs_awr_outstanding[M])); + + always @(*) + assert(fs_wr_outstanding[M] + (M_AXI_WVALID[M] ? 1:0) + <= fs_awr_outstanding[M] + (M_AXI_AWVALID[M]? 1:0)); + + always @(*) + if (!mwgrant[M]) + begin + assert(fs_awr_outstanding[M] == 0); + assert(fs_wr_outstanding[M] == 0); + end + + always @(*) + if (!mrgrant[M]) + assert(fs_rd_outstanding[M] == 0); + + always @(*) + assert(fs_awr_outstanding[M] < { 1'b1, {(F_LGDEPTH-1){1'b0}} }); + always @(*) + assert(fs_wr_outstanding[M] < { 1'b1, {(F_LGDEPTH-1){1'b0}} }); + always @(*) + assert(fs_rd_outstanding[M] < { 1'b1, {(F_LGDEPTH-1){1'b0}} }); + + always @(*) + if (M_AXI_AWVALID[M]) + assert(((M_AXI_AWADDR[M*AW +: AW] + ^ SLAVE_ADDR[M*AW +: AW]) + & SLAVE_MASK[M*AW +: AW]) == 0); + + always @(*) + if (M_AXI_ARVALID[M]) + assert(((M_AXI_ARADDR[M*AW +: AW] + ^ SLAVE_ADDR[M*AW +: AW]) + & SLAVE_MASK[M*AW +: AW]) == 0); + // }}} + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin : CORRELATE_OUTSTANDING + // {{{ + always @(*) + if (S_AXI_ARESETN && (swgrant[N] && (swindex[N] < NS))) + begin + assert((fm_awr_outstanding[N] + - (S_AXI_AWREADY[N] ? 0:1) + -((OPT_BUFFER_DECODER && dcd_awvalid[N]) ? 1:0) + - (S_AXI_BVALID[N] ? 1:0)) + == (fs_awr_outstanding[swindex[N]] + + (m_axi_awvalid[swindex[N]] ? 1:0) + + (m_axi_bready[swindex[N]] ? 0:1))); + + assert((fm_wr_outstanding[N] + - (S_AXI_WREADY[N] ? 0:1) + - (S_AXI_BVALID[N] ? 1:0)) + == (fs_wr_outstanding[swindex[N]] + + (m_axi_wvalid[swindex[N]] ? 1:0) + + (m_axi_bready[swindex[N]] ? 0:1))); + + end else if (S_AXI_ARESETN && (!swgrant[N] || (swindex[N]==NS))) + begin + if (!swgrant[N]) + assert(fm_awr_outstanding[N] == + (S_AXI_AWREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER && dcd_awvalid[N]) ? 1:0) + +(S_AXI_BVALID[N] ? 1:0)); + else + assert(fm_awr_outstanding[N] >= + (S_AXI_AWREADY[N] ? 0:1) + +((OPT_BUFFER_DECODER && dcd_awvalid[N]) ? 1:0) + +(S_AXI_BVALID[N] ? 1:0)); + + assert(fm_wr_outstanding[N] == + (S_AXI_WREADY[N] ? 0:1) + +(S_AXI_BVALID[N] ? 1:0)); + end + + always @(*) + if (srgrant[N] && (srindex[N] < NS)) + begin + assert((fm_rd_outstanding[N]//17 + - (S_AXI_ARREADY[N] ? 0:1)//1 + -((OPT_BUFFER_DECODER && dcd_arvalid[N]) ? 1:0) + - (S_AXI_RVALID[N] ? 1:0))//0 + == (fs_rd_outstanding[srindex[N]]//16 + + (m_axi_arvalid[srindex[N]] ? 1:0)//0 + + (m_axi_rready[srindex[N]] ? 0:1)));//0 + end + // }}} + end endgenerate + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // Can every master reach every slave? + // Can things transition without dropping the request line(s)? + generate for(N=0; N<NM; N=N+1) + begin : COVER_CONNECTIVITY_FROM_MASTER + reg [3:0] cvr_w_returns, cvr_r_returns; + reg err_wr_return, err_rd_return; + reg [NS-1:0] cvr_w_every, cvr_r_every; + reg cvr_was_wevery, cvr_was_revery, + cvr_whsreturn, cvr_rhsreturn; + + // cvr_w_returns is a speed check: Can we return one write + // acknowledgement per clock cycle? + initial cvr_w_returns = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_w_returns = 0; + else begin + cvr_w_returns <= { cvr_w_returns[2:0], 1'b0 }; + if (S_AXI_BVALID[N] && S_AXI_BREADY[N] && !wgrant[N][NS]) + cvr_w_returns[0] <= 1'b1; + end + + initial cvr_whsreturn = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_whsreturn <= 0; + else + cvr_whsreturn <= cvr_whsreturn || (&cvr_w_returns); + + // w_every is a connectivity test: Can we get a return from + // every slave? + initial cvr_w_every = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_w_every <= 0; + else if (!S_AXI_AWVALID[N]) + cvr_w_every <= 0; + else begin + if (S_AXI_BVALID[N] && S_AXI_BREADY[N] && !wgrant[N][NS]) + cvr_w_every[swindex[N]] <= 1'b1; + end + + always @(posedge S_AXI_ACLK) + if (S_AXI_BVALID[N]) + assert($stable(swindex[N])); + + initial cvr_was_wevery = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_was_wevery <= 0; + else + cvr_was_wevery <= cvr_was_wevery || (&cvr_w_every); + + // err_wr_return is a test to make certain we can return a + // bus error on the write channel. + initial err_wr_return = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + err_wr_return = 0; + else if (wgrant[N][NS] && S_AXI_BVALID[N] + && (S_AXI_BRESP[2*N+:2]==INTERCONNECT_ERROR)) + err_wr_return = 1; + +`ifndef VERILATOR + always @(*) + cover(!swgrant[N] && cvr_whsreturn); + always @(*) + cover(!swgrant[N] && cvr_was_wevery); + + always @(*) + cover(S_AXI_ARESETN && wrequest[N][NS]); + always @(*) + cover(S_AXI_ARESETN && wrequest[N][NS] && slave_awaccepts[N]); + always @(*) + cover(err_wr_return); + always @(*) + cover(!swgrant[N] && err_wr_return); +`endif + + always @(*) + if (S_AXI_BVALID[N]) + assert(swgrant[N]); + + // cvr_r_returns is a speed check: Can we return one read + // acknowledgment per clock cycle? + initial cvr_r_returns = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_r_returns = 0; + else begin + cvr_r_returns <= { cvr_r_returns[2:0], 1'b0 }; + if (S_AXI_RVALID[N] && S_AXI_RREADY[N]) + cvr_r_returns[0] <= 1'b1; + end + + initial cvr_rhsreturn = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_rhsreturn <= 0; + else + cvr_rhsreturn <= cvr_rhsreturn || (&cvr_r_returns); + + + // r_every is a connectivity test: Can we get a read return from + // every slave? + initial cvr_r_every = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_r_every = 0; + else if (!S_AXI_ARVALID[N]) + cvr_r_every = 0; + else begin + if (S_AXI_RVALID[N] && S_AXI_RREADY[N]) + cvr_r_every[srindex[N]] <= 1'b1; + end + + // cvr_was_revery is a return to idle check following the + // connectivity test. Since the connectivity test is cleared + // if there's ever a drop in the valid line, we need a separate + // wire to check that this master can return to idle again. + initial cvr_was_revery = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_was_revery <= 0; + else + cvr_was_revery <= cvr_was_revery || (&cvr_r_every); + + always @(posedge S_AXI_ACLK) + if (S_AXI_RVALID[N]) + assert($stable(srindex[N])); + + initial err_rd_return = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + err_rd_return = 0; + else if (rgrant[N][NS] && S_AXI_RVALID[N] + && (S_AXI_RRESP[2*N+:2]==INTERCONNECT_ERROR)) + err_rd_return = 1; + +`ifndef VERILATOR + always @(*) + cover(!srgrant[N] && cvr_rhsreturn); // @26 + always @(*) + cover(!srgrant[N] && cvr_was_revery); // @26 + + always @(*) + cover(S_AXI_ARVALID[N] && rrequest[N][NS]); + always @(*) + cover(rgrant[N][NS]); + always @(*) + cover(err_rd_return); + always @(*) + cover(!srgrant[N] && err_rd_return); //@! +`endif + + always @(*) + if (S_AXI_BVALID[N] && wgrant[N][NS]) + assert(S_AXI_BRESP[2*N+:2]==INTERCONNECT_ERROR); + always @(*) + if (S_AXI_RVALID[N] && rgrant[N][NS]) + assert(S_AXI_RRESP[2*N+:2]==INTERCONNECT_ERROR); + end endgenerate + + reg cvr_multi_write_hit, cvr_multi_read_hit; + + initial cvr_multi_write_hit = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_multi_write_hit <= 0; + else if (fm_awr_outstanding[0] > 2 && !wgrant[0][NS]) + cvr_multi_write_hit <= 1; + + initial cvr_multi_read_hit = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_multi_read_hit <= 0; + else if (fm_rd_outstanding[0] > 2 && !rgrant[0][NS]) + cvr_multi_read_hit <= 1; + + always @(*) + cover(cvr_multi_write_hit); + + always @(*) + cover(cvr_multi_read_hit); + + always @(*) + cover(S_AXI_ARESETN && cvr_multi_write_hit & mwgrant == 0 && M_AXI_BVALID == 0); + + always @(*) + cover(S_AXI_ARESETN && cvr_multi_read_hit & mrgrant == 0 && M_AXI_RVALID == 0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Negation check + // {{{ + // Pick a particular value. Assume the value doesn't show up on the + // input. Prove it doesn't show up on the output. This will check for + // ... + // 1. Stuck bits on the output channel + // 2. Cross-talk between channels + // + //////////////////////////////////////////////////////////////////////// + // + // + (* anyconst *) reg [LGNM-1:0] f_const_source; + (* anyconst *) reg [AW-1:0] f_const_addr; + (* anyconst *) reg [AW-1:0] f_const_addr_n; + (* anyconst *) reg [DW-1:0] f_const_data_n; + (* anyconst *) reg [DW/8-1:0] f_const_strb_n; + (* anyconst *) reg [3-1:0] f_const_prot_n; + (* anyconst *) reg [2-1:0] f_const_resp_n; + reg [LGNS-1:0] f_const_slave; + + always @(*) + assume(f_const_source < NM); + always @(*) + begin + f_const_slave = NS; + for(iM=0; iM<NS; iM=iM+1) + begin + if (((f_const_addr ^ SLAVE_ADDR[iM*AW+:AW]) + &SLAVE_MASK[iM*AW+:AW])==0) + f_const_slave = iM; + end + + assume(f_const_slave < NS); + end + + reg [AW-1:0] f_awaddr; + reg [AW-1:0] f_araddr; + always @(*) + f_awaddr = S_AXI_AWADDR[f_const_source * AW +: AW]; + always @(*) + f_araddr = S_AXI_ARADDR[f_const_source * AW +: AW]; + + // The assumption check: assume our negated values are not found on + // the inputs + always @(*) + begin + if (S_AXI_AWVALID[f_const_source]) + begin + assume(f_awaddr != f_const_addr_n); + assume(S_AXI_AWPROT[f_const_source*3+:3] != f_const_prot_n); + end + if (m_wvalid) + begin + assume(m_wdata[f_const_source] != f_const_data_n); + assume(m_wstrb[f_const_source] != f_const_strb_n); + end + if (S_AXI_ARVALID[f_const_source]) + begin + assume(f_araddr != f_const_addr_n); + assume(S_AXI_ARPROT[f_const_source*3+:3] != f_const_prot_n); + end + + if (M_AXI_BVALID[f_const_slave] && wgrant[f_const_source][f_const_slave]) + begin + assume(m_axi_bresp[f_const_slave] != f_const_resp_n); + end + + if (M_AXI_RVALID[f_const_slave] && rgrant[f_const_source][f_const_slave]) + begin + assume(m_axi_rdata[f_const_slave] != f_const_data_n); + assume(m_axi_rresp[f_const_slave] != f_const_resp_n); + end + end + + // Proof check: Prove these values are not found on our outputs + always @(*) + begin + if (skd_awvalid[f_const_source]) + begin + assert(skd_awaddr[f_const_source] != f_const_addr_n); + assert(skd_awprot[f_const_source] != f_const_prot_n); + end + if (dcd_awvalid[f_const_source]) + begin + assert(m_awaddr[f_const_source] != f_const_addr_n); + assert(m_awprot[f_const_source] != f_const_prot_n); + end + if (M_AXI_AWVALID[f_const_slave] && wgrant[f_const_source][f_const_slave]) + begin + assert(M_AXI_AWADDR[f_const_slave*AW+:AW] != f_const_addr_n); + assert(M_AXI_AWPROT[f_const_slave*3+:3] != f_const_prot_n); + end + if (M_AXI_WVALID[f_const_slave] && wgrant[f_const_source][f_const_slave]) + begin + assert(M_AXI_WDATA[f_const_slave*DW+:DW] != f_const_data_n); + assert(M_AXI_WSTRB[f_const_slave*(DW/8)+:(DW/8)] != f_const_strb_n); + end + if (skd_arvalid[f_const_source]) + begin + assert(skd_araddr[f_const_source] != f_const_addr_n); + assert(skd_arprot[f_const_source] != f_const_prot_n); + end + if (dcd_arvalid[f_const_source]) + begin + assert(m_araddr[f_const_source] != f_const_addr_n); + assert(m_arprot[f_const_source] != f_const_prot_n); + end + if (M_AXI_ARVALID[f_const_slave] && rgrant[f_const_source][f_const_slave]) + begin + assert(M_AXI_ARADDR[f_const_slave*AW+:AW] != f_const_addr_n); + assert(M_AXI_ARPROT[f_const_slave*3+:3] != f_const_prot_n); + end + // + if (r_bvalid[f_const_source] && wgrant[f_const_source][f_const_slave]) + assert(r_bresp[f_const_source] != f_const_resp_n); + if (S_AXI_BVALID[f_const_source] && wgrant[f_const_source][f_const_slave]) + assert(S_AXI_BRESP[f_const_source*2+:2] != f_const_resp_n); + if (r_rvalid[f_const_source] && rgrant[f_const_source][f_const_slave]) + begin + assert(r_rresp[f_const_source] != f_const_resp_n); + assert(r_rdata[f_const_source] != f_const_data_n); + end + if (S_AXI_RVALID[f_const_source] && rgrant[f_const_source][f_const_slave]) + begin + assert(S_AXI_RRESP[f_const_source*2+:2]!=f_const_resp_n); + assert(S_AXI_RDATA[f_const_source*DW+:DW]!=f_const_data_n); + end + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // (Careless) constraining assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate for(N=0; N<NM; N=N+1) + begin + + end endgenerate + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axim2wbsp.v b/rtl/wb2axip/axim2wbsp.v new file mode 100644 index 0000000..fb58458 --- /dev/null +++ b/rtl/wb2axip/axim2wbsp.v @@ -0,0 +1,317 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axim2wbsp.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: So ... this converter works in the other direction from +// wbm2axisp. This converter takes AXI commands, and organizes +// them into pipelined wishbone commands. +// +// This particular core treats AXI as two separate buses: one for writes, +// and the other for reads. This particular core combines the two channels +// into one. The designer should be aware that the two AXI buses turned +// Wishbone buses can be kept separate as separate inputs to a WB crosssbar +// for better performance in some circumstances. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2016-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axim2wbsp #( + // {{{ + parameter C_AXI_ID_WIDTH = 2, // The AXI id width used for R&W + // This is an int between 1-16 + parameter C_AXI_DATA_WIDTH = 32,// Width of the AXI R&W data + parameter C_AXI_ADDR_WIDTH = 28, // AXI Address width + localparam AXI_LSBS = $clog2(C_AXI_DATA_WIDTH)-3, + localparam DW = C_AXI_DATA_WIDTH, + localparam AW = C_AXI_ADDR_WIDTH - AXI_LSBS, + parameter LGFIFO = 5, + parameter [0:0] OPT_SWAP_ENDIANNESS = 1'b0, + parameter [0:0] OPT_READONLY = 1'b0, + parameter [0:0] OPT_WRITEONLY = 1'b0 + // }}} + ) ( + // {{{ + // + input wire S_AXI_ACLK, // System clock + input wire S_AXI_ARESETN, + + // AXI write address channel signals + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [7:0] S_AXI_AWLEN, + input wire [2:0] S_AXI_AWSIZE, + input wire [1:0] S_AXI_AWBURST, + input wire [0:0] S_AXI_AWLOCK, + input wire [3:0] S_AXI_AWCACHE, + input wire [2:0] S_AXI_AWPROT, + input wire [3:0] S_AXI_AWQOS, + // }}} + // AXI write data channel signals + // {{{ + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + // }}} + // AXI write response channel signals + // {{{ + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // }}} + // AXI read address channel signals + // {{{ + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [7:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire [0:0] S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + // }}} + // AXI read data channel signals + // {{{ + output wire S_AXI_RVALID, // Rd rslt valid + input wire S_AXI_RREADY, // Rd rslt ready + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, // Response ID + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,// Read data + output wire S_AXI_RLAST, // Read last + output wire [1:0] S_AXI_RRESP, // Read response + // }}} + // We'll share the clock and the reset + // {{{ + output wire o_reset, + output wire o_wb_cyc, + output wire o_wb_stb, + output wire o_wb_we, + output wire [(AW-1):0] o_wb_addr, + output wire [(C_AXI_DATA_WIDTH-1):0] o_wb_data, + output wire [(C_AXI_DATA_WIDTH/8-1):0] o_wb_sel, + input wire i_wb_stall, + input wire i_wb_ack, + input wire [(C_AXI_DATA_WIDTH-1):0] i_wb_data, + input wire i_wb_err + // }}} + // }}} + ); + // + // + // + + + wire [(AW-1):0] w_wb_addr, r_wb_addr; + wire [(C_AXI_DATA_WIDTH-1):0] w_wb_data; + wire [(C_AXI_DATA_WIDTH/8-1):0] w_wb_sel, r_wb_sel; + wire r_wb_err, r_wb_cyc, r_wb_stb, r_wb_stall, r_wb_ack; + wire w_wb_err, w_wb_cyc, w_wb_stb, w_wb_stall, w_wb_ack; + wire r_wb_we, w_wb_we; + + assign r_wb_we = 1'b0; + assign w_wb_we = 1'b1; + + generate if (!OPT_READONLY) + begin : AXI_WR + // {{{ + aximwr2wbsp #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .OPT_SWAP_ENDIANNESS(OPT_SWAP_ENDIANNESS), + .LGFIFO(LGFIFO) + // }}} + ) axi_write_decoder( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), + // + .S_AXI_AWVALID(S_AXI_AWVALID), + .S_AXI_AWREADY(S_AXI_AWREADY), + .S_AXI_AWID( S_AXI_AWID), + .S_AXI_AWADDR( S_AXI_AWADDR), + .S_AXI_AWLEN( S_AXI_AWLEN), + .S_AXI_AWSIZE( S_AXI_AWSIZE), + .S_AXI_AWBURST(S_AXI_AWBURST), + .S_AXI_AWLOCK( S_AXI_AWLOCK), + .S_AXI_AWCACHE(S_AXI_AWCACHE), + .S_AXI_AWPROT( S_AXI_AWPROT), + .S_AXI_AWQOS( S_AXI_AWQOS), + // + .S_AXI_WVALID( S_AXI_WVALID), + .S_AXI_WREADY( S_AXI_WREADY), + .S_AXI_WDATA( S_AXI_WDATA), + .S_AXI_WSTRB( S_AXI_WSTRB), + .S_AXI_WLAST( S_AXI_WLAST), + // + .S_AXI_BVALID(S_AXI_BVALID), + .S_AXI_BREADY(S_AXI_BREADY), + .S_AXI_BID( S_AXI_BID), + .S_AXI_BRESP( S_AXI_BRESP), + // + .o_wb_cyc( w_wb_cyc), + .o_wb_stb( w_wb_stb), + .o_wb_addr( w_wb_addr), + .o_wb_data( w_wb_data), + .o_wb_sel( w_wb_sel), + .i_wb_ack( w_wb_ack), + .i_wb_stall(w_wb_stall), + .i_wb_err( w_wb_err) + // }}} + ); + // }}} + end else begin : NO_WRITE_CHANNEL + // {{{ + assign w_wb_cyc = 0; + assign w_wb_stb = 0; + assign w_wb_addr = 0; + assign w_wb_data = 0; + assign w_wb_sel = 0; + assign S_AXI_AWREADY = 0; + assign S_AXI_WREADY = 0; + assign S_AXI_BVALID = 0; + assign S_AXI_BRESP = 2'b11; + assign S_AXI_BID = 0; + // }}} + end endgenerate + + generate if (!OPT_WRITEONLY) + begin : AXI_RD + // {{{ + aximrd2wbsp #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .OPT_SWAP_ENDIANNESS(OPT_SWAP_ENDIANNESS), + .LGFIFO(LGFIFO) + // }}} + ) axi_read_decoder( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), .S_AXI_ARESETN(S_AXI_ARESETN), + // + .S_AXI_ARVALID(S_AXI_ARVALID), + .S_AXI_ARREADY(S_AXI_ARREADY), + .S_AXI_ARID( S_AXI_ARID), + .S_AXI_ARADDR( S_AXI_ARADDR), + .S_AXI_ARLEN( S_AXI_ARLEN), + .S_AXI_ARSIZE( S_AXI_ARSIZE), + .S_AXI_ARBURST(S_AXI_ARBURST), + .S_AXI_ARLOCK( S_AXI_ARLOCK), + .S_AXI_ARCACHE(S_AXI_ARCACHE), + .S_AXI_ARPROT( S_AXI_ARPROT), + .S_AXI_ARQOS( S_AXI_ARQOS), + // + .S_AXI_RVALID(S_AXI_RVALID), + .S_AXI_RREADY(S_AXI_RREADY), + .S_AXI_RID( S_AXI_RID), + .S_AXI_RDATA( S_AXI_RDATA), + .S_AXI_RLAST( S_AXI_RLAST), + .S_AXI_RRESP( S_AXI_RRESP), + // + .o_wb_cyc( r_wb_cyc), + .o_wb_stb( r_wb_stb), + .o_wb_addr( r_wb_addr), + .o_wb_sel( r_wb_sel), + .i_wb_ack( r_wb_ack), + .i_wb_stall(r_wb_stall), + .i_wb_data( i_wb_data), + .i_wb_err( r_wb_err) + // }}} + ); + // }}} + end else begin : NO_READ_CHANNEL + // {{{ + assign r_wb_cyc = 0; + assign r_wb_stb = 0; + assign r_wb_addr = 0; + // + assign S_AXI_ARREADY = 0; + assign S_AXI_RVALID = 0; + assign S_AXI_RID = 0; + assign S_AXI_RDATA = 0; + assign S_AXI_RLAST = 0; + assign S_AXI_RRESP = 0; + // }}} + end endgenerate + + generate if (OPT_READONLY) + begin : ARB_RD + // {{{ + assign o_wb_cyc = r_wb_cyc; + assign o_wb_stb = r_wb_stb; + assign o_wb_we = r_wb_we; + assign o_wb_addr = r_wb_addr; + assign o_wb_data = 0; + assign o_wb_sel = r_wb_sel; + assign r_wb_ack = i_wb_ack; + assign r_wb_stall= i_wb_stall; + assign r_wb_ack = i_wb_ack; + assign r_wb_err = i_wb_err; + // }}} + end else if (OPT_WRITEONLY) + begin : ARB_WR + // {{{ + assign o_wb_cyc = w_wb_cyc; + assign o_wb_stb = w_wb_stb; + assign o_wb_we = w_wb_we; + assign o_wb_addr = w_wb_addr; + assign o_wb_data = w_wb_data; + assign o_wb_sel = w_wb_sel; + assign w_wb_ack = i_wb_ack; + assign w_wb_stall= i_wb_stall; + assign w_wb_ack = i_wb_ack; + assign w_wb_err = i_wb_err; + // }}} + end else begin : ARB_WB + // {{{ + wbarbiter #(.DW(DW), .AW(AW)) + readorwrite(S_AXI_ACLK, o_reset, + r_wb_cyc, r_wb_stb, r_wb_we, r_wb_addr, w_wb_data, r_wb_sel, + r_wb_ack, r_wb_stall, r_wb_err, + w_wb_cyc, w_wb_stb, w_wb_we, w_wb_addr, w_wb_data, w_wb_sel, + w_wb_ack, w_wb_stall, w_wb_err, + o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data, o_wb_sel, + i_wb_ack, i_wb_stall, i_wb_err + ); + // }}} + end endgenerate + + assign o_reset = (S_AXI_ARESETN == 1'b0); + +`ifdef FORMAL +`endif +endmodule diff --git a/rtl/wb2axip/aximm2s.v b/rtl/wb2axip/aximm2s.v new file mode 100644 index 0000000..b4c1056 --- /dev/null +++ b/rtl/wb2axip/aximm2s.v @@ -0,0 +1,2158 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: aximm2s +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Converts an AXI (full) memory port to an AXI-stream +// interface. +// // {{{ +// While I am aware that other vendors sell similar components, if you +// look under the hood you'll find no relation to anything but my own +// work here. +// +// Registers: +// +// 0: CMD_CONTROL +// Controls the transaction via either starting or aborting an +// ongoing transaction, provides feedback regarding the current +// status. +// +// [31] r_busy +// True if the core is in the middle of a transaction +// +// [30] r_err +// True if the core has detected an error, a bus error +// while the FIFO is reading. +// +// Writing a '1' to this bit while the core is idle will clear it. +// New transfers will not start until this bit is cleared. +// +// [29] r_complete +// True if the transaction has completed, whether normally or +// abnormally (error or abort). +// +// Any write to the CMD_CONTROL register will clear this flag. +// +// [28] r_continuous +// Normally the FIFO gets cleared and reset between operations. +// However, if you set r_continuous, the core will then expect +// a second operation to take place following the first one. +// In this case, the operation will complete but the FIFO won't +// get cleared. During this time, the FIFO will not fill further. +// +// Any write to the CMD_CONTROL register while the core is not +// busy will adjust this bit. +// +// [27] !r_increment +// +// If clear, the core reads from subsequent and incrementing +// addresses. If set, the core reads from the same address +// throughout a transaction. +// +// Writes to CMD_CONTROL while the core is idle will adjust this +// bit. +// +// [20:16] LGFIFO +// These are read-only bits, returning the size of the FIFO. +// +// ABORT +// If the core is busy, and ABORT_KEY (currently set to 8'h6d +// below) is written to the top 8-bits of this register, +// the current transfer will be aborted. Any pending reads +// will be completed, but nothing more will be written to the +// stream. +// +// Alternatively, the core will enter into an abort state +// following any returned bus error indications. +// +// 4: (Unused and reserved) +// +// 8-c: CMD_ADDRLO, CMD_ADDR_HI +// [C_AXI_ADDR_WIDTH-1:($clog2(C_AXI_DATA_WIDTH)-3)] +// If idle, the address the core will read from when it starts. +// If busy, the address the core is currently reading from. +// Upon completion, the address either returns to the starting +// address (if r_continuous is clear), or otherwise becomes the +// address where the core left off. In the case of an abort or an +// error, this will be (near) the address that was last read. +// +// Why "near"? Because this address records the reads that have +// been issued while no error is pending. If a bus error return +// comes back, there may have been several more reads issued before +// that error address. +// +// 10-14: (Unused and reserved) +// +// 18-1c: CMD_LENLO, CMD_LENHI +// [LGLEN-1:0] +// The size of the transfer in bytes. Only accepts aligned +// addresses, therefore bits [($clog2(C_AXI_DATA_WIDTH)-3):0] +// will always be forced to zero. To find out what size bus +// this core is conencted to, or the maximum transfer length, +// write a -1 to this value and read the returning result. +// Only the active bits will be set. +// +// While the core is busy, reads from this address will return +// the number of items still to be read from the bus. +// +// I hope to eventually add support for unaligned bursts. Such +// support is not currently part of this core. +// +// }}} +// +// Status: +// {{{ +// 1. The core passes both cover checks and formal property (assertion) +// based checks. It has not (yet) been tested in real hardware. +// +// 2. I'd like to support unaligned addresses and lengths. This will +// require aligning the data coming out of the FIFO as well. +// As written, the core doesn't yet support these. +// +// }}} +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module aximm2s #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ID_WIDTH = 1, + // + // We support five 32-bit AXI-lite registers, requiring 5-bits + // of AXI-lite addressing + localparam C_AXIL_ADDR_WIDTH = 5, + localparam C_AXIL_DATA_WIDTH = 32, + // + // The bottom ADDRLSB bits of any AXI address are subword bits + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3, + localparam AXILLSB = $clog2(C_AXIL_DATA_WIDTH)-3, + // + // OPT_UNALIGNED: Allow unaligned accesses, address requests + // and sizes which may or may not match the underlying data + // width. If set, the core will quietly align these requests. + parameter [0:0] OPT_UNALIGNED = 1'b0, + // + // OPT_TKEEP [Future]: If set, will also add TKEEP signals to + // the outgoing slave interface. This is necessary if ever you + // wish to output partial stream words, such as might happen if + // the length were ever something other than a full number of + // words. (Not yet implemented) + // parameter [0:0] OPT_TKEEP = 1'b0, + // + // OPT_TLAST: If enabled, will embed TLAST=1 at the end of every + // commanded burst. If disabled, TLAST will be set to a + // constant 1'b1. + parameter [0:0] OPT_TLAST = 1'b0, + // + parameter [0:0] OPT_LOWPOWER = 1'b0, + parameter [0:0] OPT_CLKGATE = OPT_LOWPOWER, + // + // ABORT_KEY is the value that, when written to the top 8-bits + // of the control word, will abort any ongoing operation. + parameter [7:0] ABORT_KEY = 8'h6d, + // + // The size of the FIFO + parameter LGFIFO = 9, + // + // Maximum number of bytes that can ever be transferred, in + // log-base 2 + parameter LGLEN = 20, + // + // AXI_ID is the ID we will use for all of our AXI transactions + parameter AXI_ID = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The stream interface + // {{{ + output wire M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXIS_TDATA, + output wire M_AXIS_TLAST, + // }}} + // + // The control interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // + + // + // The AXI (full) read interface + // {{{ + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [7:0] M_AXI_ARLEN, + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [1:0] M_AXI_RRESP, + // }}} + // + // + // Create an output signal to indicate that we've finished + output reg o_int + // }}} + ); + + // Local parameter declarations + // {{{ + localparam [2:0] CMD_CONTROL = 3'b000, + // CMD_UNUSED_1 = 3'b001, + CMD_ADDRLO = 3'b010, + CMD_ADDRHI = 3'b011, + // CMD_UNUSED_2 = 3'b100, + // CMD_UNUSED_3 = 3'b101, + CMD_LENLO = 3'b110, + CMD_LENHI = 3'b111; + localparam CBIT_BUSY = 31, + CBIT_ERR = 30, + CBIT_COMPLETE = 29, + CBIT_CONTINUOUS = 28, + CBIT_INCREMENT = 27; + localparam TMP_LGMAXBURST=(LGFIFO > 8) ? 8 : LGFIFO-1; + localparam LGMAXBURST = ((4096/(C_AXI_DATA_WIDTH/8)) + > (1<<TMP_LGMAXBURST)) + ? TMP_LGMAXBURST : $clog2(4096 * 8 / C_AXI_DATA_WIDTH); + localparam LGMAX_FIXED_BURST = (LGMAXBURST < 4) ? LGMAXBURST : 4, + MAX_FIXED_BURST = (1<<LGMAX_FIXED_BURST); + localparam LGLENW = LGLEN - ($clog2(C_AXI_DATA_WIDTH)-3), + LGLENWA = LGLENW + (OPT_UNALIGNED ? 1:0); + // localparam LGFIFOB = LGFIFO + ($clog2(C_AXI_DATA_WIDTH)-3); + // localparam [ADDRLSB-1:0] LSBZEROS = 0; + // }}} + + // Signal declarations + // {{{ + wire clk_active, gated_clk; + wire i_clk = gated_clk; + wire i_reset = !S_AXI_ARESETN; + + reg r_busy, r_err, r_complete, r_continuous, r_increment, + cmd_abort, zero_length, + w_cmd_start, w_complete, w_cmd_abort, r_pre_start; + // reg cmd_start; + reg axi_abort_pending; + + reg [LGLENWA-1:0] ar_requests_remaining, + ar_bursts_outstanding, + ar_next_remaining; + reg [LGMAXBURST:0] r_max_burst; + reg [C_AXI_ADDR_WIDTH-1:0] axi_raddr; + + reg [C_AXI_ADDR_WIDTH-1:0] cmd_addr; + reg [LGLENW-1:0] cmd_length_w; + reg [LGLENWA-1:0] cmd_length_aligned_w; + reg unaligned_cmd_addr; + + // FIFO signals + wire reset_fifo, write_to_fifo, + read_from_fifo; + wire [C_AXI_DATA_WIDTH-1:0] write_data; + wire [LGFIFO:0] fifo_fill; + wire fifo_full, fifo_empty; + + wire awskd_valid, axil_write_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + // + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire arskd_valid, axil_read_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + reg [C_AXIL_DATA_WIDTH-1:0] w_status_word; + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_address, wide_length, + new_wideaddr, new_widelen; + wire [C_AXIL_DATA_WIDTH-1:0] new_cmdaddrlo, new_cmdaddrhi, + new_lengthlo, new_lengthhi; + + + + reg axi_arvalid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [7:0] axi_arlen; + + // Speed up checking for zeros + reg ar_none_remaining, + ar_none_outstanding, + phantom_start, start_burst; + reg ar_multiple_full_bursts, + ar_multiple_fixed_bursts, + ar_multiple_bursts_remaining, + ar_needs_alignment; + wire partial_burst_requested; + reg [LGMAXBURST-1:0] addralign; + reg [LGFIFO:0] rd_uncommitted; + reg [LGMAXBURST:0] initial_burstlen; + reg [LGLENWA-1:0] rd_reads_remaining; + reg rd_none_remaining, + rd_last_remaining; + + wire realign_last_valid; +/* + wr_none_pending, r_none_remaining; + + reg w_phantom_start, phantom_start; + reg [LGFIFO:0] next_fill; +*/ + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // This is mostly the skidbuffer logic, and handling of the VALID + // and READY signals for the AXI-lite control logic in the next + // section. + // + + // + // Write signaling + // + // {{{ + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_ADDR_WIDTH-AXILLSB) + // }}} + ) axilawskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr) + // }}} + ); + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8) + // }}} + ) axilwskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb }) + // }}} + ); + + assign axil_write_ready = clk_active && awskd_valid && wskd_valid + && (!S_AXIL_BVALID || S_AXIL_BREADY); + + initial axil_bvalid = 0; + always @(posedge i_clk) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXIL_BREADY) + axil_bvalid <= 0; + + assign S_AXIL_BVALID = axil_bvalid; + assign S_AXIL_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_ADDR_WIDTH-AXILLSB) + // }}} + ) axilarskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr) + // }}} + ); + + assign axil_read_ready = clk_active && arskd_valid + && (!axil_read_valid || S_AXIL_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge i_clk) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXIL_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXIL_RVALID = axil_read_valid; + assign S_AXIL_RDATA = axil_read_data; + assign S_AXIL_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite controlled logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // w_cmd_abort, cmd_abort : Abort transaction + // {{{ + always @(*) + begin + w_cmd_abort = 0; + w_cmd_abort = (axil_write_ready && awskd_addr == CMD_CONTROL) + && (wskd_strb[3] && wskd_data[31:24] == ABORT_KEY); + if (!r_busy) + w_cmd_abort = 0; + end + + initial cmd_abort = 0; + always @(posedge i_clk) + if (i_reset) + cmd_abort <= 0; + else + cmd_abort <= (cmd_abort && r_busy)||w_cmd_abort; + // }}} + + // w_cmd_start: Start command + // {{{ + always @(*) + if (r_busy) + w_cmd_start = 0; + else begin + w_cmd_start = 0; + if ((axil_write_ready && awskd_addr == CMD_CONTROL) + && (wskd_strb[3] && wskd_data[CBIT_BUSY])) + w_cmd_start = 1; + if (r_err && !wskd_data[CBIT_ERR]) + w_cmd_start = 0; + if (zero_length) + w_cmd_start = 0; + if (OPT_UNALIGNED && unaligned_cmd_addr + && wskd_data[CBIT_INCREMENT]) + w_cmd_start = 0; + end + // }}} + + // r_busy, r_complete: Calculate busy or complete flags + // {{{ + initial r_busy = 0; + initial r_complete = 0; + always @(posedge i_clk) + if (i_reset) + begin + r_busy <= 0; + r_complete <= 0; + end else if (!r_busy) + begin + if (w_cmd_start) + r_busy <= 1'b1; + if (axil_write_ready && awskd_addr == CMD_CONTROL) + // Any write to the control register will clear the + // completion flag + r_complete <= 1'b0; + end else if (r_busy) + begin + if (w_complete) + begin + r_complete <= 1; + r_busy <= 1'b0; + end + end + // }}} + + // o_int: Interrupts + // {{{ + initial o_int = 0; + always @(posedge i_clk) + if (i_reset) + o_int <= 0; + else + o_int <= (r_busy && w_complete); + // }}} + + // r_err : Error conditions + // {{{ + always @(posedge i_clk) + if (i_reset) + r_err <= 0; + else if (!r_busy) + begin + if (axil_write_ready && awskd_addr == CMD_CONTROL) + begin + if (!w_cmd_abort) + r_err <= r_err && (!wskd_strb[3] || !wskd_data[CBIT_ERR]); + // On any request to start a transfer with an unaligned + // address, that's not incrementing--declare an + // immediate error + if (wskd_strb[3] && wskd_data[CBIT_BUSY] + && wskd_data[CBIT_INCREMENT] + && (OPT_UNALIGNED && unaligned_cmd_addr)) + r_err <= 1'b1; + end + end else // if (r_busy) + begin + if (M_AXI_RVALID && M_AXI_RREADY && M_AXI_RRESP[1]) + r_err <= 1'b1; + end + // }}} + + // r_continuous + // {{{ + initial r_continuous = 0; + always @(posedge i_clk) + if (i_reset) + r_continuous <= 0; + else begin + if (!r_busy && axil_write_ready && awskd_addr == CMD_CONTROL + && !w_cmd_abort) + r_continuous <= wskd_strb[3] && wskd_data[CBIT_CONTINUOUS]; + end + // }}} + + // wide_address, wide_length + // {{{ + always @(*) + begin + wide_address = 0; + wide_address[C_AXI_ADDR_WIDTH-1:0] = cmd_addr; + if (!OPT_UNALIGNED) + wide_address[ADDRLSB-1:0] = 0; + + wide_length = 0; + wide_length[ADDRLSB +: LGLENW] = cmd_length_w; + end + // }}} + + // new_cmdaddr* + // {{{ + assign new_cmdaddrlo = apply_wstrb( + wide_address[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + + assign new_cmdaddrhi = apply_wstrb( + wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + // }}} + + // new_length* + // {{{ + assign new_lengthlo = apply_wstrb( + wide_length[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + + assign new_lengthhi = apply_wstrb( + wide_length[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + // }}} + + always @(*) + begin + new_wideaddr = wide_address; + + if (awskd_addr == CMD_ADDRLO) + new_wideaddr[C_AXIL_DATA_WIDTH-1:0] = new_cmdaddrlo; + if (awskd_addr == CMD_ADDRHI) + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] = new_cmdaddrhi; + if (!OPT_UNALIGNED) + new_wideaddr[ADDRLSB-1:0] = 0; + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + + + new_widelen = wide_length; + if (awskd_addr == CMD_LENLO) + new_widelen[C_AXIL_DATA_WIDTH-1:0] = new_lengthlo; + if (awskd_addr == CMD_LENHI) + new_widelen[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] = new_lengthhi; + new_widelen[ADDRLSB-1:0] = 0; + new_widelen[2*C_AXIL_DATA_WIDTH-1:ADDRLSB+LGLENW] = 0; + end + + + initial r_increment = 1'b1; + initial cmd_addr = 0; + initial cmd_length_w = 0; // Counts in bytes + initial zero_length = 1; + initial cmd_length_aligned_w = 0; + initial unaligned_cmd_addr = 1'b0; + initial ar_multiple_full_bursts = 0; + initial ar_multiple_fixed_bursts = 0; + always @(posedge i_clk) + begin + if (axil_write_ready && !r_busy) + begin + case(awskd_addr) + CMD_CONTROL: + r_increment <= !wskd_data[CBIT_INCREMENT]; + CMD_ADDRLO: begin + cmd_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + unaligned_cmd_addr <= |new_wideaddr[ADDRLSB-1:0]; + if (OPT_UNALIGNED) + cmd_length_aligned_w <= cmd_length_w + + (|new_wideaddr[ADDRLSB-1:0] ? 1:0); + // ERR: What if !r_increment? In that case, we can't + // support unaligned addressing + end + CMD_ADDRHI: if (C_AXI_ADDR_WIDTH > C_AXIL_DATA_WIDTH) + begin + cmd_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + end + CMD_LENLO: begin + cmd_length_w <= new_widelen[ADDRLSB +: LGLENW]; + zero_length <= (new_widelen[ADDRLSB +: LGLENW] == 0); + cmd_length_aligned_w <= new_widelen[ADDRLSB +: LGLENW] + + (unaligned_cmd_addr ? 1:0); + ar_multiple_full_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAXBURST)]; + ar_multiple_fixed_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAX_FIXED_BURST)]; + end + CMD_LENHI: begin + cmd_length_w <= new_widelen[ADDRLSB +: LGLENW]; + zero_length <= (new_widelen[ADDRLSB +: LGLENW] == 0); + cmd_length_aligned_w <= new_widelen[ADDRLSB +: LGLENW] + + (unaligned_cmd_addr ? 1:0); + ar_multiple_full_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAXBURST)]; + ar_multiple_fixed_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAX_FIXED_BURST)]; + end + default: begin end + endcase + end else if(r_busy && r_continuous && !axi_abort_pending + && r_increment) + cmd_addr <= axi_raddr + + ((M_AXI_RVALID && !M_AXI_RRESP[1] + && (!unaligned_cmd_addr || realign_last_valid)) + ? (1<<ADDRLSB) : 0); + + if (i_reset) + begin + r_increment <= 1'b1; + cmd_addr <= 0; + cmd_length_w <= 0; + zero_length <= 1; + unaligned_cmd_addr <= 0; + cmd_length_aligned_w <= 0; + ar_multiple_full_bursts <= 0; + ar_multiple_fixed_bursts <= 0; + end + + if (!OPT_UNALIGNED) + unaligned_cmd_addr <= 0; + end + + // w_status_word + // {{{ + always @(*) + begin + w_status_word = 0; + w_status_word[CBIT_BUSY] = r_busy; + w_status_word[CBIT_ERR] = r_err; + w_status_word[CBIT_COMPLETE] = r_complete; + w_status_word[CBIT_CONTINUOUS] = r_continuous; + w_status_word[CBIT_INCREMENT] = !r_increment; + w_status_word[20:16] = LGFIFO; + end + // }}} + + // axil_read_data + // {{{ + always @(posedge i_clk) + if (!axil_read_valid || S_AXIL_RREADY) + begin + axil_read_data <= 0; + case(arskd_addr) + CMD_CONTROL: axil_read_data <= w_status_word; + CMD_ADDRLO: axil_read_data <= wide_address[C_AXIL_DATA_WIDTH-1:0]; + CMD_ADDRHI: axil_read_data <= wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + CMD_LENLO: axil_read_data <= wide_length[C_AXIL_DATA_WIDTH-1:0]; + CMD_LENHI: axil_read_data <= wide_length[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + default axil_read_data <= 0; + endcase + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + // }}} + + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + // {{{ + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The data FIFO section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + assign reset_fifo = i_reset || (!r_busy && (!r_continuous || r_err)); + + // Realign the data (if OPT_UNALIGN) before sending it to the FIFO + // {{{ + // This allows us to handle unaligned addresses. + generate if (OPT_UNALIGNED) + begin : REALIGN_DATA + + reg r_write_to_fifo; + reg [C_AXI_DATA_WIDTH-1:0] last_data, + r_write_data; + reg [ADDRLSB-1:0] corollary_shift; + reg last_valid; + reg [ADDRLSB-1:0] realignment; + + always @(*) + realignment = cmd_addr[ADDRLSB-1:0]; + + initial last_data = 0; + always @(posedge i_clk) + if (reset_fifo || !unaligned_cmd_addr) + last_data <= 0; + else if (M_AXI_RVALID && M_AXI_RREADY) + last_data <= M_AXI_RDATA >> (realignment * 8); + + initial last_valid = 1'b0; + always @(posedge i_clk) + if (reset_fifo) + last_valid <= 0; + else if (M_AXI_RVALID && M_AXI_RREADY) + last_valid <= 1'b1; + else if (!r_busy) + last_valid <= 1'b0; + + assign realign_last_valid = last_valid; + + always @(*) + corollary_shift = -realignment; + + always @(posedge i_clk) + if (M_AXI_RVALID && M_AXI_RREADY) + r_write_data <= (M_AXI_RDATA << (corollary_shift*8)) + | last_data; + else if (!fifo_full) + r_write_data <= last_data; + + initial r_write_to_fifo = 1'b0; + always @(posedge i_clk) + if (reset_fifo) + r_write_to_fifo <= 1'b0; + else if (M_AXI_RVALID && M_AXI_RREADY) + r_write_to_fifo <= last_valid || !unaligned_cmd_addr; + else if (!fifo_full) + r_write_to_fifo <= 1'b0; + + assign write_to_fifo = r_write_to_fifo; + assign write_data = r_write_data; + + end else begin : ALIGNED_DATA + + assign write_to_fifo = M_AXI_RVALID && M_AXI_RREADY; + assign write_data = M_AXI_RDATA; + assign realign_last_valid = 0; + + end endgenerate + // }}} + + assign read_from_fifo = M_AXIS_TVALID && M_AXIS_TREADY; + assign M_AXIS_TVALID = !fifo_empty; + + // Write the results to the FIFO + // {{{ + generate if (OPT_TLAST) + begin : FIFO_W_TLAST + // FIFO section--used if we have to add a TLAST signal to the + // data stream + // {{{ + reg pre_tlast; + wire tlast; + + // tlast will be set on the last data word of any commanded + // burst. + + // Appropriately, pre_tlast = (something) && M_AXI_RVALID + // && M_AXI_RREADY && M_AXI_RLAST + // We can simplify this greatly, since any time M_AXI_RVALID is + // true, we also know that M_AXI_RREADY will be true. This + // allows us to get rid of the RREADY condition. Next, we can + // simplify the RVALID condition since we'll never write to the + // FIFO if RVALID isn't also true. Finally, we can get rid of + // M_AXI_RLAST since this is captured by rd_last_remaining. + always @(*) + pre_tlast = rd_last_remaining; + + if (OPT_UNALIGNED) + begin : GEN_UNALIGNED_TLAST + reg r_tlast; + + // REALIGN delays the data by one clock period. We'll + // also need to delay the last as well. + // Note that no one cares what tlast is if write_to_fifo + // is zero, allowing us to massively simplify this. + always @(posedge i_clk) + r_tlast <= pre_tlast; + + assign tlast = r_tlast; + + end else begin : NO_UNALIGNED_TLAST + + assign tlast = pre_tlast; + end + + + sfifo #( + // {{{ + .BW(C_AXI_DATA_WIDTH+1), .LGFLEN(LGFIFO) + // }}} + ) sfifo( + // {{{ + i_clk, reset_fifo, + write_to_fifo, { tlast, write_data }, fifo_full, fifo_fill, + read_from_fifo, { M_AXIS_TLAST, M_AXIS_TDATA }, fifo_empty + // }}} + ); + // }}} + end else begin : NO_TLAST_FIFO + + // FIFO section, where TLAST is held at 1'b1 + // {{{ + sfifo #( + // {{{ + .BW(C_AXI_DATA_WIDTH), .LGFLEN(LGFIFO) + // }}} + ) sfifo( + // {{{ + i_clk, reset_fifo, + write_to_fifo, write_data, fifo_full, fifo_fill, + read_from_fifo, M_AXIS_TDATA, fifo_empty + // }}} + ); + + assign M_AXIS_TLAST = 1'b1; + // }}} + end endgenerate + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The incoming AXI (full) protocol section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // + + // Some counters to keep track of our state + // {{{ + + + // Count the number of word writes left to be requested, starting + // with the overall command length and then reduced by M_AWLEN on + // each address write + // {{{ + always @(*) + begin + ar_next_remaining = ar_requests_remaining; + ar_next_remaining = ar_requests_remaining + + { {(LGLENWA-8){phantom_start}}, + (phantom_start) ? ~M_AXI_ARLEN : 8'h00}; + end + + always @(posedge i_clk) + if (!r_busy) + r_pre_start <= 1; + else + r_pre_start <= 0; + + always @(posedge i_clk) + if (!r_busy) + begin + ar_needs_alignment <= 0; + + if (|new_wideaddr[ADDRLSB +: LGMAXBURST]) + begin + if (|new_widelen[LGLEN-1:(LGMAXBURST+ADDRLSB)]) + ar_needs_alignment <= 1; + if (~new_wideaddr[ADDRLSB +: LGMAXBURST] + < new_widelen[ADDRLSB +: LGMAXBURST]) + ar_needs_alignment <= 1; + end + end + + initial ar_none_remaining = 1; + initial ar_requests_remaining = 0; + always @(posedge i_clk) + if (!r_busy) + begin + ar_requests_remaining <= cmd_length_aligned_w; + ar_none_remaining <= zero_length; + ar_multiple_bursts_remaining + <= |cmd_length_aligned_w[LGLENWA-1:LGMAXBURST+1]; + end else if (cmd_abort || axi_abort_pending) + begin + ar_requests_remaining <= 0; + ar_none_remaining <= 1; + ar_multiple_bursts_remaining <= 0; + + end else if (phantom_start) + begin + // Verilator lint_off WIDTH + ar_requests_remaining + <= ar_next_remaining; + ar_none_remaining <= (ar_next_remaining == 0); + ar_multiple_bursts_remaining + <= |ar_next_remaining[LGLENWA-1:LGMAXBURST+1]; + // Verilator lint_on WIDTH + end + // }}} + + // Calculate the maximum possible burst length, ignoring 4kB boundaries + // {{{ + always @(*) + addralign = 1+(~cmd_addr[ADDRLSB +: LGMAXBURST]); + + always @(*) + begin + initial_burstlen = (1<<LGMAXBURST); + if (!r_increment) + begin + initial_burstlen = MAX_FIXED_BURST; + if (!ar_multiple_fixed_bursts + && !cmd_length_aligned_w[LGMAX_FIXED_BURST]) + begin + initial_burstlen = 0; + initial_burstlen[LGMAX_FIXED_BURST-1:0] + = cmd_length_aligned_w[ + LGMAX_FIXED_BURST-1:0]; + end + end else if (ar_needs_alignment) + initial_burstlen = { 1'b0, addralign }; + else if (!ar_multiple_full_bursts + && !cmd_length_aligned_w[LGMAXBURST]) + initial_burstlen = { 1'b0, cmd_length_aligned_w[ + LGMAXBURST-1:0] }; + end + + initial r_max_burst = 0; + always @(posedge i_clk) + if (!r_busy || r_pre_start) + begin + // Force us to align ourself early + // That way we don't need to check for + // alignment (again) later + r_max_burst <= initial_burstlen; + end else if (phantom_start) + begin + // Verilator lint_off WIDTH + if (r_increment || LGMAXBURST <= LGMAX_FIXED_BURST) + begin : LIMIT_BY_LGMAXBURST + r_max_burst <= (1<<LGMAXBURST); + + if (!ar_multiple_bursts_remaining + && ar_next_remaining[LGMAXBURST:0] < (1<<LGMAXBURST)) + r_max_burst <= { 1'b0, ar_next_remaining[8:0] }; + end else begin : LIMIT_BY_SIXTEEN + r_max_burst <= MAX_FIXED_BURST; + + if (!ar_multiple_bursts_remaining + && ar_next_remaining[LGMAXBURST:0] < MAX_FIXED_BURST) + r_max_burst <= { 1'b0, ar_next_remaining[LGMAXBURST:0] }; + end + // Verilator lint_on WIDTH + end + // }}} + + // Count the number of bursts outstanding--these are the number of + // ARVALIDs that have been accepted, but for which the RVALID && RLAST + // has not (yet) been returned. + // {{{ + initial ar_none_outstanding = 1; + initial ar_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset) + begin + ar_bursts_outstanding <= 0; + ar_none_outstanding <= 1; + end else case ({ phantom_start, + M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST }) + 2'b01: begin + ar_bursts_outstanding <= ar_bursts_outstanding - 1; + ar_none_outstanding <= (ar_bursts_outstanding == 1); + end + 2'b10: begin + ar_bursts_outstanding <= ar_bursts_outstanding + 1; + ar_none_outstanding <= 0; + end + default: begin end + endcase + // }}} + + // Are we there yet? + // {{{ + initial rd_reads_remaining = 0; + initial rd_none_remaining = 1; + initial rd_last_remaining = 0; + always @(posedge i_clk) + if (!r_busy) + begin + rd_reads_remaining <= cmd_length_aligned_w; + rd_last_remaining <= (cmd_length_aligned_w == 1); + rd_none_remaining <= (cmd_length_aligned_w == 0); + end else if (M_AXI_RVALID && M_AXI_RREADY) + begin + rd_reads_remaining <= rd_reads_remaining - 1; + rd_last_remaining <= (rd_reads_remaining == 2); + rd_none_remaining <= (rd_reads_remaining == 1); + end + + always @(*) + if (!r_busy) + w_complete = 0; + else if (axi_abort_pending && ar_none_outstanding && !M_AXI_ARVALID) + w_complete = 1; + else if (r_continuous) + w_complete = (rd_none_remaining)||((rd_last_remaining) && M_AXI_RVALID && M_AXI_RREADY); + else // if !r_continuous + w_complete = (rd_none_remaining && fifo_empty); + + // }}} + + // Are we stopping early? Aborting something ongoing? + // {{{ + initial axi_abort_pending = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + axi_abort_pending <= 0; + else begin + if (M_AXI_RVALID && M_AXI_RREADY && M_AXI_RRESP[1]) + axi_abort_pending <= 1; + if (cmd_abort) + axi_abort_pending <= 1; + end + // }}} + + // Count the number of uncommited spaces in the FIFO + // {{{ + generate if (OPT_UNALIGNED) + begin : GEN_UNALIGNED_BREQ_COUNT + reg r_partial_burst_requested; + + initial r_partial_burst_requested = 1'b1; + always @(posedge i_clk) + if (!r_busy) + r_partial_burst_requested <= !unaligned_cmd_addr; + else if (phantom_start) + r_partial_burst_requested <= 1'b1; + + assign partial_burst_requested = r_partial_burst_requested; + end else begin : NO_UNALIGNED_BREQ_COUNT + + assign partial_burst_requested = 1'b1; + end endgenerate + + initial rd_uncommitted = (1<<LGFIFO); + always @(posedge i_clk) + if (reset_fifo) + begin + rd_uncommitted <= (1<<LGFIFO); + end else case ({ phantom_start, + M_AXIS_TVALID && M_AXIS_TREADY }) + 2'b00: begin end + 2'b01: begin + rd_uncommitted <= rd_uncommitted + 1; + end + 2'b10: begin + // Verilator lint_off WIDTH + rd_uncommitted <= rd_uncommitted - (M_AXI_ARLEN + 1) + + (partial_burst_requested ? 0 :1); + end + 2'b11: begin + rd_uncommitted <= rd_uncommitted - (M_AXI_ARLEN) + + (partial_burst_requested ? 0 :1); + // Verilator lint_on WIDTH + end + endcase + // }}} + + // So that we can monitor where we are at, and perhaps restart it + // later, keep track of the current address used by the R-channel + // {{{ + + initial axi_raddr = 0; + always @(posedge i_clk) + begin + if (!r_busy) + axi_raddr <= cmd_addr; + else if (axi_abort_pending || !r_increment) + // Stop incrementing tthe address following an abort + axi_raddr <= axi_raddr; + else begin + if (M_AXI_RVALID && M_AXI_RREADY && !M_AXI_RRESP[1] + && (!unaligned_cmd_addr || realign_last_valid)) + axi_raddr <= axi_raddr + (1<<ADDRLSB); + end + + if (!OPT_UNALIGNED) + axi_raddr[ADDRLSB-1:0] <= 0; + end + + // }}} + + // + // }}} + + // start_burst, phantom_start + // {{{ + always @(*) + begin + start_burst = !ar_none_remaining; + if ((rd_uncommitted[LGFIFO:LGMAXBURST] == 0) + && ({ 1'b0, rd_uncommitted[LGMAXBURST-1:0] } + < r_max_burst)) + start_burst = 0; + if (phantom_start || r_pre_start) + // Insist on a minimum of one clock between burst + // starts, so we can get our lengths right + start_burst = 0; + if (M_AXI_ARVALID && !M_AXI_ARREADY) + start_burst = 0; + if (!r_busy || cmd_abort || axi_abort_pending) + start_burst = 0; + end + + initial phantom_start = 0; + always @(posedge i_clk) + if (i_reset) + phantom_start <= 0; + else + phantom_start <= start_burst; + // }}} + + + // Calculate ARLEN and ARADDR for the next ARVALID + // {{{ + generate if (LGMAXBURST >= 8) + begin : GEN_BIG_AWLEN + // Verilator lint_off WIDTH + + always @(posedge i_clk) + if (!r_busy) + axi_arlen <= initial_burstlen - 1; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arlen <= r_max_burst - 1; + + // Verilator lint_on WIDTH + end else begin : GEN_SHORT_AWLEN + + always @(posedge i_clk) + begin + axi_arlen[7:LGMAXBURST] <= 0; + if (!r_busy) + axi_arlen[LGMAXBURST:0] <= initial_burstlen - 1; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arlen[LGMAXBURST:0] <= r_max_burst - 1; + end + + end endgenerate + // }}} + + // Calculate ARADDR for the next ARVALID + // {{{ + initial axi_araddr = 0; + always @(posedge i_clk) + begin + if (M_AXI_ARVALID && M_AXI_ARREADY) + begin + if (r_increment) + // Verilator lint_off WIDTH + axi_araddr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + <= axi_araddr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + + (M_AXI_ARLEN + 1); + // Verilator lint_on WIDTH + + axi_araddr[ADDRLSB-1:0] <= 0; + end + + if (!r_busy) + begin + axi_araddr <= cmd_addr; + end + + if (!OPT_UNALIGNED) + axi_araddr[ADDRLSB-1:0] <= 0; + end + // }}} + + // ARVALID + // {{{ + initial axi_arvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_arvalid <= 0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arvalid <= start_burst; + // }}} + + // Set the constant M_AXI_* signals + // {{{ + assign M_AXI_ARVALID= axi_arvalid; + assign M_AXI_ARID = AXI_ID; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARLEN = axi_arlen; + // Verilator lint_off WIDTH + assign M_AXI_ARSIZE = $clog2(C_AXI_DATA_WIDTH)-3; + // Verilator lint_on WIDTH + assign M_AXI_ARBURST= { 1'b0, r_increment }; + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE= 4'b0011; + assign M_AXI_ARPROT = 0; + assign M_AXI_ARQOS = 0; + + assign M_AXI_RREADY = 1; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // (Optional) clock gating + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_CLKGATE) + begin : CLK_GATING + // {{{ + reg gatep, r_clk_active; + reg gaten /* verilator clock_enable */; + + // clk_active + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_clk_active <= 1'b1; + else begin + r_clk_active <= 1'b0; + + if (r_busy) + r_clk_active <= 1'b1; + if (awskd_valid || wskd_valid || arskd_valid) + r_clk_active <= 1'b1; + if (S_AXIL_BVALID || S_AXIL_RVALID) + r_clk_active <= 1'b1; + + if (M_AXIS_TVALID) + r_clk_active <= 1'b1; + end + + assign clk_active = r_clk_active; + // }}} + // Gate the clock here locally + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + gatep <= 1'b1; + else + gatep <= clk_active; + + always @(negedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + gaten <= 1'b1; + else + gaten <= gatep; + + assign gated_clk = S_AXI_ACLK && gaten; + + assign clk_active = r_clk_active; + // }}} + // }}} + end else begin : NO_CLK_GATING + // {{{ + // Always active + assign clk_active = 1'b1; + assign gated_clk = S_AXI_ACLK; + // }}} + end endgenerate + // }}} + + // Keep Verilator happy + // {{{ + // Verilator coverage_off + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_RID, + M_AXI_RRESP[0], fifo_full, wskd_strb[2:0], fifo_fill, + ar_none_outstanding, S_AXIL_AWADDR[AXILLSB-1:0], + S_AXIL_ARADDR[AXILLSB-1:0], + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], + new_widelen + }; + // Verilator coverage_on + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // The formal properties for this unit are maintained elsewhere. + // This core does, however, pass a full prove (w/ induction) for all + // bus properties. + // + // ... + // + + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1; + //////////////////////////////////////////////////////////////////////// + // + // Properties of the AXI-stream data interface + // {{{ + // + //////////////////////////////////////////////////////////////////////// + // + // + + // (These are captured by the FIFO within) + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH), + // + // ... + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + // ... + // }}} + ); + + // + // ... + // + + always @(posedge i_clk) + if (f_past_valid && $past(S_AXI_ARESETN)) + begin + if ($past(r_busy)||$past(w_cmd_start)) + begin + assert($stable(cmd_length_b)); + assert($stable(cmd_length_w)); + assert($stable(cmd_length_aligned_w)); + end + if ($past(r_busy)) + begin + assert($stable(r_increment)); + assert($stable(r_continuous)); + end + if ($past(r_busy) && $past(r_busy,2)) + assert($stable(fv_start_addr)); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI master memory interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // ... + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // + .OPT_EXCLUSIVE(1'b0), + .OPT_NARROW_BURST(1'b0), + // + // ... + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(1'b0), + .i_axi_awready(1'b0), + .i_axi_awid( AXI_ID), + .i_axi_awaddr( 0), + .i_axi_awlen( 0), + .i_axi_awsize( 0), + .i_axi_awburst(0), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( 0), + .i_axi_awqos( 0), + // + .i_axi_wvalid(0), + .i_axi_wready(0), + .i_axi_wdata( 0), + .i_axi_wstrb( 0), + .i_axi_wlast( 0), + // + .i_axi_bvalid(1'b0), + .i_axi_bready(1'b0), + .i_axi_bid( AXI_ID), + .i_axi_bresp( 2'b00), + // + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rlast( M_AXI_RLAST), + .i_axi_rresp( M_AXI_RRESP), + // + // ... + // + // }}} + ); + + + // + // ... + // + + always @(*) + if (r_busy) + begin + // + // ... + // + end else begin + // + // ... + // + + assert(rd_uncommitted + + ((OPT_UNALIGNED && write_to_fifo) ? 1:0) + + fifo_fill == (1<<LGFIFO)); + if (!r_continuous) + assert(fifo_fill == 0 || reset_fifo); + end + + // + // ... + // + + always @(*) + if (r_busy) + begin + if (!OPT_UNALIGNED) + begin + assert(M_AXI_ARADDR[ADDRLSB-1:0] == 0); + end else + assert((M_AXI_ARADDR[ADDRLSB-1:0] == 0) + ||(M_AXI_ARADDR == fv_start_addr)); + end + + always @(*) + if (!OPT_UNALIGNED || (r_busy && !r_increment)) + begin + assert(cmd_addr[ADDRLSB-1:0] == 0); + assert(fv_start_addr[ADDRLSB-1:0] == 0); + assert(axi_araddr[ADDRLSB-1:0] == 0); + assert(axi_raddr[ADDRLSB-1:0] == 0); + end + + // + // f_last_addr is the (aligned) address one following the last valid + // address read. Once all reading is done, all (aligned) address + // pointers should point there. + always @(*) + begin + f_last_addr = { fv_start_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB], + {(ADDRLSB){1'b0}} }; + + if (r_increment) + f_last_addr = f_last_addr + cmd_length_b; + if (unaligned_cmd_addr) // Only true if r_increment as well + f_last_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + = f_last_addr[C_AXI_ADDR_WIDTH-1:ADDRLSB]+1; + + f_last_addr[ADDRLSB-1:0] = 0; + + f_next_start = fv_start_addr; + if (r_increment) + f_next_start = f_next_start + cmd_length_b; + if (!OPT_UNALIGNED) + assert(f_next_start == f_last_addr); + end + + + // + // ... + // + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other formal properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Define some helper metrics + // + initial fv_start_addr = 0; + always @(posedge i_clk) + if (!r_busy) + fv_start_addr <= cmd_addr; + + always @(*) + begin + // Metrics defining M_AXI_ARADDR + f_araddr_is_aligned = (M_AXI_ARADDR[ADDRLSB+LGMAXBURST-1:0]==0); + f_araddr_is_initial = (M_AXI_ARADDR == fv_start_addr); + f_araddr_is_final = (M_AXI_ARADDR == f_last_addr); + + // + // Metrics to check ARADDR, assuming it had been accepted + // + + // + // ... + // + end + + // + // fv_ar_requests_remaining ... shadows ar_requests_remaining + // + // Since ar_requests_remaining drops to zero suddenly on any + // axi_abort_pending, we need another counter that we can use + // which doesn't have this feature, but which can also be used + // to check assertions and intermediate logic against until the + // abort takes effect. + initial fv_ar_requests_remaining = 0; + always @(posedge i_clk) + if (!r_busy) + begin + fv_ar_requests_remaining <= cmd_length_aligned_w; + end else if (phantom_start) + begin + // Verilator lint_off WIDTH + fv_ar_requests_remaining + <= fv_ar_requests_remaining - (M_AXI_ARLEN + 1); + // Verilator lint_on WIDTH + end + + always @(*) + if (r_busy) + begin + if (!axi_abort_pending) + begin + assert(fv_ar_requests_remaining == ar_requests_remaining); + end else + assert((ar_requests_remaining == 0) + ||(fv_ar_requests_remaining + == ar_requests_remaining)); + end + + always @(*) + if(r_busy) + begin + assert(fv_ar_requests_remaining <= cmd_length_aligned_w); + // + // ... + // + end + + // + // fv_axi_raddr ... shadows axi_raddr + // + // The low order bits will match the low order bits of the initial + // cmd_addr + // + initial fv_axi_raddr = 0; + always @(posedge i_clk) + if (!r_busy) + fv_axi_raddr <= cmd_addr; + else if (!r_increment) + fv_axi_raddr <= fv_start_addr; + else begin + if (M_AXI_RVALID && M_AXI_RREADY + && (!unaligned_cmd_addr || realign_last_valid)) + fv_axi_raddr <= fv_axi_raddr + (1<<ADDRLSB); + if (!OPT_UNALIGNED) + fv_axi_raddr[ADDRLSB-1:0] <= 0; + end + + // Constrain start <= axi_raddr <= fv_axi_raddr <= f_last_addr + // in spite of any address wrapping + always @(*) + if (r_busy) + begin + assert(axi_raddr[ADDRLSB-1:0] == cmd_addr[ADDRLSB-1:0]); + assert(axi_abort_pending || fv_axi_raddr == axi_raddr); + if (!r_increment) + begin + assert(fv_axi_raddr == fv_start_addr); + assert(axi_raddr == fv_start_addr); + end if (!axi_abort_pending) + begin + if (fv_start_addr <= f_last_addr) + begin + // Natural order: start < f_raddr < last + assert(fv_axi_raddr <= f_last_addr); + assert(fv_axi_raddr >= fv_start_addr); + end else begin + // Reverse order + // Either: last < start <= f_raddr + // or: f_raddr < last < start + assert((fv_axi_raddr >= fv_start_addr) + ||(fv_axi_raddr <= f_last_addr)); + end + + if (fv_start_addr <= fv_axi_raddr) + begin + // Natural order: start < rad < f_rad < last + // or even: last < start < rad < f_rad + assert(axi_raddr <= fv_axi_raddr); + assert(fv_start_addr <= axi_raddr); + end else if (fv_axi_raddr <= f_last_addr) + begin + // Reverse order: f_raddr < last < start + // so either: last < start < raddr + // or: raddr < f_raddr < last < start + // + assert((axi_raddr >= fv_start_addr) + || (axi_raddr <= fv_axi_raddr)); + end + end + end + + always @(*) + if (!r_busy) + begin + assert(!M_AXI_ARVALID); + assert(!M_AXI_RVALID); + // + // ... + // + end + + always @(*) + assert(zero_length == (cmd_length_w == 0)); + + always @(*) + if (phantom_start) + assert(rd_uncommitted >= (M_AXI_ARLEN + 1)); + + always @(*) + if (zero_length) + assert(!r_busy); + always @(*) + if (r_busy) + assert(ar_none_remaining == (ar_requests_remaining == 0)); + always @(*) + assert(ar_none_outstanding == (ar_bursts_outstanding == 0)); + always @(*) + assert(rd_none_remaining == (rd_reads_remaining == 0)); + always @(*) + assert(rd_last_remaining == (rd_reads_remaining == 1)); + + always @(*) + if (r_complete) + assert(!r_busy); + + // + // ... + // + + // + // fifo_availability is a measure of (1<<LGFIFO) minus the current + // fifo fill. This does not include what's in the pre-FIFO + // logic when OPT_UNALIGNED is true. + always @(*) + f_fifo_availability = rd_uncommitted; + + always @(*) + assert(f_fifo_committed <= (1<<LGFIFO)); + + always @(*) + assert(f_fifo_availability <= (1<<LGFIFO)); + + // + // ... + // + + always @(*) + if (!reset_fifo) + assert(f_fifo_committed + f_fifo_availability + fifo_fill + == (1<<LGFIFO)); + + // + // ... + // + + always @(*) + if (r_busy) + assert(r_max_burst <= (1<<LGMAXBURST)); + + always @(*) + if (r_busy && !r_pre_start) + assert((r_max_burst > 0) || (ar_requests_remaining == 0)); + + + always @(*) + if (phantom_start) + assert(M_AXI_ARVALID); + + always @(posedge i_clk) + if (phantom_start) + begin + assert(r_max_burst > 0); + assert(M_AXI_ARLEN == $past(r_max_burst)-1); + end + + + + // + // Address checking + // + + // Check cmd_addr + always @(*) + if (r_busy) + begin + if (r_increment && r_continuous) + begin + assert(cmd_addr == axi_raddr); + end else + assert(cmd_addr == fv_start_addr); + end + + // Check M_AXI_ARADDR + // + // ... + // + + // + // Check M_AXI_ARLEN + // + // ... + // + + // + // Constrain the r_maxburst + // + // ... + // + + always @(*) + if (r_busy) + begin + assert(r_max_burst <= (1<<LGMAXBURST)); + // + // ... + // + end + + // + // Constrain the length + // + // ... + // + + // Constrain rd_reads_remaining + // + // ... + // + always @(*) + if (r_busy) + begin + assert(rd_reads_remaining <= cmd_length_w); + // + // ... + // + assert(ar_bursts_outstanding <= rd_reads_remaining); + // + // ... + // + end + + // + // Constrain the number of requests remaining + // + // ... + // + + // + // Make sure our aw_bursts_outstanding counter never overflows + // (Given that the counter is as long as the length register, it cannot) + // + always @(*) + begin + if (&ar_bursts_outstanding[LGLENWA-1:1]) + assert(!M_AXI_ARVALID); + // + // ... + // + end + + // }}} + + // + // Some (fairly) random/unsorted properties + // {{{ + + always @(*) + begin + assert(ar_multiple_full_bursts + == |cmd_length_w[LGLENW-1:LGMAXBURST]); + assert(ar_multiple_fixed_bursts + == |cmd_length_w[LGLENW-1:LGMAX_FIXED_BURST]); + end + // + // Match axi_raddr to the faxi_ values + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // 1. All data values must get read and placed into the FIFO, with + // none skipped + // Captured in logic above(?) + // + // 2. No addresses skipped. Check the write address against the + // write address we are expecting + // + // ... + // + + // 3. If we aren't incrementing addresses, then our current address + // should always be the axi address + // + // ... + + // + // 4. Whenever we go from busy to idle, we should raise o_int for one + // (and only one) cycle + always @(posedge i_clk) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assert(!o_int); + end else + assert(o_int == $fell(r_busy)); + + // + // ... + // + + + // 5. Pick an incoming data value. Choose whether or not to restrict + // incoming data not to be that value. If the incoming data is so + // restricted then assert that the stream output will never contain that + // value. + (* anyconst *) reg f_restrict_data; + (* anyconst *) reg [C_AXI_DATA_WIDTH-1:0] f_restricted; + + always @(*) + if (f_restrict_data && M_AXI_RVALID + && (!OPT_UNALIGNED || !unaligned_cmd_addr)) + assume(M_AXI_RDATA != f_restricted); + + always @(*) + if (f_restrict_data && M_AXIS_TVALID + && (!OPT_UNALIGNED || !unaligned_cmd_addr)) + assert(M_AXIS_TDATA != f_restricted); + + // + // ... + // + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + reg cvr_aborted, cvr_buserr; + reg [2:0] cvr_continued; + + initial { cvr_aborted, cvr_buserr } = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + { cvr_aborted, cvr_buserr } <= 0; + else if (r_busy && !axi_abort_pending) + begin + if (cmd_abort && ar_requests_remaining > 0) + cvr_aborted <= 1; + if (M_AXI_RVALID && M_AXI_RRESP[1] && M_AXI_RLAST) + cvr_buserr <= 1; + end + + initial cvr_continued = 0; + always @(posedge i_clk) + if (i_reset || r_err || cmd_abort) + cvr_continued <= 0; + else begin + // Cover a continued transaction across two separate bursts + if (r_busy && r_continuous) + cvr_continued[0] <= 1; + if (!r_busy && cvr_continued[0]) + cvr_continued[1] <= 1; + if (r_busy && cvr_continued[1]) + cvr_continued[2] <= 1; + end + + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && !i_reset && $fell(r_busy)) + begin + cover( r_err && cvr_aborted); + cover( r_err && cvr_buserr); + cover(!r_err); + if (!r_err && !axi_abort_pending && !cvr_aborted && !cvr_buserr) + begin + cover(cmd_length_w > 5); + cover(cmd_length_w > 8); + // + // ... + // + cover(&cvr_continued); + cover(&cvr_continued && (cmd_length_w > 2)); + cover(&cvr_continued && (cmd_length_w > 5)); + end + end + + always @(*) + if (!i_reset) + cover(!r_err && fifo_fill > 8 && !r_busy); + + always @(*) + cover(r_busy); + + always @(*) + cover(start_burst); + + always @(*) + cover(M_AXI_ARVALID && M_AXI_ARREADY); + + always @(*) + cover(M_AXI_RVALID); + + always @(*) + cover(M_AXI_RVALID & M_AXI_RLAST); + always @(*) + if (r_busy) + begin + cover(!ar_none_remaining); + if(!ar_none_remaining) + begin + cover(1); + cover(rd_uncommitted>= {{(LGFIFO-LGMAXBURST){1'b0}}, r_max_burst}); + if(rd_uncommitted>= {{(LGFIFO-LGMAXBURST){1'b0}}, r_max_burst}) + begin + cover(!phantom_start); + cover(phantom_start); + end + end + end + + // + // Unaligned cover properties + generate if (OPT_TLAST) + begin + reg [3:0] cvr_lastcount; + always @(posedge i_clk) + if (i_reset || (r_busy && cmd_length_w <= 2)) + cvr_lastcount <= 0; + else if (M_AXIS_TVALID && M_AXIS_TREADY && M_AXIS_TLAST + && !cvr_lastcount[3]) + cvr_lastcount <= cvr_lastcount + 1; + + always @(*) + cover(M_AXIS_TVALID && M_AXIS_TREADY && M_AXIS_TLAST); + + always @(posedge i_clk) + cover(o_int && cvr_lastcount > 2); + + end endgenerate + + generate if (OPT_UNALIGNED) + begin + // + // ... + // + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && !i_reset &&$fell(r_busy)) + begin + cover(r_err); + cover(!r_err); + cover(axi_abort_pending); + cover(!axi_abort_pending); + cover(cvr_aborted); + cover(!cvr_aborted); + cover(cvr_buserr); + cover(!cvr_buserr); + cover(!cvr_buserr && !axi_abort_pending); + cover(!cvr_buserr && !axi_abort_pending + && (cmd_length_w > 2)); + cover(!r_err && !cvr_aborted && !cvr_buserr + && !axi_abort_pending + && (cmd_length_w > 2)); + end + end endgenerate + + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/aximrd2wbsp.v b/rtl/wb2axip/aximrd2wbsp.v new file mode 100644 index 0000000..07dd7a7 --- /dev/null +++ b/rtl/wb2axip/aximrd2wbsp.v @@ -0,0 +1,740 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: aximrd2wbsp.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Bridge an AXI read channel pair to a single wishbone read +// channel. +// +// Rules: +// 1. Any read channel error *must* be returned to the correct +// read channel ID. In other words, we can't pipeline between IDs +// 2. A FIFO must be used on getting a WB return, to make certain that +// the AXI return channel is able to stall with no loss +// 3. No request can be accepted unless there is room in the return +// channel for it +// +// Status: Passes a formal bounded model check at 15 steps. Should be +// ready for a hardware check. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module aximrd2wbsp #( + // {{{ + parameter C_AXI_ID_WIDTH = 6, // The AXI id width used for R&W + // This is an int between 1-16 + parameter C_AXI_DATA_WIDTH = 32,// Width of the AXI R&W data + parameter C_AXI_ADDR_WIDTH = 28, // AXI Address width + localparam AXI_LSBS = $clog2(C_AXI_DATA_WIDTH/8), + localparam AW = C_AXI_ADDR_WIDTH - AXI_LSBS, + parameter LGFIFO = 3, + parameter [0:0] OPT_SWAP_ENDIANNESS = 1'b0, + parameter [0:0] OPT_SIZESEL = 1 + // parameter WBMODE = "B4PIPELINE" + // Could also be "BLOCK" + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, // Bus clock + input wire S_AXI_ARESETN, // Bus reset + // AXI + // {{{ + // AXI read address channel signals + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [7:0] S_AXI_ARLEN, + input wire [2:0] S_AXI_ARSIZE, + input wire [1:0] S_AXI_ARBURST, + input wire [0:0] S_AXI_ARLOCK, + input wire [3:0] S_AXI_ARCACHE, + input wire [2:0] S_AXI_ARPROT, + input wire [3:0] S_AXI_ARQOS, + + // AXI read data channel signals + output reg S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire S_AXI_RLAST, + output reg [1:0] S_AXI_RRESP, + // }}} + // Wishbone channel + // {{{ + // We'll share the clock and the reset + output reg o_wb_cyc, + output reg o_wb_stb, + output reg [(AW-1):0] o_wb_addr, + output wire [(C_AXI_DATA_WIDTH/8-1):0] o_wb_sel, + input wire i_wb_stall, + input wire i_wb_ack, + input wire [(C_AXI_DATA_WIDTH-1):0] i_wb_data, + input wire i_wb_err + // }}} + // }}} + ); + + // Register/net definitions + // {{{ + wire w_reset; + + wire lastid_fifo_full, lastid_fifo_empty, + resp_fifo_full, resp_fifo_empty; + wire [LGFIFO:0] lastid_fifo_fill, resp_fifo_fill; + + + reg last_stb, last_ack, err_state; + reg [C_AXI_ID_WIDTH-1:0] axi_id; + reg [7:0] stblen; + + wire skid_arvalid; + wire [C_AXI_ID_WIDTH-1:0] skid_arid;// r_id; + wire [C_AXI_ADDR_WIDTH-1:0] skid_araddr;// r_addr; + wire [7:0] skid_arlen;// r_len; + wire [2:0] skid_arsize;// r_size; + wire [1:0] skid_arburst;// r_burst; + reg fifo_nearfull; + wire accept_request; + + reg [1:0] axi_burst; + reg [2:0] axi_size; + reg [C_AXI_DATA_WIDTH/8-1:0] axi_strb; + reg [7:0] axi_len; + reg [C_AXI_ADDR_WIDTH-1:0] axi_addr; + wire [C_AXI_ADDR_WIDTH-1:0] next_addr; + wire response_err; + wire lastid_fifo_wr; + reg midissue, wb_empty; + reg [LGFIFO+7:0] acks_expected; + + reg [C_AXI_DATA_WIDTH-1:0] read_data; + + assign w_reset = (S_AXI_ARESETN == 1'b0); + // }}} + + // incoming skidbuffer + // {{{ + skidbuffer #( + .OPT_OUTREG(0), + .DW(C_AXI_ID_WIDTH + C_AXI_ADDR_WIDTH + 8 + 3 + 2) + ) axirqbuf(S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_ARVALID, S_AXI_ARREADY, + { S_AXI_ARID, S_AXI_ARADDR, S_AXI_ARLEN, + S_AXI_ARSIZE, S_AXI_ARBURST }, + skid_arvalid, accept_request, + { skid_arid, skid_araddr, skid_arlen, + skid_arsize, skid_arburst }); + // }}} + + // accept_request + // {{{ + assign accept_request = (!err_state) + &&((!o_wb_cyc)||(!i_wb_err)) + // &&(!lastid_fifo_full) + &&(!midissue + ||(o_wb_stb && last_stb && !i_wb_stall)) + &&(skid_arvalid) + // One ID at a time, lest we return a bus error + // to the wrong AXI master + &&(wb_empty ||(skid_arid == axi_id)); + // }}} + + // o_wb_cyc, o_wb_stb, stblen, last_stb + // {{{ + initial o_wb_cyc = 0; + initial o_wb_stb = 0; + initial stblen = 0; + initial last_stb = 0; + always @(posedge S_AXI_ACLK) + if (w_reset) + begin + o_wb_stb <= 1'b0; + o_wb_cyc <= 1'b0; + end else if (err_state || (o_wb_cyc && i_wb_err)) + begin + o_wb_cyc <= 1'b0; + o_wb_stb <= 1'b0; + end else if ((!o_wb_stb)||(!i_wb_stall)) + begin + if (accept_request) + begin + // Process the new request + o_wb_cyc <= 1'b1; + if (lastid_fifo_full && (!S_AXI_RVALID||!S_AXI_RREADY)) + o_wb_stb <= 1'b0; + else if (fifo_nearfull && midissue + && (!S_AXI_RVALID||!S_AXI_RREADY)) + o_wb_stb <= 1'b0; + else + o_wb_stb <= 1'b1; + end else if (!o_wb_stb && midissue) + begin + // Restart a transfer once the FIFO clears + if (S_AXI_RVALID) + o_wb_stb <= S_AXI_RREADY; + // end else if ((o_wb_cyc)&&(i_wb_err)||(err_state)) + end else if (o_wb_stb && !last_stb) + begin + if (fifo_nearfull + && (!S_AXI_RVALID||!S_AXI_RREADY)) + o_wb_stb <= 1'b0; + end else if (!o_wb_stb || last_stb) + begin + // End the request + o_wb_stb <= 1'b0; + + // Check for the last acknowledgment + if ((i_wb_ack)&&(last_ack)) + o_wb_cyc <= 1'b0; + if (i_wb_err) + o_wb_cyc <= 1'b0; + end + end + // }}} + + // stblen, last_stb, midissue + // {{{ + initial stblen = 0; + initial last_stb = 0; + initial midissue = 0; + always @(posedge S_AXI_ACLK) + if (w_reset) + begin + stblen <= 0; + last_stb <= 0; + midissue <= 0; + end else if (accept_request) + begin + stblen <= skid_arlen; + last_stb <= (skid_arlen == 0); + midissue <= 1'b1; + end else if (lastid_fifo_wr) + begin + if (stblen > 0) + stblen <= stblen - 1; + last_stb <= (stblen == 1); + midissue <= (stblen > 0); + end + // }}} + + // axi_id, axi_burst, axi_size, axi_len + // {{{ + initial axi_size = AXI_LSBS[2:0]; + initial axi_strb = -1; + always @(posedge S_AXI_ACLK) + if (accept_request) + begin + axi_id <= skid_arid; + axi_burst <= skid_arburst; + axi_size <= skid_arsize; + axi_len <= skid_arlen; + + if (OPT_SIZESEL) + axi_strb <= (1<<(1<<(skid_arsize)))-1; + else + axi_strb <= { (C_AXI_DATA_WIDTH/8){1'b1} }; + end +`ifdef FORMAL + always @(*) + case(axi_size) + 0: assert(axi_strb == 1); + 1: assert((C_AXI_DATA_WIDTH > 8) && (axi_strb == 2'b11)); + 2: assert((C_AXI_DATA_WIDTH > 16) && (axi_strb == 4'b1111)); + 3: assert((C_AXI_DATA_WIDTH > 32) && (axi_strb == 8'hff)); + 4: assert((C_AXI_DATA_WIDTH > 64) && (axi_strb == 16'hffff)); + 5: assert((C_AXI_DATA_WIDTH > 128) && (axi_strb == 32'hffff_ffff)); + 6: assert((C_AXI_DATA_WIDTH > 256) && (axi_strb == 64'hffff_ffff_ffff_ffff)); + default: assert((C_AXI_DATA_WIDTH == 1024) && (&axi_strb)); + endcase +`endif + // }}} + + // next_addr + // {{{ + axi_addr #(.AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH)) + next_read_addr(axi_addr, axi_size, axi_burst, axi_len, next_addr); + // }}} + + // {{{ + always @(posedge S_AXI_ACLK) + if (accept_request) + axi_addr <= skid_araddr; + else if (!i_wb_stall) + axi_addr <= next_addr; + // }}} + + always @(*) + o_wb_addr = axi_addr[(C_AXI_ADDR_WIDTH-1):C_AXI_ADDR_WIDTH-AW]; + + // o_wb_sel + // {{{ + generate if (OPT_SIZESEL && C_AXI_DATA_WIDTH > 8) + begin : MULTI_BYTE_SEL + // {{{ + assign o_wb_sel = axi_strb << axi_addr[AXI_LSBS-1:0]; + // }}} + end else begin : FULL_WORD_SEL + assign o_wb_sel = {(C_AXI_DATA_WIDTH/8){1'b1}}; + + // Verilator lint_off UNUSED + wire unused_sel; + assign unused_sel = &{ 1'b0, axi_strb }; + // Verilator lint_on UNUSED + end endgenerate + // }}} + + // lastid_fifo + // {{{ + assign lastid_fifo_wr = (o_wb_stb && (i_wb_err || !i_wb_stall)) + ||(err_state && midissue && !lastid_fifo_full); + + sfifo #(.BW(C_AXI_ID_WIDTH+1), .LGFLEN(LGFIFO)) + lastid_fifo(S_AXI_ACLK, w_reset, + lastid_fifo_wr, + { axi_id, last_stb }, + lastid_fifo_full, lastid_fifo_fill, + S_AXI_RVALID && S_AXI_RREADY, + { S_AXI_RID, S_AXI_RLAST }, + lastid_fifo_empty); + // }}} + + // read_data + // {{{ + generate if (OPT_SWAP_ENDIANNESS) + begin : SWAP_ENDIANNESS + integer ik; + + // AXI is little endian. WB can be either. Most of my WB + // work is big-endian. + // + // This will convert byte ordering between the two + always @(*) + for(ik=0; ik<C_AXI_DATA_WIDTH/8; ik=ik+1) + read_data[ik*8 +: 8] + = i_wb_data[(C_AXI_DATA_WIDTH-(ik+1)*8) +: 8]; + + end else begin : KEEP_ENDIANNESS + + always @(*) + read_data = i_wb_data; + + end endgenerate + // }}} + + // resp_fifo + // {{{ + sfifo #(.BW(C_AXI_DATA_WIDTH+1), .LGFLEN(LGFIFO)) + resp_fifo(S_AXI_ACLK, w_reset, + o_wb_cyc && (i_wb_ack || i_wb_err), { read_data, i_wb_err }, + resp_fifo_full, resp_fifo_fill, + S_AXI_RVALID && S_AXI_RREADY, + { S_AXI_RDATA, response_err }, + resp_fifo_empty); + // }}} + + // acks_expected + // {{{ + // Count the number of acknowledgements we are still expecting. This + // is to support the last_ack calculation in the next process + initial acks_expected = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || err_state) + acks_expected <= 0; + else case({ accept_request, o_wb_cyc && i_wb_ack }) + 2'b01: acks_expected <= acks_expected -1; + 2'b10: acks_expected <= acks_expected+({{(LGFIFO){1'b0}},skid_arlen} + 1); + 2'b11: acks_expected <= acks_expected+{{(LGFIFO){1'b0}},skid_arlen}; + default: begin end + endcase + // }}} + + // last_ack + // {{{ + // last_ack should be true if the next acknowledgment will end the bus + // cycle + initial last_ack = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || err_state) + last_ack <= 1; + else case({ accept_request, i_wb_ack }) + 2'b01: last_ack <= (acks_expected <= 2); + 2'b10: last_ack <= (acks_expected == 0)&&(skid_arlen == 0); + 2'b11: last_ack <= (acks_expected < 2)&&(skid_arlen < 2) + &&(!acks_expected[0]||!skid_arlen[0]); + default: begin end + endcase + // }}} + + // fifo_nearfull + // {{{ + initial fifo_nearfull = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + fifo_nearfull <= 1'b0; + else case({ lastid_fifo_wr, S_AXI_RVALID && S_AXI_RREADY }) + 2'b10: fifo_nearfull <= (lastid_fifo_fill >= (1<<LGFIFO)-2); + 2'b01: fifo_nearfull <= lastid_fifo_full; + default: begin end + endcase + // }}} + + // wb_empty + // {{{ + initial wb_empty = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !o_wb_cyc) + wb_empty <= 1'b1; + else case({ lastid_fifo_wr, (i_wb_ack || i_wb_err) }) + 2'b10: wb_empty <= 1'b0; + 2'b01: wb_empty <= (lastid_fifo_fill == resp_fifo_fill + 1); + default: begin end + endcase + // }}} + + // S_AXI_RRESP, S_AXI_RVALID + // {{{ + always @(*) + begin + S_AXI_RRESP[0] = 1'b0; + S_AXI_RRESP[1] = response_err || (resp_fifo_empty && err_state); + + + S_AXI_RVALID = !resp_fifo_empty + || (err_state && !lastid_fifo_empty); + end + // }}} + + // err_state + // {{{ + initial err_state = 0; + always @(posedge S_AXI_ACLK) + if (w_reset) + err_state <= 1'b0; + else if ((o_wb_cyc)&&(i_wb_err)) + err_state <= 1'b1; + else if (lastid_fifo_empty && !midissue) + err_state <= 1'b0; + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_ARLOCK, S_AXI_ARCACHE, + S_AXI_ARPROT, S_AXI_ARQOS, resp_fifo_full }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +// +// The following are the formal properties used to verify this core. +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The following are a subset of the properties used to verify this + // core. + // + //////////////////////////////////////////////////////////////////////// + // + // + localparam DW = C_AXI_DATA_WIDTH; + + reg f_past_valid; + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + //////////////////////////////////////////////////////////////////////// + // + // Assumptions + // + // + always @(*) + if (!f_past_valid) + assume(w_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Ad-hoc assertions + // + // + + //////////////////////////////////////////////////////////////////////// + // + // Error state + // + // + always @(*) + if (err_state) + assert(!o_wb_stb && !o_wb_cyc); + + //////////////////////////////////////////////////////////////////////// + // + // Bus properties + // + // + + localparam F_LGDEPTH = (LGFIFO>8) ? LGFIFO+1 : 10, F_LGRDFIFO = 72; // 9*F_LGFIFO; + // + // ... + // + wire [(F_LGDEPTH-1):0] + fwb_nreqs, fwb_nacks, fwb_outstanding; + + fwb_master #(.AW(AW), .DW(C_AXI_DATA_WIDTH), .F_MAX_STALL(2), + .F_MAX_ACK_DELAY(3), .F_LGDEPTH(F_LGDEPTH), + .F_OPT_DISCONTINUOUS(1)) + fwb(S_AXI_ACLK, w_reset, + o_wb_cyc, o_wb_stb, 1'b0, o_wb_addr, {(DW){1'b0}}, 4'hf, + i_wb_ack, i_wb_stall, i_wb_data, i_wb_err, + fwb_nreqs, fwb_nacks, fwb_outstanding); + + always @(*) + if (err_state) + assert(fwb_outstanding == 0); + + + faxi_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .F_LGDEPTH(F_LGDEPTH), + .F_AXI_MAXSTALL(0), + .F_AXI_MAXDELAY(0) + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + .i_axi_awready(1'b0), + .i_axi_awaddr(0), + .i_axi_awlen(8'h0), + .i_axi_awsize(3'h0), + .i_axi_awburst(2'h0), + .i_axi_awlock(1'b0), + .i_axi_awcache(4'h0), + .i_axi_awprot(3'h0), + .i_axi_awqos(4'h0), + .i_axi_awvalid(1'b0), + // + .i_axi_wready(1'b0), + .i_axi_wdata(0), + .i_axi_wstrb(0), + .i_axi_wlast(0), + .i_axi_wvalid(1'b0), + // + .i_axi_bid(0), + .i_axi_bresp(0), + .i_axi_bvalid(1'b0), + .i_axi_bready(1'b0), + // + .i_axi_arready(S_AXI_ARREADY), + .i_axi_arid(S_AXI_ARID), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arlen(S_AXI_ARLEN), + .i_axi_arsize(S_AXI_ARSIZE), + .i_axi_arburst(S_AXI_ARBURST), + .i_axi_arlock(S_AXI_ARLOCK), + .i_axi_arcache(S_AXI_ARCACHE), + .i_axi_arprot(S_AXI_ARPROT), + .i_axi_arqos(S_AXI_ARQOS), + .i_axi_arvalid(S_AXI_ARVALID), + // + .i_axi_rresp(S_AXI_RRESP), + .i_axi_rid(S_AXI_RID), + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rlast(S_AXI_RLAST), + .i_axi_rready(S_AXI_RREADY) + // + // ... + // + ); + + // + // ... + // + + always @(*) + if (!resp_fifo_empty && response_err) + assert(resp_fifo_fill == 1); + + always @(*) + assert(midissue == ((stblen > 0)||(last_stb))); + + always @(*) + if (last_stb && !err_state) + assert(o_wb_stb || lastid_fifo_full); + + always @(*) + if (last_stb) + assert(stblen == 0); + + always @(*) + if (lastid_fifo_full) + begin + assert(!o_wb_stb); + assert(!lastid_fifo_wr); + end + + always @(*) + if (!err_state) + begin + if (midissue && !last_stb) + assert(!last_ack); + + assert(lastid_fifo_fill - resp_fifo_fill + == fwb_outstanding); + + if (fwb_outstanding > 1) + assert(!last_ack); + else if (fwb_outstanding == 1) + assert(midissue || last_ack); + else if (o_wb_cyc) // && (fwb_outstanding ==0) + assert(last_ack == last_stb); + + if (midissue) + assert(o_wb_cyc); + end + + // wb_empty + // {{{ + always @(*) + if (o_wb_cyc) + assert(wb_empty == (lastid_fifo_fill == resp_fifo_fill)); + // }}} + + // !o_wb_cyc, if nothing pending + // {{{ + always @(*) + if ((fwb_nacks == fwb_nreqs)&&(!midissue)) + assert(!o_wb_cyc); + // }}} + + always @(*) + assert(fwb_outstanding <= (1<<LGFIFO)); + + // fifo_nearfull + // {{{ + always @(*) + assert(fifo_nearfull == (lastid_fifo_fill >= (1<<LGFIFO)-1)); + // }}} + + always @(*) + if (o_wb_stb) + assert(last_ack == (last_stb&& wb_empty));//&&(!i_wb_stall); + else if (o_wb_cyc && !midissue) + assert(last_ack == (resp_fifo_fill + 1 >= lastid_fifo_fill)); + + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [3:0] cvr_reads, cvr_read_bursts, cvr_rdid_bursts; + reg [C_AXI_ID_WIDTH-1:0] cvr_read_id; + + // cvr_reads + // {{{ + initial cvr_reads = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_reads <= 1; + else if (i_wb_err) + cvr_reads <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && !cvr_reads[3] + && cvr_reads > 0) + cvr_reads <= cvr_reads + 1; + // }}} + + // cvr_read_bursts + // {{{ + initial cvr_read_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_bursts <= 1; + else if (S_AXI_ARVALID && S_AXI_ARLEN < 1) + cvr_read_bursts <= 0; + else if (i_wb_err) + cvr_read_bursts <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST + && !cvr_read_bursts[3] && cvr_read_bursts > 0) + cvr_read_bursts <= cvr_read_bursts + 1; + // }}} + + // cvr_read_id + // {{{ + initial cvr_read_id = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_read_id <= 1; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + cvr_read_id <= cvr_read_id + 1; + // }}} + + // cvr_rdid_bursts + // {{{ + initial cvr_rdid_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_rdid_bursts <= 1; + else if (S_AXI_ARVALID && S_AXI_ARLEN < 1) + cvr_rdid_bursts <= 0; + else if (i_wb_err) + cvr_rdid_bursts <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST + && S_AXI_RID == cvr_read_id + && !cvr_rdid_bursts[3] && cvr_rdid_bursts > 0) + cvr_rdid_bursts <= cvr_rdid_bursts + 1; + // }}} + + always @(*) + cover(cvr_reads == 4); + + always @(*) + cover(cvr_read_bursts == 4); + + always @(*) + cover(cvr_rdid_bursts == 4); + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/aximwr2wbsp.v b/rtl/wb2axip/aximwr2wbsp.v new file mode 100644 index 0000000..074467c --- /dev/null +++ b/rtl/wb2axip/aximwr2wbsp.v @@ -0,0 +1,751 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: aximwr2wbsp.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Convert the three AXI4 write channels to a single wishbone +// channel to write the results. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2015-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module aximwr2wbsp #( + // {{{ + parameter C_AXI_ID_WIDTH = 6, + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ADDR_WIDTH = 28, + parameter [0:0] OPT_SWAP_ENDIANNESS = 1'b0, + localparam AXI_LSBS = $clog2(C_AXI_DATA_WIDTH)-3, + localparam AW = C_AXI_ADDR_WIDTH-AXI_LSBS, + + parameter LGFIFO = 5 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, // System clock + input wire S_AXI_ARESETN, + // Incoming AXI bus connections + // {{{ + // AXI write address channel signals + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [7:0] S_AXI_AWLEN, + input wire [2:0] S_AXI_AWSIZE, + input wire [1:0] S_AXI_AWBURST, + input wire [0:0] S_AXI_AWLOCK, + input wire [3:0] S_AXI_AWCACHE, + input wire [2:0] S_AXI_AWPROT, + input wire [3:0] S_AXI_AWQOS, + + // AXI write data channel signals + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + + // AXI write response channel signals + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [1:0] S_AXI_BRESP, + // }}} + // Downstream wishbone bus + // {{{ + // We'll share the clock and the reset + output reg o_wb_cyc, + output reg o_wb_stb, + output reg [(AW-1):0] o_wb_addr, + output reg [(C_AXI_DATA_WIDTH-1):0] o_wb_data, + output reg [(C_AXI_DATA_WIDTH/8-1):0] o_wb_sel, + input wire i_wb_stall, + input wire i_wb_ack, + // input [(C_AXI_DATA_WIDTH-1):0] i_wb_data, + input wire i_wb_err + // }}} + + // }}} + ); + + // Register/net declarations + // {{{ + localparam DW = C_AXI_DATA_WIDTH; + wire w_reset; + + wire skid_awvalid; + reg accept_write_burst; + wire [C_AXI_ID_WIDTH-1:0] skid_awid; + wire [C_AXI_ADDR_WIDTH-1:0] skid_awaddr; + wire [7:0] skid_awlen; + wire [2:0] skid_awsize; + wire [1:0] skid_awburst; + // + wire skid_wvalid, skid_wlast; + reg skid_wready; + wire [C_AXI_DATA_WIDTH-1:0] skid_wdata; + wire [C_AXI_DATA_WIDTH/8-1:0] skid_wstrb; + + reg skid_awready; + reg [7:0] axi_wlen, wlen; + reg [C_AXI_ID_WIDTH-1:0] axi_wid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_waddr; + wire [C_AXI_ADDR_WIDTH-1:0] next_addr; + reg [1:0] axi_wburst; + reg [2:0] axi_wsize; + + reg [LGFIFO+7:0] acks_expected; + reg [LGFIFO:0] writes_expected; + reg last_ack; + reg err_state; + + reg read_ack_fifo; + wire [7:0] fifo_ack_ln; + reg [8:0] acklen; + reg ack_last, ack_err, ack_empty; + + reg [LGFIFO:0] total_fifo_fill; + reg total_fifo_full; + + wire wb_ack_fifo_full, wb_ack_fifo_empty; + wire [LGFIFO:0] wb_ack_fifo_fill; + + wire err_fifo_full, err_fifo_empty; + wire [LGFIFO:0] err_fifo_fill; + reg err_fifo_write; + + wire bid_fifo_full, bid_fifo_empty; + wire [LGFIFO:0] bid_fifo_fill; + + reg [8:0] next_acklen; + reg [1:0] next_acklow; + + assign w_reset = (S_AXI_ARESETN == 1'b0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Skid buffers--all incoming signals go throug skid buffers + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // write address skid buffer + // {{{ + skidbuffer #( + .OPT_OUTREG(0), + .DW(C_AXI_ADDR_WIDTH+C_AXI_ID_WIDTH+8+3+2)) + awskid(S_AXI_ACLK, !S_AXI_ARESETN, S_AXI_AWVALID, S_AXI_AWREADY, + { S_AXI_AWID, S_AXI_AWADDR, S_AXI_AWLEN, + S_AXI_AWSIZE, S_AXI_AWBURST }, + skid_awvalid, accept_write_burst, + { skid_awid, skid_awaddr, skid_awlen, + skid_awsize, skid_awburst }); + // }}} + + // write channel skid buffer + // {{{ + skidbuffer #( +`ifdef FORMAL + .OPT_PASSTHROUGH(1'b1), +`endif + .OPT_OUTREG(0), + .DW(C_AXI_DATA_WIDTH + C_AXI_DATA_WIDTH/8+1)) + wskid(S_AXI_ACLK, !S_AXI_ARESETN, + S_AXI_WVALID, S_AXI_WREADY, + { S_AXI_WDATA, S_AXI_WSTRB, S_AXI_WLAST }, + skid_wvalid, skid_wready, + { skid_wdata, skid_wstrb, skid_wlast }); + // }}} + + // accept_write_burst + // {{{ + always @(*) + begin + accept_write_burst = (skid_awready)&&(!o_wb_stb || !i_wb_stall) + &&(!err_state)&&(skid_awvalid) + &&(!total_fifo_full); + if (axi_wid != skid_awid && (acks_expected > 0)) + accept_write_burst = 0; + if (!skid_wvalid) + accept_write_burst = 0; + end + // }}} + + // skid_wready + // {{{ + always @(*) + skid_wready = (!o_wb_stb || !i_wb_stall || err_state) + &&(!skid_awready || accept_write_burst); + // }}} + + // skid_awready + // {{{ + initial skid_awready = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + skid_awready <= 1'b1; + else if (accept_write_burst) + skid_awready <= (skid_awlen == 0)&&(skid_wvalid)&&(skid_wlast); + else if (skid_wvalid && skid_wready && skid_wlast) + skid_awready <= 1'b1; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Burst unwinding + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // axi_w*, wlen -- properties of the currently active burst + // {{{ + always @(posedge S_AXI_ACLK) + if (accept_write_burst) + begin + axi_wid <= skid_awid; + axi_waddr <= skid_awaddr; + axi_wsize <= skid_awsize; + axi_wburst <= skid_awburst; + axi_wlen <= skid_awlen; + wlen <= skid_awlen; + end else if (skid_wvalid && skid_wready) + begin + axi_waddr <= next_addr; + if (!skid_awready) + wlen <= wlen - 1; + end + // }}} + + // next_addr + // {{{ + axi_addr #(.AW(C_AXI_ADDR_WIDTH), .DW(C_AXI_DATA_WIDTH)) + next_write_addr(axi_waddr, axi_wsize, axi_wburst, axi_wlen, next_addr); + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Issue the Wishbone request + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_wb_cyc, o_wb_stb + // {{{ + initial { o_wb_cyc, o_wb_stb } = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || err_state || (o_wb_cyc && i_wb_err)) + begin + o_wb_cyc <= 1'b0; + o_wb_stb <= 1'b0; + end else if (accept_write_burst) + begin + o_wb_cyc <= 1'b1; + o_wb_stb <= skid_wvalid && skid_wready; + end else begin + if (!o_wb_stb || !i_wb_stall) + o_wb_stb <= (!skid_awready)&&(skid_wvalid&&skid_wready); + if (o_wb_cyc && last_ack && i_wb_ack && !skid_awvalid) + o_wb_cyc <= 0; + end + // }}} + + always @(*) + o_wb_addr = axi_waddr[C_AXI_ADDR_WIDTH-1:AXI_LSBS]; + + // o_wb_data, o_wb_sel + // {{{ + generate if (OPT_SWAP_ENDIANNESS) + begin : SWAP_ENDIANNESS + integer ik; + + always @(posedge S_AXI_ACLK) + if (!o_wb_stb || !i_wb_stall) + begin + for(ik=0; ik<DW/8; ik=ik+1) + begin + o_wb_data[ik*8 +: 8] + <= skid_wdata[(DW/8-1-ik)*8 +: 8]; + o_wb_sel[ik] <= skid_wstrb[DW/8-1-ik]; + end + end + + end else begin : KEEP_ENDIANNESS + + always @(posedge S_AXI_ACLK) + if (!o_wb_stb || !i_wb_stall) + begin + o_wb_data <= skid_wdata; + o_wb_sel <= skid_wstrb; + end + + end endgenerate + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // FIFO usage tracking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // writes_expected + // {{{ + initial writes_expected = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + writes_expected <= 0; + end else case({skid_wvalid && skid_wready && skid_wlast, + S_AXI_BVALID && S_AXI_BREADY }) + 2'b01: writes_expected <= writes_expected - 1; + 2'b10: writes_expected <= writes_expected + 1; + default: begin end + endcase + // }}} + + // acks_expected + // {{{ + initial acks_expected = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || i_wb_err || err_state) + begin + acks_expected <= 0; + end else case({skid_awvalid && accept_write_burst, {i_wb_ack|i_wb_err}}) + 2'b01: acks_expected <= acks_expected - 1; + 2'b10: acks_expected <= acks_expected + ({{(LGFIFO){1'b0}},skid_awlen} + 1); + 2'b11: acks_expected <= acks_expected + {{(LGFIFO){1'b0}},skid_awlen}; + default: begin end + endcase + // }}} + + // last_ack + // {{{ + initial last_ack = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || i_wb_err || err_state) + begin + last_ack <= 1; + end else case({skid_awvalid && accept_write_burst, i_wb_ack }) + 2'b01: last_ack <= (acks_expected <= 2); + 2'b10: last_ack <= (acks_expected == 0)&&(skid_awlen == 0); + 2'b11: last_ack <= last_ack && (skid_awlen == 0); + default: begin end + endcase + + // }}} + + // total_fifo_fill + // {{{ + initial total_fifo_fill = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + total_fifo_fill <= 0; + else case({ accept_write_burst, S_AXI_BVALID && S_AXI_BREADY }) + 2'b01: total_fifo_fill <= total_fifo_fill - 1; + 2'b10: total_fifo_fill <= total_fifo_fill + 1; + default: begin end + endcase + // }}} + + // total_fifo_full + // {{{ + always @(*) + total_fifo_full = total_fifo_fill[LGFIFO]; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Return channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wb_ack_fifo + // {{{ + sfifo #(.BW(8), .LGFLEN(LGFIFO), + .OPT_ASYNC_READ(1'b1)) + wb_ack_fifo(S_AXI_ACLK, !S_AXI_ARESETN, + accept_write_burst, skid_awlen, + wb_ack_fifo_full, wb_ack_fifo_fill, + read_ack_fifo, fifo_ack_ln, wb_ack_fifo_empty); + // }}} + + // read_ack_fifo + // {{{ + always @(*) + begin + read_ack_fifo = ack_last && (i_wb_ack || i_wb_err); + if (err_state || ack_empty) + read_ack_fifo = 1; + if (wb_ack_fifo_empty) + read_ack_fifo = 1'b0; + end + // }}} + + // next_acklen + // {{{ + always @(*) + next_acklen = fifo_ack_ln + ((acklen[0] ? 1:0) + + ((i_wb_ack|i_wb_err)? 0:1)); + // }}} + + // next_acklow + // {{{ + always @(*) + next_acklow = fifo_ack_ln[0] + ((acklen[0] ? 1:0) + + ((i_wb_ack|i_wb_err)? 0:1)); + // }}} + + // acklen, ack_last, ack_empty + // {{{ + initial acklen = 0; + initial ack_last = 0; + initial ack_empty = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || err_state) + begin + acklen <= 0; + ack_last <= 0; + ack_empty<= 1; + end else if (read_ack_fifo) + begin + acklen <= next_acklen; + ack_last <= (fifo_ack_ln < 2)&&(next_acklow == 1); + ack_empty<= (fifo_ack_ln == 0)&&(!acklen[0]) + &&(i_wb_ack || i_wb_err); + end else if (i_wb_ack || i_wb_err) + begin + if (acklen > 0) + acklen <= acklen - 1; + ack_last <= (acklen == 2); + ack_empty <= ack_last; + end + // }}} + + // ack_err + // {{{ + always @(posedge S_AXI_ACLK) + if (read_ack_fifo) + begin + ack_err <= (wb_ack_fifo_empty) || err_state || i_wb_err; + end else if (i_wb_ack || i_wb_err || err_state) + ack_err <= ack_err || (i_wb_err || err_state); + // }}} + + // err_state + // {{{ + initial err_state = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + err_state <= 0; + else if (o_wb_cyc && i_wb_err) + err_state <= 1; + else if ((total_fifo_fill == bid_fifo_fill) + &&(total_fifo_fill == err_fifo_fill)) + err_state <= 0; + // }}} + + // err_fifo_write + // {{{ + initial err_fifo_write = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + err_fifo_write <= 0; + else if (read_ack_fifo && ack_empty && fifo_ack_ln == 0) + err_fifo_write <= (i_wb_ack || i_wb_err || err_state); + else if (ack_last) + err_fifo_write <= (i_wb_ack || i_wb_err || err_state); + else + err_fifo_write <= 1'b0; + // }}} + + // bid_fifo - Keep track of BID's + // {{{ + sfifo #(.BW(C_AXI_ID_WIDTH), .LGFLEN(LGFIFO)) + bid_fifo(S_AXI_ACLK, !S_AXI_ARESETN, + skid_wvalid && skid_wready && skid_wlast, + (total_fifo_fill == bid_fifo_fill) ? skid_awid:axi_wid, + bid_fifo_full, bid_fifo_fill, + S_AXI_BVALID & S_AXI_BREADY, S_AXI_BID, bid_fifo_empty); + // }}} + + // err_fifo - Keep track of error returns + // {{{ + sfifo #(.BW(1), .LGFLEN(LGFIFO)) + err_fifo(S_AXI_ACLK, !S_AXI_ARESETN, + err_fifo_write, { ack_err || i_wb_err }, + err_fifo_full, err_fifo_fill, + S_AXI_BVALID & S_AXI_BREADY, S_AXI_BRESP[1], err_fifo_empty); + // }}} + + assign S_AXI_BVALID = !bid_fifo_empty && !err_fifo_empty; + assign S_AXI_BRESP[0]= 1'b0; + // }}} + + // Make Verilator happy + // {{{ + // verilator lint_on UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWBURST, S_AXI_AWSIZE, + S_AXI_AWLOCK, S_AXI_AWCACHE, S_AXI_AWPROT, + S_AXI_AWQOS, S_AXI_WLAST, + wb_ack_fifo_full, wb_ack_fifo_fill, + bid_fifo_full, err_fifo_full, + w_reset + }; + // verilator lint_off UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The following are a subset of the properties used to verify this + // core + // + //////////////////////////////////////////////////////////////////////// + // + // + // Formal only register/wire/parameter definitions + // {{{ + localparam F_LGDEPTH = (LGFIFO>8) ? LGFIFO+1 : 10, + F_LGRDFIFO = 72; // 9*F_LGFIFO; + reg f_past_valid; + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + localparam F_LGDEPTH = (LGFIFO>8) ? LGFIFO+1 : 10, F_LGRDFIFO = 72; // 9*F_LGFIFO; + wire [(F_LGDEPTH-1):0] + fwb_nreqs, fwb_nacks, fwb_outstanding; + + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Wishbone properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + fwb_master #( + // {{{ + .AW(AW), .DW(C_AXI_DATA_WIDTH), .F_MAX_STALL(2), + .F_MAX_ACK_DELAY(3), .F_LGDEPTH(F_LGDEPTH), + .F_OPT_DISCONTINUOUS(1) + // }}} + ) fwb(S_AXI_ACLK, w_reset, + // {{{ + o_wb_cyc, o_wb_stb, 1'b1, o_wb_addr, o_wb_data, o_wb_sel, + i_wb_ack, i_wb_stall, {(DW){1'b0}}, i_wb_err, + fwb_nreqs, fwb_nacks, fwb_outstanding + // }}} + ); + + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI bus properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxi_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .F_LGDEPTH(F_LGDEPTH), + .F_AXI_MAXSTALL(0), + .F_AXI_MAXDELAY(0) + // }}} + ) faxi(.i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // {{{ + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awid( S_AXI_AWID), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awlen( S_AXI_AWLEN), + .i_axi_awsize( S_AXI_AWSIZE), + .i_axi_awburst(S_AXI_AWBURST), + .i_axi_awlock( S_AXI_AWLOCK), + .i_axi_awcache(S_AXI_AWCACHE), + .i_axi_awprot( S_AXI_AWPROT), + .i_axi_awqos( S_AXI_AWQOS), + .i_axi_awvalid(S_AXI_AWVALID), + // + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + .i_axi_wlast( S_AXI_WLAST), + .i_axi_wvalid(S_AXI_WVALID), + // + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + // + .i_axi_arready(1'b0), + .i_axi_arid( {(C_AXI_ID_WIDTH){1'b0}}), + .i_axi_araddr({(C_AXI_ADDR_WIDTH){1'b0}}), + .i_axi_arlen( 8'h0), + .i_axi_arsize( 3'h0), + .i_axi_arburst(2'h0), + .i_axi_arlock( 1'b0), + .i_axi_arcache(4'h0), + .i_axi_arprot( 3'h0), + .i_axi_arqos( 4'h0), + .i_axi_arvalid(1'b0), + // + .i_axi_rresp( 2'h0), + .i_axi_rid( {(C_AXI_ID_WIDTH){1'b0}}), + .i_axi_rvalid(1'b0), + .i_axi_rdata( {(C_AXI_DATA_WIDTH){1'b0}}), + .i_axi_rlast( 1'b0), + .i_axi_rready(1'b0) + // + // ... + // + ); + + + // never_err control(s) + // {{{ + always @(*) + if (never_err) + begin + assume(!i_wb_err); + assert(!err_state); + if (!skid_awvalid) + assert(o_wb_cyc == (acks_expected != 0)); + if (!skid_awready) + assert(o_wb_cyc); + if (S_AXI_BVALID) + assert(!S_AXI_BRESP[1]); + assert(!S_AXI_BRESP[0]); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Cover registers + // {{{ + reg [3:0] cvr_writes, cvr_write_bursts, + cvr_wrid_bursts; + reg [C_AXI_ID_WIDTH-1:0] cvr_write_id; + // }}} + + // cvr_writes + // {{{ + initial cvr_writes = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_writes <= 1; + else if (i_wb_err) + cvr_writes <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY && !cvr_writes[3] + && cvr_writes > 0) + cvr_writes <= cvr_writes + 1; + // }}} + + // cvr_write_bursts + // {{{ + initial cvr_write_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_write_bursts <= 1; + else if (S_AXI_AWVALID && S_AXI_AWLEN < 1) + cvr_write_bursts <= 0; + else if (i_wb_err) + cvr_write_bursts <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY + && !cvr_write_bursts[3] && cvr_write_bursts > 0) + cvr_write_bursts <= cvr_write_bursts + 1; + // }}} + + // cvr_write_id + // {{{ + initial cvr_write_id = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_write_id <= 1; + else if (S_AXI_BVALID && S_AXI_BREADY) + cvr_write_id <= cvr_write_id + 1; + // }}} + + // cvr_wrid_bursts + // {{{ + initial cvr_wrid_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_wrid_bursts <= 1; + else if (S_AXI_AWVALID && S_AXI_AWLEN < 1) + cvr_wrid_bursts <= 0; + else if (i_wb_err) + cvr_wrid_bursts <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY + && S_AXI_BID == cvr_write_id + && !cvr_wrid_bursts[3] && cvr_wrid_bursts > 0) + cvr_wrid_bursts <= cvr_wrid_bursts + 1; + // }}} + + always @(*) cover(cvr_writes == 4); + always @(*) cover(cvr_write_bursts == 4); + always @(*) cover(cvr_wrid_bursts == 4); + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axiperf.v b/rtl/wb2axip/axiperf.v new file mode 100644 index 0000000..f2498e6 --- /dev/null +++ b/rtl/wb2axip/axiperf.v @@ -0,0 +1,1603 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axiperf +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Measure the performance of a high speed AXI interface. The +// {{{ +// following monitor requires connecting to both an AXI-lite slave +// interface, as well as a second AXI interface as a monitor. The AXI +// monitor interface is read only, and (ideally) shouldn't be corrupted by +// the inclusion of the AXI-lite interface on the same bus. +// +// The core works by counting clock cycles in a fashion that should +// supposedly make it easy to calculate 1) throughput, and 2) lag. +// Moreover, the counters are arranged such that after the fact, the +// various contributors to throughput can be measured and evaluted: was +// the slave the holdup? Or was it the master? +// +// To use the core, connect it and build your design. Then write a '3' +// to register 15 (address 60). Once the bus returns to idle, the core +// will begin capturing data and statistics. When done, write a '0' to +// the same register. This will create a stop request. Once the bus +// comes to a stop, the core will stop accumulating values into its +// statistics. Those statistics can then be read out from the AXI-lite +// bus and analyzed. +// }}} +// Goals: +// {{{ +// My two biggest goals are to measure throughput and lag. Defining those +// two measures, of course, is half the battle. The other half of the +// battle is knowing which side to blame for any particular issue. +// +// Let's start with the total time required for any transaction. This +// equals the time from the indication of a request to the last response. +// We'll use a linear model to describe this transaction time: +// +// Transaction time = Latency + (Beats in transaction) / Throughput +// +// The goal of this core is to help you identify latency and throughput +// numbers. +// +// One measure might be to take the total number of clock cycles, from when +// the core was enabled to when it was disabled, and to divide by the +// number of beats transmitted. +// +// (Poor) Throughput = (Total beats transferred) / (total time) +// +// In a heavily used bus, this might be a good enough measure. However, +// this is a poor measure for most systems where the bus is idle most of +// the time. Instead, it might be nice to start the measurement early +// on during some task, and conclude it much later. In the meantime, the +// bus might go from idle to busy and back again many times. For example, +// you don't want to copy information from the disk drive if you haven't +// made a request of the controller. For these reasons, we try to achieve +// a better measurement. +// +// Here's the basic approach: we'll look at all of the clocks associated +// with any particular type of transaction, and lump them into a couple +// of categories: latency limiting clocks and throughput limiting clocks. +// We'll then divide the latency limiting clocks by the number of bursts +// that have taken place, and divide the total number of beats by the +// time taken to transmit them. +// +// Latency = (latency measures) / (bursts) +// Throughput = (beats) / (transmission duration, inc. beats) +// +// In general, we'll define the transmission duration as the time from the +// first clock cycle that RVALID (or WVALID) is raised until the final +// cycle when RVALID && RREADY && RLAST (or WVALID && WREADY && WLAST). +// Unless we know otherwise, all clock cycles between these two will +// be marked as a transmission duration clock cycles. The exception +// to this rule, however, is the W* channel where one or two W* +// transactions might take place prior to the first AW* transaction. In +// this case, any idle cycles during this time are marked as a latency +// measure, not a throughput measure of transmission duration. +// +// Latency measures, on the other hand, are anything that appear to be +// burst related--such as the time from the request to the first +// RVALID (or WVALID), or similarly the time from the last WVALID && WLAST +// until the final BVALID && BREADY. +// +// These measures are listed in more detail below. +// +// Certain measures below are marked as *ORTHOGONAL*. These are perhaps +// better known as (independent), but I started calling them orthogonal +// and ... will probably do so for some time. Orthogonal measures are +// those that don't overlap. For example, if you just counted AWVALID +// && AWREADY (bursts) and WVALID && WREADY clock cycles (beats), you might +// get a big overlap between the two and so not know which to count. Not +// so with the orthogonal measures. +// +// Further, at the end of every list of orthogonal measures is a metric +// that can be used to calculate total cycles used--that way you know +// how the measures relate. +// }}} +// Registers +// {{{ +// 0: Active time +// Number of clock periods that the performance monitor has been +// accumulating data for. This register has a protection measure +// built into it: if it ever overflows, it will report an +// 8'hffff_fff value. This is your indication that other values +// from within this performance measure are not to be trusted. +// Either increase LGCNT or try a shorter collection time to fix +// such a condition. +// +// 4: Max bursts +// Bits 31:24 -- the maximum number of outstanding write bursts at any +// given time. A write burst begins with either +// AWVALID && AWREADY or WVALID && WREADY and ends with +// BVALID && BREADY. This will be the maximum of the two. +// Bits 23:16 -- the maximum number of outstanding read bursts at any +// given time. A read burst begins with ARVALID &&ARREADY, +// and ends with RVALID && RLAST +// Bits 15: 8 -- the maximum write burst size seen, as captured by AWLEN +// Bits 7: 0 -- the maximum read burst size seen, as captured by ARLEN +// 8: Write idle cycles +// Number of cycles where the write channel is totally idle. +// *ORTHOGONAL* +// 12: AWBurst count +// Number of AWVALID && AWREADY's +// 16: Write beat count +// Number of write beats, WVALID && WREADYs +// 20: AW Byte count +// Number of bytes written, as recorded by the AW* channel (not the +// W* channel and WSTRB signals) +// 24: Write Byte count +// Number of bytes written, as recorded by the W* channel and the +// non zero WSTRB's +// 28: Write slow data +// Number of cycles where a write has started, that is WVALID +// and WREADY (but !WLAST) have been seen, but yet WVALID is now +// low. These are only counted if a write address request has +// already been received--otherwise this would be considered +// a latency measure on the AW* channel. +// *ORTHOGONAL* +// 32: wr_stall--Write stalls +// Counts the number of cycles where WVALID && !WREADY, but +// only if AWVALID is true or has been true. This is to +// distinguish from stalls which may take place before AWVALID, +// where the slave may be waiting on AWVALID (lag) versus +// unable to handle the throuhgput. (Those are counted under +// wr_early_stall below ...) +// *ORTHOGONAL* +// 36: wr_addr_lag--Write address channel lagging +// Counts the number of cycles where the write data has been +// present on the channel prior to the write address. This +// includes cycles where AWVALID is true or stalled, just not +// cycles where WVALID is also true--since those have already +// been counted. +// *ORTHOGONAL* +// 40: wr_data_lag--Write data laggging +// The AWVALID && AWREADY has been received, but no data has +// yet been received for this write burst and WVALID remains +// low. (i.e., no BVALIDs are pending either.) This is a +// lag measure since WVALID hasn't shown up (yet) to start sending +// data. +// *ORTHOGONAL* +// 44: wr_awr_early--AWVALID && AWREADY, but only if !WVALID and +// no AWVALID has yet been received. This is a lag measure since +// AWVALID is preceding WVALID. +// *ORTHOGONAL* +// 48: wr_early_beat--WVALID && WREADY && !AWVALID, and also prior to +// any AWVALID. This value is double counted in the write +// beat counts, so you will need to subtract the two if you +// wish to separate them. +// *Otherwise ORTHOGONAL* +// 52: wr_addr_stall--AWVALID && !AWREADY, but only if !WVALID and +// no AWVALID has yet been received. (This keeps it from being +// double counted as part of a throughput measure.) +// *ORTHOGONAL* +// 56: wr_early_stall--WVALID && !WREADY, but only if this burst has +// not yet started and no AWVALID has yet been received. That +// makes this a lag measure, since the slave is likely waiting +// for the address before starting to process the burst. +// *ORTHOGONAL* +// 60: b_lag_count +// Counts the number of cycles between the last accepted AWVALID +// and WVALID && WLAST and its corresponding BVALID. This is +// the number of cycles where BVALID could be high in response +// to any burst, but yet where it isn't. To avoid interfering +// with the throughput measure, this excludes any cycles where +// WVALID is also true. +// *ORTHOGONAL* +// 64: b_stall_count +// Number of cycles where BVALID && !BREADY. This could be a +// possible indication of backpressure in the interconnect. +// This also excludes any cycles where WVALID is also true. +// *ORTHOGONAL* +// 68: b_end_count +// Number of cycles where BVALID && BREADY, but where nothing +// else is outstanding and where no WVALID is being requested. +// This just completes our measurements, making sure that all +// beats of a transaction are properly accounted for. +// *ORTHOGONAL* +// +// 72: Write Bias (Signed) +// Total number of cycles between the first AWVALID and the +// first WVALID, minus the total number of cycles between the +// first WVALID and the first AWVALID. This is a measure of +// how often AWV clock cycles come before the first WV cycle and +// by how much. To make use of this statistic, divide it by the +// total number of bursts for the average distance between the +// first AWV and the first WV. Note that unlike many of these +// statistics, this value is signed. Negative distances are +// possible if the first WV tends to precede the first AWV. +// +// Total write cycles = (active_time - wr_b_end_count - wr_idle_cycles) +// = (wr_addr_lag+wr_data_lag+wr_awr_early+wr_early_beat +// + wr_addr_stall + wr_b_lag_count + wr_b_stall_count) +// + (wr_slow_data + wr_stall + wr_beats - wr_early_beats) +// +// Latency = (wr_addr_lag + wr_data_lag + wr_awr_early + wr_early_beat +// + wr_addr_stall + wr_b_lag + wr_b_stall) / WR BURSTS +// Throughput= (wr_beats) / +// (wr_slow_data + wr_stall + wr_beats - wr_early_beats) +// +// 80: Read idle cycles +// Number of clock cycles, while the core is collecting, where +// nothing is happening on the read channel--ARVALID is low, +// nothing is outstanding, etc. *ORTHOGONAL* +// 84: Max responding bursts +// This is the maximum number of bursts that have been responding +// at the same time, as counted by the maximum number of ID's +// which have seen an RVALID but not RLAST. It's an estimate of +// how out of order the channel has become. +// 88: Read burst count +// The total number of RVALID && RREADY && RLAST's seen +// 92: Read beat count +// The total number of beats requested, as measured by +// RVALID && RREADY (or equivalently by ARLEN ... but we measure +// RVALID && RREADY here). *ORTHOGONAL* +// 96: Read byte count +// The total number of bytes requested, as measured by ARSIZE +// and ARLEN. +// 100: AR cycles +// Total number of cycles where the interface is idle, but yet +// ARVALID && ARREADY are both true. Yes, it'll be busy on the +// next cycle, but we still need to count them. +// *ORTHOGONAL* +// 104: AR stalls +// Total number of clock cycles where ARVALID && !ARREADY, but +// only under the condition that nothing is currently outstanding. +// If the master refuses to allow a second AR* burst into the +// pipeline, this should show in the maximum number of outstanding +// read bursts ever allowed. *ORTHOGONAL* +// 108: R stalls +// Total number of clock cycles where RVALID && !RREADY. This is +// an indication of a master that has issued more read requests +// than it can process, and so it is suffering from internal +// back pressure. *ORTHOGONAL* +// 112: Read Lag counter +// Counts the number of clock cycles where an outstanding read +// request exists, but for which no data has (yet) been returned. +// *ORTHOGONAL* +// 116: Slow link +// Counts the number of clock cycles where RVALID is low, but yet +// a burst return has already started but not yet been completed. +// *ORTHOGONAL* +// +// If we've done this right, then +// +// active_time == read idle cycles (channel is idle) +// + read_beat_count (data is transferred)_ +// + r stalls (Master isn't ready) +// + lag counter (No data is ready) +// + slow link (Slave isn't ready)) +// + rd_ar_stalls (Slave not ready for AR*) +// + rd_ar_cycles (Slave accepted AR*, o.w. idle) +// +// We can then measure read throughput as the number of +// active cycles (active time - read idle counts) divided by the +// number of bytes (or beats) transferred (depending upon the +// units you want. +// +// Lag would be measured by the lag counter divided by the number +// of read bursts. +// +// 120: Read first lag +// This is another attempt to estimate the latency in a channel. +// Its a measure from ARVALID on an idle channel until RVALID. +// Other latency measures (above) can get confused by multiple +// transactions in progress at a time. This artificially lowers +// the latency from what the channel would otherwise produce, +// counting latency cycles as throughput cycles. On the other +// hand, if we count the number of cycles from idle to the first +// return, we can get a more conventional measure of latency. +// To use this statistic to get latency, divide it by the total +// number of AR Cycles. This works because those cycles are +// *only* counted if the channel is otherwise idle. +// +// 124: Control register +// Write a 1 to this register to start recording, and a 0 to this +// register to stop. Writing a 2 will clear the counters as +// well. +// +// This performance monitor depends on certain counters to make +// sure it can recognize when the bus is idle. If any of these +// counters overflow, then the core cannot tell when to start +// or stop counting and so all performance measures will then be +// invalid. If this happens, the perf_error (bit 3) will be set. +// This bit can only be cleared on a full bus reset--often +// requiring a power cycle. +// +// Performance: +// Write Throughput = (Wr Beats) / (Wr Beats + WrStalls + WrSlow); +// Read Throughput = (Rd Beats) / (Rd Beats + R Stalls + RSlow); +// Read Latency = (AR Stalls + RdLag) ./ (Rd Bursts) +// }}} +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axiperf #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + parameter C_AXIL_ADDR_WIDTH = 7, + localparam C_AXIL_DATA_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_ID_WIDTH = 4, + parameter [0:0] OPT_LOWPOWER = 0, + parameter LGCNT = 32 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // + // + // The AXI Monitor interface + // + input wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + input wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + input wire [7:0] M_AXI_AWLEN, + input wire [2:0] M_AXI_AWSIZE, + input wire [1:0] M_AXI_AWBURST, + input wire M_AXI_AWLOCK, + input wire [3:0] M_AXI_AWCACHE, + input wire [2:0] M_AXI_AWPROT, + input wire [3:0] M_AXI_AWQOS, + // + // + input wire M_AXI_WVALID, + input wire M_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + input wire M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + input wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // + input wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + input wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + input wire [7:0] M_AXI_ARLEN, + input wire [2:0] M_AXI_ARSIZE, + input wire [1:0] M_AXI_ARBURST, + input wire M_AXI_ARLOCK, + input wire [3:0] M_AXI_ARCACHE, + input wire [2:0] M_AXI_ARPROT, + input wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + input wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP + // + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam ADDRLSB = $clog2(C_AXIL_DATA_WIDTH/8); + wire i_reset = !S_AXI_ARESETN; + + // AXI signaling + // {{{ + wire axil_write_ready; + wire [C_AXIL_ADDR_WIDTH-ADDRLSB-1:0] awskd_addr; + // + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire axil_read_ready; + wire [C_AXIL_ADDR_WIDTH-ADDRLSB-1:0] arskd_addr; + reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + + wire awskd_valid, wskd_valid; + wire arskd_valid; + // }}} + + reg r_idle_bus, triggered, stop_request, + clear_request, start_request; + wire idle_bus; + reg [LGCNT:0] active_time; + reg wr_aw_err, wr_w_err, rd_err, perf_err; + + + // Write measures + // {{{ + reg [7:0] wr_max_burst_size; + reg [LGCNT-1:0] wr_awburst_count, wr_wburst_count, wr_beat_count; + reg [LGCNT-1:0] wr_aw_byte_count, wr_w_byte_count; + reg [7:0] wr_aw_outstanding, wr_w_outstanding, + wr_aw_max_outstanding, wr_w_max_outstanding, + wr_max_outstanding, wr_now_outstanding; + reg wr_aw_zero_outstanding, wr_w_zero_outstanding, + wr_in_progress; + reg [LGCNT-1:0] wr_idle_cycles, + wr_b_lag_count, wr_b_stall_count, wr_b_end_count, + wr_slow_data, wr_stall, wr_early_beat, // wr_beat, + wr_addr_lag, wr_data_lag, wr_awr_early, + wr_bias, wr_addr_stall, wr_early_stall; + reg[C_AXI_DATA_WIDTH/8:0] wstrb_count; + // }}} + + // Read measures + // {{{ + reg [LGCNT-1:0] rd_idle_cycles, rd_lag_counter, rd_slow_link, + rd_burst_count, rd_byte_count, rd_beat_count, + rd_ar_stalls, rd_r_stalls, rd_ar_cycles; + reg [7:0] rd_outstanding_bursts, rd_max_burst_size, + rd_max_outstanding_bursts; + reg [7:0] rd_outstanding_bursts_id [0:(1<<C_AXI_ID_WIDTH)-1]; + reg [(1<<C_AXI_ID_WIDTH)-1:0] rd_nonzero_outstanding_id, + rd_bursts_in_flight; + reg [C_AXI_ID_WIDTH:0] rd_total_in_flight, rd_responding, + rd_max_responding_bursts; + reg [LGCNT-1:0] rd_first_lag; + reg rd_first; + // }}} + + integer ik; + genvar gk; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write signaling + // + // {{{ + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_ADDR_WIDTH-ADDRLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXIL_BVALID || S_AXIL_BREADY); + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXIL_BREADY) + axil_bvalid <= 0; + + assign S_AXIL_BVALID = axil_bvalid; + assign S_AXIL_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIL_ADDR_WIDTH-ADDRLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXIL_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXIL_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXIL_RVALID = axil_read_valid; + assign S_AXIL_RDATA = axil_read_data; + assign S_AXIL_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK) + begin + if (idle_bus) + clear_request <= 1'b0; + if (!clear_request && idle_bus) + begin + start_request <= 0; + stop_request <= 0; + end + + if (axil_write_ready) + begin + case(awskd_addr) + 5'h1f: if (wskd_strb[0]) begin + // Start, stop, clear, reset + // + clear_request <= (clear_request && !idle_bus) + || (wskd_data[1] && !wskd_data[0]); + stop_request <= !wskd_data[0]; + start_request <= wskd_data[0] && (!stop_request); + end + default: begin end + endcase + end + + if (!S_AXI_ARESETN) + begin + clear_request <= 1'b0; + stop_request <= 1'b0; + start_request <= 1'b0; + end + end + + initial axil_read_data = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_read_data <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + begin + axil_read_data <= 0; + case(arskd_addr) + 5'h00: begin + // {{{ + if (!active_time[LGCNT]) + axil_read_data[LGCNT-1:0] <= active_time[LGCNT-1:0]; + else + // OVERFLOW! + axil_read_data <= -1; + end + // }}} + 5'h01: axil_read_data <= { wr_max_outstanding, + rd_max_outstanding_bursts, + wr_max_burst_size, + rd_max_burst_size }; + 5'h02: axil_read_data[LGCNT-1:0] <= wr_idle_cycles; + 5'h03: axil_read_data[LGCNT-1:0] <= wr_awburst_count; + 5'h04: axil_read_data[LGCNT-1:0] <= wr_beat_count; + 5'h05: axil_read_data[LGCNT-1:0] <= wr_aw_byte_count; + 5'h06: axil_read_data[LGCNT-1:0] <= wr_w_byte_count; + // + 5'h07: axil_read_data[LGCNT-1:0] <= wr_slow_data; + 5'h08: axil_read_data[LGCNT-1:0] <= wr_stall; + 5'h09: axil_read_data[LGCNT-1:0] <= wr_addr_lag; + 5'h0a: axil_read_data[LGCNT-1:0] <= wr_data_lag; + 5'h0b: axil_read_data[LGCNT-1:0] <= wr_awr_early; + 5'h0c: axil_read_data[LGCNT-1:0] <= wr_early_beat; + 5'h0d: axil_read_data[LGCNT-1:0] <= wr_addr_stall; + 5'h0e: axil_read_data[LGCNT-1:0] <= wr_early_stall; + 5'h0f: axil_read_data[LGCNT-1:0] <= wr_b_lag_count; + 5'h10: axil_read_data[LGCNT-1:0] <= wr_b_stall_count; + 5'h11: axil_read_data[LGCNT-1:0] <= wr_b_end_count; + // + 5'h12: begin + // {{{ + // Sign extend the write bias + if (wr_bias[LGCNT-1]) + axil_read_data <= -1; + axil_read_data[LGCNT-1:0] <= wr_bias; + end + // }}} + // 5'h13: axil_read_data[LGCNT-1:0] <= wr_first_lag; + // + 5'h14: axil_read_data[LGCNT-1:0] <= rd_idle_cycles; + 5'h15: axil_read_data <= { + {(C_AXIL_DATA_WIDTH-C_AXI_ID_WIDTH-1){1'b0}}, + rd_max_responding_bursts }; + 5'h16: axil_read_data[LGCNT-1:0] <= rd_burst_count; + 5'h17: axil_read_data[LGCNT-1:0] <= rd_beat_count; + 5'h18: axil_read_data[LGCNT-1:0] <= rd_byte_count; + 5'h19: axil_read_data[LGCNT-1:0] <= rd_ar_cycles; + 5'h1a: axil_read_data[LGCNT-1:0] <= rd_ar_stalls; + 5'h1b: axil_read_data[LGCNT-1:0] <= rd_r_stalls; + 5'h1c: axil_read_data[LGCNT-1:0] <= rd_lag_counter; + 5'h1d: axil_read_data[LGCNT-1:0] <= rd_slow_link; + 5'h1e: axil_read_data[LGCNT-1:0] <= rd_first_lag; + 5'h1f: axil_read_data <= { + // pending_idle, + // pending_first_burst, + // cleared, + 28'h0, perf_err, + triggered, + clear_request, + start_request + }; + default: begin end + endcase + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + + function [C_AXI_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXI_DATA_WIDTH-1:0] prior_data; + input [C_AXI_DATA_WIDTH-1:0] new_data; + input [C_AXI_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI performance counters + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // triggered + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + triggered <= 0; + else if (idle_bus) + begin + if (start_request && !clear_request) + triggered <= 1'b1; + if (stop_request) + triggered <= 0; + end + // }}} + + // active_time : count number of cycles while triggered + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + active_time <= 0; + else if (triggered) + begin + if (!active_time[LGCNT]) + active_time <= active_time + 1; + end + // }}} + + // idle_bus : Can we start or stop our couters? Can't if not idle + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_idle_bus <= 1; + else if (M_AXI_AWVALID || M_AXI_WVALID || M_AXI_ARVALID || perf_err) + r_idle_bus <= 0; + else if ((wr_aw_outstanding + ==((M_AXI_BVALID && M_AXI_BREADY) ? 1:0)) + && (wr_w_outstanding == ((M_AXI_BVALID && M_AXI_BREADY) ? 1:0)) + && (rd_outstanding_bursts + ==((M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST)? 1:0))) + r_idle_bus <= 1; + + assign idle_bus = r_idle_bus && !M_AXI_AWVALID && !M_AXI_WVALID + && !M_AXI_ARVALID; + // }}} + + // perf_err + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + perf_err <= 0; + else if (wr_aw_err || wr_w_err || rd_err) + perf_err <= 1; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Write statistics + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wr_max_burst_size: max of all AWLEN values + // {{{ + initial wr_max_burst_size = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_max_burst_size <= 0; + else if (triggered) + begin + if (M_AXI_AWVALID && M_AXI_AWLEN > wr_max_burst_size) + wr_max_burst_size <= M_AXI_AWLEN; + end + // }}} + + // wr_awburst_count -- count AWVALID && AWREADY + // {{{ + initial wr_awburst_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_awburst_count <= 0; + else if (triggered && M_AXI_AWVALID && M_AXI_AWREADY) + wr_awburst_count <= wr_awburst_count + 1; + // }}} + + // wr_wburst_count -- count of WVALID && WLAST && WREADY + // {{{ + initial wr_wburst_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_wburst_count <= 0; + else if (triggered && M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST) + wr_wburst_count <= wr_wburst_count + 1; + // }}} + + // wr_beat_count -- count of WVALID && WREADY + // {{{ + initial wr_beat_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_beat_count <= 0; + else if (triggered && M_AXI_WVALID && M_AXI_WREADY) + wr_beat_count <= wr_beat_count + 1; + // }}} + + // wr_aw_byte_count : count of (AWLEN+1)<<AWSIZE + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_aw_byte_count <= 0; + else if (triggered && M_AXI_AWVALID && M_AXI_AWREADY) + begin + wr_aw_byte_count <= wr_aw_byte_count + + (({ 24'b0, M_AXI_AWLEN}+32'h1) << M_AXI_AWSIZE); + end + // }}} + + // wstrb_count -- combinatorial, current active strobe count + // {{{ + always @(*) + begin + wstrb_count = 0; + for(ik=0; ik<C_AXI_DATA_WIDTH/8; ik=ik+1) + if (M_AXI_WSTRB[ik]) + wstrb_count = wstrb_count + 1; + end + // }}} + + // wr_w_byte_count : Count of active WSTRBs + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_w_byte_count <= 0; + else if (triggered && M_AXI_WVALID && M_AXI_WREADY) + begin + wr_w_byte_count <= wr_w_byte_count + + { {(LGCNT-C_AXI_DATA_WIDTH/8-1){1'b0}}, wstrb_count }; + end + // }}} + + // wr_aw_outstanding, wr_aw_zero_outstanding: AWV && AWR - BV && BR + // {{{ + initial wr_aw_outstanding = 0; + initial wr_aw_zero_outstanding = 1; + initial wr_aw_err = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + wr_aw_outstanding <= 0; + wr_aw_zero_outstanding <= 1; + wr_aw_err <= 0; + end else if (!wr_aw_err) + case ({ M_AXI_AWVALID && M_AXI_AWREADY, + M_AXI_BVALID && M_AXI_BREADY }) + 2'b10: begin + { wr_aw_err, wr_aw_outstanding } <= wr_aw_outstanding + 1; + wr_aw_zero_outstanding <= 0; + end + 2'b01: begin + wr_aw_outstanding <= wr_aw_outstanding - 1; + wr_aw_zero_outstanding <= (wr_aw_outstanding <= 1); + end + default: begin end + endcase +`ifdef FORMAL + always @(*) + assert(wr_aw_zero_outstanding == (wr_aw_outstanding == 0)); +`endif + // }}} + + // wr_aw_max_outstanding : max of wr_aw_outstanding + // {{{ + initial wr_aw_max_outstanding = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_aw_max_outstanding <= 0; + else if (triggered && (wr_aw_max_outstanding < wr_aw_outstanding)) + wr_aw_max_outstanding <= wr_aw_outstanding; + // }}} + + // wr_w_outstanding, wr_w_zero_outstanding: WV & WR & WL - BV & BR + // {{{ + initial wr_w_outstanding = 0; + initial wr_w_zero_outstanding = 1; + initial wr_w_err = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + wr_w_outstanding <= 0; + wr_w_zero_outstanding <= 1; + wr_w_err <= 0; + end else if (!wr_w_err) + case ({ M_AXI_WVALID && M_AXI_WREADY && M_AXI_WLAST, + M_AXI_BVALID && M_AXI_BREADY }) + 2'b10: begin + { wr_w_err, wr_w_outstanding } <= wr_w_outstanding + 1; + wr_w_zero_outstanding <= 0; + end + 2'b01: begin + wr_w_outstanding <= wr_w_outstanding - 1; + wr_w_zero_outstanding <= (wr_w_outstanding <= 1); + end + default: begin end + endcase +`ifdef FORMAL + always @(*) + assert(wr_w_zero_outstanding == (wr_w_outstanding == 0)); +`endif + // }}} + + // wr_w_max_outstanding: max of wr_w_outstanding + wr_in_progress + // {{{ + initial wr_w_max_outstanding = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_w_max_outstanding <= 0; + else if (triggered) + begin + if (wr_w_outstanding + (wr_in_progress ? 1:0) + > wr_max_outstanding) + wr_w_max_outstanding <= wr_w_outstanding + + (wr_in_progress ? 1:0); + end + // }}} + + // wr_now_outs*, wr_max_outs*: max of wr_w_outs* and wr_aw_outs* + // {{{ + always @(*) + begin + wr_now_outstanding = 0; + wr_now_outstanding = wr_w_max_outstanding; + if (wr_aw_max_outstanding > wr_now_outstanding) + wr_now_outstanding = wr_aw_max_outstanding; + end + + initial wr_max_outstanding = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_max_outstanding <= 0; + else if (triggered) + begin + if (wr_now_outstanding > wr_max_outstanding) + wr_max_outstanding <= wr_now_outstanding; + end + // }}} + + // wr_in_progress: Flag, true between WVALID and WV && WR && WLAST + // {{{ + initial wr_in_progress = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + wr_in_progress <= 0; + else if (M_AXI_WVALID) + begin + if (M_AXI_WREADY && M_AXI_WLAST) + wr_in_progress <= 0; + else + wr_in_progress <= 1; + end + // }}} + + // Orthogonal write statistics + // {{{ + // Here's where we capture our orthogonal measures for the write + // channel. It's important that, for all of these counters, only + // one of them ever counts a given burst. Hence, our criteria are + // binned and orthogonalized below. + // + // AW_O W_O WIP AWV AWR WV WR BV BR + // 0 0 0 0 0 IDLE-CYCLE + // + // 1 1 0 SLOW-DATA + // 1 1 0 Write stall (#1) + // 0 1 1 0 Write stall (#2) + // 1 1 W-BEAT (Counted elsewhere) + // + // 0 0 0 1 1 0 Early write address + // 0 0 0 1 0 0 Write address stall + // 1 0 0 0 W-DATA-LAG + // 0 1 0 0 WR Early (AWR after WLAST) + // 0 1 0 Write data before AWR + // 0 0 1 0 Early write data stall + // + // 1 1 0 0 0 B - Lag count + // 1 1 0 0 1 0 B - stall count + // 1 1 0 0 1 1 B - End of burst + // + // 0 0 1 1 Early write beat (special) + // 1 1 AW-BURST (Counted elsewhere) + // + // (DRAFT) Single channel AWR orthogonal + // {{{ + // AWR bursts (got that) + // AWR Cycles = (AWR latency) + (WR Beats) / (Throughput) + // + // AWR bias = (counts where W follows AW) + // - (counts where AW follows W) + // If (AWR bias > 0), then + // Write lag = (BLAG + AWR bias) / AWR beats + // Else if (AWR bias < 0) (WData before AWVALID), then + // Write lag = (BLAG - AWR bias) / AWR beats + // Write throughput = (WR BEATS + WR STALL + WR SLOW + // + AWR bias) / WR Beats + // }}} + // Skip the boring stuffs (if using VIM folding) + // {{{ + initial wr_data_lag = 0; + initial wr_idle_cycles = 0; + initial wr_early_beat = 0; + initial wr_awr_early = 0; + initial wr_b_lag_count = 0; + initial wr_b_stall_count = 0; + initial wr_b_end_count = 0; + initial wr_slow_data = 0; + initial wr_stall = 0; + initial wr_early_beat = 0; + initial wr_data_lag = 0; + // initial wr_aw_burst = 0; + initial wr_addr_stall = 0; + initial wr_addr_lag = 0; + initial wr_early_stall = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + begin + wr_data_lag <= 0; + wr_idle_cycles <= 0; + wr_early_beat <= 0; + wr_awr_early <= 0; + wr_b_lag_count <= 0; + wr_b_stall_count <= 0; + wr_b_end_count <= 0; + wr_slow_data <= 0; + wr_stall <= 0; + wr_data_lag <= 0; + wr_addr_stall <= 0; + wr_addr_lag <= 0; + wr_early_stall <= 0; + end else if (triggered) + // }}} + casez({ !wr_aw_zero_outstanding, !wr_w_zero_outstanding, + wr_in_progress, + M_AXI_AWVALID, M_AXI_AWREADY, + M_AXI_WVALID, M_AXI_WREADY, + M_AXI_BVALID, M_AXI_BREADY }) + 9'b0000?0???: wr_idle_cycles <= wr_idle_cycles + 1; + // +// Throughput measures + 9'b1?1??0???: wr_slow_data <= wr_slow_data + 1; + 9'b1????10??: wr_stall <= wr_stall + 1; // Stall #1 + 9'b0?1??10??: wr_stall <= wr_stall + 1; // Stall #2 + // + 9'b0??0?11??: wr_early_beat<= wr_early_beat + 1; // Before AWV + // 9'b?????11??: wr_beat <= wr_beat + 1; // Elsewhere + // +// Lag measures + 9'b000110???: wr_awr_early <= wr_awr_early + 1; + 9'b000100???: wr_addr_stall <= wr_addr_stall + 1; + + 9'b100??0???: wr_data_lag <= wr_data_lag + 1; + 9'b010??0???: wr_addr_lag <= wr_addr_lag + 1; + 9'b0?1??0???: wr_addr_lag <= wr_addr_lag + 1; + 9'b0?0??10??: wr_early_stall<= wr_early_stall+ 1; + + 9'b110??0?0?: wr_b_lag_count <= wr_b_lag_count + 1; + 9'b110??0?10: wr_b_stall_count <= wr_b_stall_count + 1; + 9'b110??0?11: wr_b_end_count <= wr_b_end_count + 1; + // + default: begin end + endcase + // }}} + + // WR Bias: How far ahead of WVALID does AWVALID show up? + // {{{ + initial wr_bias = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + wr_bias <= 0; + else if (triggered) + begin + if ((!wr_aw_zero_outstanding + || (M_AXI_AWVALID && (!M_AXI_AWREADY + || (!M_AXI_WVALID || !M_AXI_WREADY)))) + && (wr_w_zero_outstanding && !wr_in_progress)) + // Address precedes data + wr_bias <= wr_bias + 1; + else if ((wr_aw_zero_outstanding + && (!M_AXI_AWVALID || !M_AXI_ARREADY)) + && ((M_AXI_WVALID && (!M_AXI_AWVALID + || (M_AXI_WREADY && !M_AXI_AWREADY))) + || !wr_w_zero_outstanding || wr_in_progress)) + // Data precedes data + wr_bias <= wr_bias - 1; + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read statistics + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // rd_max_burst_size = max(ARLEN) + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_max_burst_size <= 0; + else if (triggered) + begin + if (M_AXI_ARVALID && M_AXI_ARLEN > rd_max_burst_size) + rd_max_burst_size <= M_AXI_ARLEN; + end + // }}} + + // rd_burst_count : Count of RVALID && RREADY && RLAST + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_burst_count <= 0; + else if (triggered && M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST) + rd_burst_count <= rd_burst_count + 1; + // }}} + + // rd_byte_count : Count of (ARLEN+1) << ARSIZE) + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_byte_count <= 0; + else if (triggered && (M_AXI_ARVALID && M_AXI_ARREADY)) + rd_byte_count <= rd_byte_count + + (({ 24'h0, M_AXI_ARLEN} + 32'h1)<< M_AXI_ARSIZE); + // }}} + + // rd_beat_count : Count of RVALID && RREADY + // {{{ + initial rd_beat_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_beat_count <= 0; + else if (triggered && (M_AXI_RVALID && M_AXI_RREADY)) + rd_beat_count <= rd_beat_count+ 1; + // }}} + + // rd_outstanding_bursts : internal counter, ARV && ARR - RV && RR && RL + // {{{ + initial rd_outstanding_bursts = 0; + initial rd_err = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rd_outstanding_bursts <= 0; + rd_err <= 0; + end else if (!rd_err) + case ({ M_AXI_ARVALID && M_AXI_ARREADY, + M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST}) + 2'b10: { rd_err, rd_outstanding_bursts } <= rd_outstanding_bursts + 1; + 2'b01: rd_outstanding_bursts <= rd_outstanding_bursts - 1; + default: begin end + endcase + // }}} + + // rd_max_outstanding_bursts : + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_max_outstanding_bursts <= 0; + else if (triggered) + begin + if (rd_outstanding_bursts > rd_max_outstanding_bursts) + rd_max_outstanding_bursts <= rd_outstanding_bursts; + end + // }}} + + generate for(gk=0; gk < (1<<C_AXI_ID_WIDTH); gk=gk+1) + begin : PER_ID_READ_STATISTICS + + // rd_outstanding_bursts_id[gk], rd_nonzero_outstanding_id[gk] + // {{{ + initial rd_outstanding_bursts_id[gk] = 0; + initial rd_nonzero_outstanding_id[gk] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rd_outstanding_bursts_id[gk] <= 0; + rd_nonzero_outstanding_id[gk] <= 0; + end else case( + { M_AXI_ARVALID && M_AXI_ARREADY && (M_AXI_ARID == gk), + M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST + && (M_AXI_RID == gk) }) + 2'b10: begin + rd_outstanding_bursts_id[gk] + <= rd_outstanding_bursts_id[gk] + 1; + rd_nonzero_outstanding_id[gk] <= 1'b1; + end + 2'b01: begin + rd_outstanding_bursts_id[gk] + <= rd_outstanding_bursts_id[gk] - 1; + rd_nonzero_outstanding_id[gk] + <= (rd_outstanding_bursts_id[gk] > 1); + end + default: begin end + endcase + // }}} + + // rd_bursts_in_flight : Are bursts in flight for this ID? + // {{{ + initial rd_bursts_in_flight = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rd_bursts_in_flight[gk] <= 0; + else if (M_AXI_RVALID && M_AXI_RID == gk) + begin + if (M_AXI_RREADY && M_AXI_RLAST) + rd_bursts_in_flight[gk] <= 1'b0; + else + rd_bursts_in_flight[gk] <= 1'b1; + end + // }}} + end endgenerate + + // rd_responding : How many ID's have bursts in flight at any time? + // {{{ + always @(*) + begin + rd_total_in_flight = 0; + for(ik=0; ik<(1<<C_AXI_ID_WIDTH); ik=ik+1) + if (rd_bursts_in_flight[ik]) + rd_total_in_flight = rd_total_in_flight + 1; + end + + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!S_AXI_ARESETN || !triggered)) + rd_responding <= 0; + else + rd_responding <= rd_total_in_flight; + // }}} + + // rd_max_responding_bursts : Max(bursts outstanding at any time) + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_max_responding_bursts <= 0; + else if (triggered) + begin + if (rd_responding > rd_max_responding_bursts) + rd_max_responding_bursts <= rd_responding; + end + // }}} + + + // rd_r_stalls : Count of RVALID && !RREADY + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_r_stalls <= 0; + else if (triggered && M_AXI_RVALID && !M_AXI_RREADY) + rd_r_stalls <= rd_r_stalls + 1; + // }}} + + // + // Orthogonal read statistics + // {{{ + // {{{ + initial rd_idle_cycles = 0; + initial rd_lag_counter = 0; + initial rd_slow_link = 0; + initial rd_ar_stalls = 0; + initial rd_ar_cycles = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + begin + rd_idle_cycles <= 0; + rd_lag_counter <= 0; + rd_slow_link <= 0; + rd_ar_stalls <= 0; + rd_ar_cycles <= 0; + end else if (triggered) + begin + // }}} + if (!M_AXI_RVALID) + begin + if (rd_bursts_in_flight != 0) + rd_slow_link <= rd_slow_link + 1; + else if (rd_nonzero_outstanding_id != 0) + rd_lag_counter <= rd_lag_counter + 1; + else if (!M_AXI_ARVALID) + rd_idle_cycles <= rd_idle_cycles + 1; + else if (M_AXI_ARVALID && !M_AXI_ARREADY) + rd_ar_stalls <= rd_ar_stalls + 1; + else // if M_AXI_ARVLD && M_AXI_ARRDY && otherwise idle + rd_ar_cycles <= rd_ar_cycles + 1; + end // else if (M_AXI_RREADDY) rd_beat_count <= rd_beat_count+1; + end + // }}} + + // rd_first_lag + // {{{ + + // rd_first: are we responding to the first request from an idle bus? + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rd_first <= 1'b1; + else if (M_AXI_RVALID) + rd_first <= 1'b0; + else if (M_AXI_ARVALID && rd_nonzero_outstanding_id == 0) + rd_first <= 1'b1; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_request) + rd_first_lag <= 0; + else if (triggered && rd_first) + rd_first_lag <= rd_first_lag + 1; + // }}} + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Simulation report generation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Notes: + // {{{ + // The following is an example report which can be to analyze bus + // statistics. It's divided in two parts. The first part prints out + // all the various collected data. All lines in this section are + // prefixed with "PERF:" to make them easier to identify via grep + // from a simulation output. The second half is designed to be + // cut/copy/pasted into a Matlab or Octave file. This contains + // the calculated data summaries for throughput, lag/latency, and + // efficiency. The performance monitor doesn't do the actual + // divide--but rather tells you what numbers need to be divided to + // achieve the desired performance measures. + // }}} + task report; + reg [31:0] num, dnm; + + if (perf_err) + $display("PERF: BUS ERROR. INVALID RESULTS. RESET BUS TO CLEAR."); + else if (active_time[LGCNT]) + $display("PERF: COUNTER OVERFLOW. TRY AGAIN."); + else if (wr_awburst_count == 0 && rd_burst_count == 0) + $display("PERF: NO DATA TRANSFERS RECORDED"); + else begin + $display("PERF: AllBurstSiz\t0x%08x", { wr_max_outstanding, + rd_max_outstanding_bursts, + wr_max_burst_size, + rd_max_burst_size }); + $display("PERF: TotalCycles\t0x%08x", active_time[LGCNT-1:0]); + $display("PERF: WrIdles\t\t0x%08x", wr_idle_cycles); + $display("PERF: AwrBursts\t\t0x%08x", wr_awburst_count); + $display("PERF: WrBeats\t\t0x%08x", wr_beat_count); + $display("PERF: AwrBytes\t\t0x%08x", wr_aw_byte_count); + $display("PERF: WrBytes\t\t0x%08x", wr_w_byte_count); + $display("PERF: WrSlowData\t0x%08x", wr_slow_data); + $display("PERF: WrStalls\t\t0x%08x", wr_stall); + $display("PERF: WrAddrLag\t\t0x%08x", wr_addr_lag); + $display("PERF: WrDataLag\t\t0x%08x", wr_data_lag); + $display("PERF: AwEarly\t\t0x%08x", wr_awr_early); + $display("PERF: WrEarlyData\t0x%08x", wr_early_beat); + $display("PERF: AwStall\t\t0x%08x", wr_addr_stall); + $display("PERF: EWrStalls\t\t0x%08x", wr_early_stall); + $display("PERF: WrBLags\t\t0x%08x", wr_b_lag_count); + $display("PERF: WrBStall\t\t0x%08x", wr_b_stall_count); + $display("PERF: WrBEnd\t\t0x%08x", wr_b_end_count); + $display("PERF: WrBias\t\t0x%08x", wr_bias); + // + $display("PERF: ---------------------------"); + // + $display("PERF: RdIdles\t\t0x%08x", rd_idle_cycles); + $display("PERF: RdMaxB\t\t0x%08x", rd_max_responding_bursts); + $display("PERF: RdBursts\t\t0x%08x",rd_burst_count); + $display("PERF: RdBeats\t\t0x%08x", rd_beat_count); + $display("PERF: RdBytes\t\t0x%08x", rd_byte_count); + $display("PERF: ARCycles\t\t0x%08x",rd_ar_cycles); + $display("PERF: ArStalls\t\t0x%08x",rd_ar_stalls); + $display("PERF: RStalls\t\t0x%08x", rd_r_stalls); + $display("PERF: RdLag\t\t0x%08x", rd_lag_counter); + $display("PERF: RdSlow\t\t0x%08x", rd_slow_link); + // + $display("PERF: ---------------------------"); + // + num = wr_awr_early + wr_addr_stall + wr_data_lag + wr_addr_lag + + wr_early_beat + wr_b_lag_count + wr_b_stall_count; + dnm = wr_awburst_count; + $display("perf_wrlag = %1d / %1d;", num, dnm); + num = wr_beat_count; + // dnm = wr_cycles; + dnm = active_time[LGCNT-1:0] - wr_b_end_count - wr_idle_cycles; + $display("perf_wreff = %1d / %1d;", num, dnm); + num = wr_beat_count; + dnm = wr_beat_count + wr_slow_data + wr_stall; + $display("perf_wrthruput = %1d / %1d;", num, dnm); + + // + // Read lag = wait (ARSTALL + LAG) / # of Bursts + num = rd_ar_stalls + rd_lag_counter; + dnm = rd_burst_count; + $display("perf_rdlag = %1d / %1d;", num, dnm); + num = rd_first_lag; + dnm = rd_ar_cycles; + $display("perf_rdlatency = %1d / %1d;", num, dnm); + num = rd_beat_count; + dnm = rd_ar_cycles + rd_ar_stalls + rd_lag_counter + rd_beat_count + + rd_r_stalls + rd_slow_link; + $display("perf_rdeff = %1d / %1d;", num, dnm); + num = rd_beat_count; + dnm = rd_slow_link + rd_r_stalls + rd_beat_count; + $display("perf_rdthruput = %1d / %1d;", num, dnm); + + end endtask + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, + S_AXIL_ARADDR[ADDRLSB-1:0], + S_AXIL_AWADDR[ADDRLSB-1:0], + wskd_data, wskd_strb, + M_AXI_AWBURST, M_AXI_AWLOCK, M_AXI_AWCACHE, M_AXI_AWQOS, + M_AXI_AWID, M_AXI_AWADDR, M_AXI_ARADDR, + M_AXI_AWPROT, M_AXI_ARPROT, + M_AXI_BID, M_AXI_BRESP, + M_AXI_ARBURST, M_AXI_ARLOCK, M_AXI_ARCACHE, M_AXI_ARQOS, + M_AXI_WDATA, M_AXI_RDATA, + M_AXI_RRESP + }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used in verfiying this core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3), + .F_OPT_COVER_BURST(4) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_awr_outstanding== (S_AXIL_BVALID ? 1:0) + +(S_AXIL_AWREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXIL_BVALID ? 1:0) + +(S_AXIL_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXIL_RVALID ? 1:0) + +(S_AXIL_ARREADY ? 0:1)); + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + if (OPT_LOWPOWER && !S_AXIL_RVALID) + assert(S_AXIL_RDATA == 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Start/stop requests + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + if (S_AXI_ARESETN) + assert(!start_request || !stop_request); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN && clear_request && !idle_bus)) + assert(clear_request); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN && start_request + && (clear_request || !idle_bus))) + begin + if (!$past(axil_write_ready)) + assert(start_request); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // While there are already cover properties in the formal property + // set above, you'll probably still want to cover something + // application specific here + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + assume(wr_aw_outstanding < 8'hff); + + always @(*) + assume(wr_w_outstanding < 8'hff); + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axis2mm.v b/rtl/wb2axip/axis2mm.v new file mode 100644 index 0000000..d578e41 --- /dev/null +++ b/rtl/wb2axip/axis2mm.v @@ -0,0 +1,2234 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axis2mm +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Converts an AXI-stream (input) to an AXI (full) memory +// interface. +// +// {{{ +// While I am aware that other vendors sell similar components, if you +// look under the hood you'll find no relation to anything but my own +// work here. +// +// Registers: +// +// 0: CMD_CONTROL +// Controls the transaction via either starting or aborting an +// ongoing transaction, provides feedback regarding the current +// status. +// +// [31] r_busy +// True if the core is in the middle of a transaction. Set this +// bit to one to begin a transaction. +// +// [30] r_err +// True if the core has detected an error, such as FIFO +// overflow while writing, or FIFO overflow in continuous mode. +// +// Writing a '1' to this bit while the core is idle will clear it. +// New transfers will not start until this bit is cleared. For +// this reason, I often start a new transfer by writing to bits +// 31 and 30 of this register. +// +// _s2mm->a_control = 0xc0000000; +// +// Other bits may be appropriate as well, as discussed below, +// depending on your application. +// +// [29] r_complete +// True if the transaction has completed, whether normally or +// abnormally (error or abort). +// +// Any write to the CMD_CONTROL register will clear this flag. +// +// [28] r_continuous +// Normally the FIFO gets cleared and reset between operations. +// However, if you set r_continuous, the core will then expectt +// a second operation to take place following the first one. +// In this case, the FIFO doesn't get cleared. However, if the +// FIFO fills and the incoming data is both valid and changing, +// the r_err flag will be set. +// +// Any write to the CMD_CONTROL register while the core is not +// busy will adjust this bit. +// +// [27] !r_increment +// +// If clear, the core writes to subsequent and incrementing +// addresses--the normal copy to memory case. If !r_increment is +// set, the core writes to the same address throughout the +// transaction. This is useful if you want to copy data to a +// FIFO or other device living at a single address in the memory +// map. +// +// Writes to CMD_CONTROL while the core is idle will adjust this +// bit. +// +// [26] !tlast_syncd +// +// Read only status indicator. Reads 0 if OPT_TLAST_SYNC isn't +// set. If OPT_TLAST_SYNC is set, then this bit indicates whether +// or not the memory transfer is currently aligned with any stream +// packets, or whether it is out of synchronization and waiting to +// sync with the incoming stream. If the IP is out of alignment +// and OPT_TLAST_SYNC is set, then the core will synchronize +// itself automatically by holding TREADY high and ignoring data +// until the first sample after TLAST. +// +// [25] Error code, decode error +// +// Read only bit. True following any AXI decode error. This will +// also set the error bit. When the error bit is cleared, this +// bit will be automatically cleared as well. +// +// [24] Error code, slave error +// +// Read only bit. True following any AXI slave error. This will +// also set the error bit. When the error bit is cleared, this bit +// will be automatically cleared as well. +// +// [23] Error code, overflow error +// +// Read only bit. True following any AXI stream overflow. As with +// the other two error code bits, this one will also set the error +// bit. It will also be cleared whenever the error bit is cleared. +// +// A "proper" AXI stream will never nor can it ever overflow. This +// overflow check therefore looks for AXI stream protocol +// violations. Such a violation might be not maintaining TVALID +// when !TREADY, or changing TDATA when TVALID && !TREADY. +// Likewise, if TLAST changes while TVALID && !TREADY an overflow +// condition will be generated--but in this case only if the +// OPT_TLAST_SYNC option is set. +// +// [22] Abort in progress +// +// Read only bit. This bit will be true following any abort until +// the bus transactions complete. Self-clearing. +// +// [20:16] LGFIFO +// These are read-only bits, returning the size of the FIFO. +// +// ABORT +// If the core is busy, and the ABORT_KEY (currently set to 8'h26 +// below) is written to the top 8-bits ([31:24]) of this command +// register, then the current transfer will be aborted. Yes, this +// does repurpose the other bits written above. Any pending writes +// will be completed, but nothing more will be written. +// +// Alternatively, the core will enter into an abort state +// following any returned bus error indications. +// +// x4-c: (Unused and reserved) +// +// x10-14: CMD_ADDR +// [C_AXI_ADDR_WIDTH-1:($clog2(C_AXI_DATA_WIDTH)-3)] +// +// If idle, this is address the core will write to when it starts. +// +// If busy, this is the address of either the current or next +// address the core will request writing to. +// +// Upon completion, the address either returns to the starting +// address (if r_continuous is clear), or otherwise becomes the +// address where the core left off. In the case of an abort or an +// error, this will be (near) the address that was last written. +// +// Why "near"? Because this address records the writes that have +// been issued while no error is pending. If a bus error return +// comes back, there may have been several writes issued before +// that error address. Likewise if an overflow is detected, the +// data associated with the overflow may have already been +// somewhat written--the AXI bus doesn't stop on a dime. +// +// I hope to eventually add support for unaligned bursts. Such +// support is not currently part of this core. +// +// x18-1c: CMD_LEN +// [LGLEN-1:0] +// The size of the transfer in bytes. Only accepts aligned +// addresses, therefore bits [($clog2(C_AXI_DATA_WIDTH)-3):0] +// will always be forced to zero. To find out what size bus +// this core is conencted to, or the maximum transfer length, +// write a -1 to this value and read the returning result. +// Only the active bits will be set. +// +// While the core is busy, reads from this address will return +// the number of items still to be written to the bus. +// +// }}} +// +// Status: +// {{{ +// 1. The core passes both cover checks and formal property (assertion) +// based checks. It has not (yet) been tested in real hardware. +// +// 2. I'd also like to support unaligned addresses (not lengths). This +// will require aligning the data coming out of the FIFO as well. +// As written, the core doesn't yet support these features. +// +// }}} +// +// Updates: +// {{{ +// 20210426 - Fix. Upon reading, the current AXI write address and length +// will (now) be returned. These are word address and lengths, not +// packet address and lengths, and may (or may not) be aligned with +// packet boundaries. +// +// In a similar fashion, the cmd_addr and cmd_length registers will +// be adjusted on any error to (approximate) the address and length +// remaining upon any discovered error. +// }}} +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axis2mm #( + // {{{ + // + // Downstream AXI (MM) address width. Remember, this is *byte* + // oriented, so an address width of 32 means this core can + // interact with a full 2^(C_AXI_ADDR_WIDTH) *bytes*. + parameter C_AXI_ADDR_WIDTH = 32, + // ... and the downstream AXI (MM) data width. High speed can + // be achieved by increasing this data width. + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXIS_TUSER_WIDTH = 0, + // + // OPT_AXIS_SKIDBUFFER will place a buffer between the incoming + // AXI stream interface and the outgoing AXI stream ready. This + // is technically necessary, and probably good practice too, + // when dealing with high speed networks. + parameter [0:0] OPT_AXIS_SKIDBUFFER = 1, + // + // OPT_AXIS_SKIDREGISTER will force the outputs of the skid + // buffer to be registered. This is something you would + // do primarily if you are trying to hit high speeds through + // this core. + parameter [0:0] OPT_AXIS_SKIDREGISTER = 0, + // + // OPT_TLAST_SYNC will synchronize the write with any incoming + // packets. Packets are assumed to be synchronized initially + // after any reset, or on the TVALID following any TLAST + parameter [0:0] OPT_TLAST_SYNC = 1, + // + // OPT_TREADY_WHILE_IDLE controls how the stream idle is set + // when the memory copy isn't running. If 1, then TREADY will + // be 1 and the core will ignore/throw out data when the core + // isn't busy. Otherwise, if this is set to 0, the core will + // force the stream to stall if ever no data is being copied. + parameter [0:0] OPT_TREADY_WHILE_IDLE = 1, + // + // If the ABORT_KEY is written to the upper 8-bits of the + // control/status word, the current operation will be halted. + // Any currently active (AxVALID through xVALID & xREADY) + // requests will continue to completion, and the core will then + // come to a halt. + parameter [7:0] ABORT_KEY = 8'h26, + // + // The size of the FIFO, log-based two. Hence LGFIFO=9 gives + // you a FIFO of size 2^(LGFIFO) or 512 elements. This is about + // as big as the FIFO should ever need to be, since AXI bursts + // can be 256 in length. + parameter LGFIFO = 9, + // + // Maximum number of bytes that can ever be transferred, in + // log-base 2. Hence LGLEN=20 will transfer 1MB of data. + parameter LGLEN = C_AXI_ADDR_WIDTH-1, + // + // We only ever use one AXI ID for all of our transactions. + // Here it is given as 0. Feel free to change it as necessary. + parameter [C_AXI_ID_WIDTH-1:0] AXI_ID = 0, + // + // Set OPT_UNALIGNED to be able to transfer from unaligned + // addresses. Only applies to non fixed addresses and + // (possibly) non-continuous bursts. (THIS IS A PLACEHOLDER. + // UNALIGNED ADDRESSING IS NOT CURRENTLY SUPPORTED.) + localparam [0:0] OPT_UNALIGNED = 0, + // + parameter [0:0] OPT_LOWPOWER = 1'b0, + parameter [0:0] OPT_CLKGATE = OPT_LOWPOWER, + // + // OPT_ASYNCMEM. The default FIFO implementation uses an + // asynchronous memory read, which will return the result in + // the same clock it is requested within. This forces the + // FIFO to use distributed RAM. For those architectures that + // don't have distributed RAM, or those designs that need to + // use block RAM, this flag should be set to zero. + parameter [0:0] OPT_ASYNCMEM = 1'b1, + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + localparam C_AXIL_ADDR_WIDTH = 5, + localparam C_AXIL_DATA_WIDTH = 32 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The stream interface + // {{{ + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire S_AXIS_TLAST, + input wire [((C_AXIS_TUSER_WIDTH>0) ? C_AXIS_TUSER_WIDTH-1:0):0] + S_AXIS_TUSER, + // }}} + // + // The control interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // + + // + // The AXI (full) write interface + // {{{ + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [7:0] M_AXI_AWLEN, + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, + output wire M_AXI_AWLOCK, + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + output wire [(C_AXIS_TUSER_WIDTH>0 ? C_AXIS_TUSER_WIDTH-1:0):0] + M_AXI_WUSER, + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // }}} + // + // + // Create an output signal to indicate that we've finished + output reg o_int + // }}} + ); + + // Local parameters + // {{{ + localparam AXILLSB = $clog2(C_AXIL_DATA_WIDTH)-3; + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + localparam [2:0] CMD_CONTROL = 3'b000, + // CMD_UNUSED_1 = 3'b001, + // CMD_UNUSED_2 = 3'b010, + // CMD_UNUSED_3 = 3'b011, + CMD_ADDRLO = 3'b100, + CMD_ADDRHI = 3'b101, + CMD_LENLO = 3'b110, + CMD_LENHI = 3'b111; + // CMD_RESERVED = 2'b11; + + // The maximum burst size is either 256, or half the FIFO size, + // whichever is smaller. + localparam TMP_LGMAXBURST=(LGFIFO > 8) ? 8 : LGFIFO-1; + // Of course, if this busts our 4kB packet size, it's an error. + // Let's clip to that size, then, if the LGMAXBURST would otherwise + // break it. So .. if 4kB is larger than our maximum burst size, then + // no change is required. + localparam LGMAXBURST = ((4096 / (C_AXI_DATA_WIDTH / 8)) + > (1<<TMP_LGMAXBURST)) + ? TMP_LGMAXBURST : $clog2(4096 * 8 / C_AXI_DATA_WIDTH); + localparam LGMAX_FIXED_BURST = (LGMAXBURST > 4) ? 4 : LGMAXBURST; + localparam MAX_FIXED_BURST = (1<<LGMAX_FIXED_BURST); + localparam LGLENW = LGLEN - ADDRLSB; + // localparam LGFIFOB = LGFIFO + ADDRLSB; + localparam ERRCODE_NOERR = 0, + ERRCODE_OVERFLOW = 0, + ERRCODE_SLVERR = 1, + ERRCODE_DECERR = 2; + // }}} + + // Signal declarations + // {{{ + wire clk_active, gated_clk; + wire i_clk = gated_clk; + wire i_reset = !S_AXI_ARESETN; + + // Incoming stream buffer + wire sskd_valid, sskd_ready, sskd_last; + wire [C_AXI_DATA_WIDTH-1:0] sskd_data; + wire [((C_AXIS_TUSER_WIDTH>0) ? C_AXIS_TUSER_WIDTH : 1)-1:0] sskd_user; + + reg r_busy, r_err, r_complete, r_continuous, r_increment, + cmd_abort, zero_length, r_pre_start, + w_cmd_start, w_complete, w_cmd_abort; + reg [2:0] r_errcode; + // reg cmd_start; + reg axi_abort_pending; + + reg [LGLENW-1:0] aw_requests_remaining, + aw_bursts_outstanding, + aw_next_remaining; + reg [LGMAXBURST:0] wr_writes_pending; + reg [LGMAXBURST:0] r_max_burst; + reg [C_AXI_ADDR_WIDTH-1:0] axi_addr; + + reg [C_AXI_ADDR_WIDTH-1:0] cmd_addr; + reg [LGLENW-1:0] cmd_length_w; + + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_address, wide_length, + new_wideaddr, new_widelen, + wide_len_remaining,wide_current_address; + wire [C_AXIL_DATA_WIDTH-1:0] new_cmdaddrlo, new_cmdaddrhi, + new_lengthlo, new_lengthhi; + + // FIFO signals + wire reset_fifo, write_to_fifo, + read_from_fifo; + wire [C_AXIS_TUSER_WIDTH+C_AXI_DATA_WIDTH-1:0] fifo_data; + wire [LGFIFO:0] fifo_fill; + wire fifo_full, fifo_empty; + + wire awskd_valid, axil_write_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + // + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire arskd_valid, axil_read_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + reg last_stalled, overflow, last_tlast; + reg [C_AXI_DATA_WIDTH-1:0] last_tdata; + reg [C_AXIL_DATA_WIDTH-1:0] w_status_word; + + reg [LGLENW-1:0] r_remaining_w; + + reg axi_awvalid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; + reg [7:0] axi_awlen; + reg axi_wvalid, axi_wlast; + reg [C_AXI_DATA_WIDTH/8-1:0] axi_wstrb; + + // Speed up checking for zeros + reg aw_none_remaining, + aw_none_outstanding, + aw_last_outstanding, + wr_none_pending; // r_none_remaining; + + reg w_phantom_start, phantom_start; + reg [LGMAXBURST:0] initial_burstlen; + reg [LGMAXBURST-1:0] addralign; + + // + // Option processing + wire tlast_syncd; + + reg aw_multiple_full_bursts, + aw_multiple_fixed_bursts, + aw_multiple_bursts_remaining, + aw_needs_alignment; + reg [LGFIFO:0] data_available; + reg sufficiently_filled; + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI Stream skidbuffer + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + skidbuffer #( + // {{{ + .OPT_OUTREG(OPT_AXIS_SKIDREGISTER), + .DW(C_AXI_DATA_WIDTH + 1 + + ((C_AXIS_TUSER_WIDTH > 0) ? C_AXIS_TUSER_WIDTH:1)), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_PASSTHROUGH(!OPT_AXIS_SKIDBUFFER) + // }}} + ) skd_stream( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(reset_fifo), + .i_valid(S_AXIS_TVALID), .o_ready(S_AXIS_TREADY), + .i_data({ S_AXIS_TUSER, S_AXIS_TDATA, S_AXIS_TLAST }), + .o_valid(sskd_valid), .i_ready(sskd_ready), + .o_data({ sskd_user, sskd_data, sskd_last }) + // }}} + ); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // This is mostly the skidbuffer logic, and handling of the VALID + // and READY signals for the AXI-lite control logic in the next + // section. + + // + // Write signaling + // + // {{{ + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB), + .OPT_LOWPOWER(OPT_LOWPOWER) + // }}} + ) axilawskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr) + // }}} + ); + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8), + .OPT_LOWPOWER(OPT_LOWPOWER) + // }}} + ) axilwskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb }) + // }}} + ); + + assign axil_write_ready = clk_active && awskd_valid && wskd_valid + && (!S_AXIL_BVALID || S_AXIL_BREADY); + + initial axil_bvalid = 0; + always @(posedge i_clk) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXIL_BREADY) + axil_bvalid <= 0; + + assign S_AXIL_BVALID = axil_bvalid; + assign S_AXIL_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB), + .OPT_LOWPOWER(OPT_LOWPOWER) + // }}} + ) axilarskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr) + // }}} + ); + + assign axil_read_ready = clk_active && arskd_valid + && (!axil_read_valid || S_AXIL_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge i_clk) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXIL_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXIL_RVALID = axil_read_valid; + assign S_AXIL_RDATA = axil_read_data; + assign S_AXIL_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite controlled logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // last_stalled -- used in overflow checking + // {{{ + initial last_stalled = 1'b0; + always @(posedge i_clk) + last_stalled <= (!i_reset) && (S_AXIS_TVALID && !S_AXIS_TREADY); + // }}} + + // last_tlast -- used to check for protocol violations in overflow next + // {{{ + always @(posedge i_clk) + if (!OPT_TLAST_SYNC) + last_tlast <= 0; + else + last_tlast <= S_AXIS_TLAST; + // }}} + + // last_tdata -- used for overflow checking + // {{{ + always @(posedge i_clk) + last_tdata <= S_AXIS_TDATA; + // }}} + + // overflow + // {{{ + // Capture and check whether or not the incoming data stream overflowed + // This is primarily a check for AXI-stream protocol violations, since + // you can't really overflow an AXI stream when following protocol. The + // problem is that many stream sources--such as ADCs for example--can't + // handle back-pressure. Hence, checking for stream violations can + // be used in those cases to check for overflows. The check is only + // so good, however, since an overflow condition might take place if + // an ADC produces two consecutive (identical) values, one of which gets + // skipped--and this check will not capture that. + initial overflow = 0; + always @(posedge i_clk) + if (i_reset) + overflow <= 0; + else if (last_stalled) + begin + // The overflow pulse is only one clock period long + overflow <= 0; + if (!sskd_valid) + overflow <= 1; + if (S_AXIS_TDATA != last_tdata) + overflow <= 1; + if (OPT_TLAST_SYNC && S_AXIS_TLAST != last_tlast) + overflow <= 1; + + // This will be caught by r_err and r_continuous + end + // }}} + + // w_cmd_abort, cmd_abort -- Abort transaction on user request + // {{{ + + // w_cmd_abort is a combinational value capturing a user abort request + // {{{ + always @(*) + begin + w_cmd_abort = 0; + w_cmd_abort = (axil_write_ready && awskd_addr == CMD_CONTROL) + && (wskd_strb[3] && wskd_data[31:24] == ABORT_KEY); + if (!r_busy) + w_cmd_abort = 0; + end + // }}} + + // cmd_abort latches the user request until the abort is complete + // {{{ + initial cmd_abort = 0; + always @(posedge i_clk) + if (i_reset) + cmd_abort <= 0; + else if (!r_busy) + cmd_abort <= 0; + else + cmd_abort <= cmd_abort || w_cmd_abort; + // }}} + // }}} + + // + // Start command + // + always @(*) + if (r_busy) + w_cmd_start = 0; + else begin + w_cmd_start = 0; + if ((axil_write_ready && awskd_addr == CMD_CONTROL) + && (wskd_strb[3] && wskd_data[31])) + w_cmd_start = 1; + if (r_err && !wskd_data[30]) + w_cmd_start = 0; + if (zero_length) + w_cmd_start = 0; + end + + // r_busy, r_complete -- Calculate busy or complete flags + // {{{ + initial r_busy = 0; + initial r_complete = 0; + always @(posedge i_clk) + if (i_reset) + begin + r_busy <= 0; + r_complete <= 0; + end else if (!r_busy) + begin + // Core is idle, waiting for a command to start + if (w_cmd_start) + r_busy <= 1'b1; + + // Any write to the control register will clear the + // completion flag + if (axil_write_ready && awskd_addr == CMD_CONTROL) + r_complete <= 1'b0; + end else if (w_complete) + begin + // Clear busy once the transaction is complete + // This includes clearing busy on any error + r_complete <= 1; + r_busy <= 1'b0; + end + // }}} + + // o_int -- interrupt generation + // {{{ + initial o_int = 0; + always @(posedge i_clk) + if (i_reset) + o_int <= 0; + else + o_int <= (r_busy && w_complete) + || (r_continuous && overflow); + // }}} + + // r_err, r_errcode: Error conditions checking + // {{{ + initial r_err = 0; + initial r_errcode = ERRCODE_NOERR; + always @(posedge i_clk) + if (i_reset) + begin + r_err <= 0; + r_errcode <= ERRCODE_NOERR; + end else if (!r_busy) + begin + if (r_continuous && overflow) + begin + r_err <= 1; + r_errcode[ERRCODE_OVERFLOW] <= 1'b1; + end + + if (axil_write_ready && awskd_addr == CMD_CONTROL + && wskd_strb[3] && wskd_data[30]) + begin + r_err <= 0; + r_errcode <= ERRCODE_NOERR; + end + end else if (r_busy) + begin + if (M_AXI_BVALID && M_AXI_BREADY && M_AXI_BRESP[1]) + begin + r_err <= 1'b1; + if (M_AXI_BRESP[0]) + r_errcode[ERRCODE_DECERR] <= 1'b1; + else + r_errcode[ERRCODE_SLVERR] <= 1'b1; + end + + if (overflow) + begin + r_err <= 1'b1; + r_errcode[ERRCODE_OVERFLOW] <= 1'b1; + end + end + // }}} + + // r_continuous + // {{{ + initial r_continuous = 0; + always @(posedge i_clk) + if (i_reset) + r_continuous <= 0; + else begin + if (r_continuous && overflow) + r_continuous <= 1'b0; + if (!r_busy && axil_write_ready && awskd_addr == CMD_CONTROL) + r_continuous <= wskd_strb[3] && wskd_data[28]; + end + // }}} + + // wide_* + // {{{ + always @(*) + begin + wide_address = 0; + wide_address[C_AXI_ADDR_WIDTH-1:0] = cmd_addr; + + wide_current_address = 0; + wide_current_address[C_AXI_ADDR_WIDTH-1:0] = axi_addr; + + wide_length = 0; + wide_length[ADDRLSB +: LGLENW] = cmd_length_w; + + wide_len_remaining = 0; + wide_len_remaining[ADDRLSB +: LGLENW] = r_remaining_w; + end + // }}} + + // new_* wires created via apply_wstrb + // {{{ + assign new_cmdaddrlo= apply_wstrb( + wide_address[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + assign new_cmdaddrhi=apply_wstrb( + wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + assign new_lengthlo= apply_wstrb( + wide_length[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + assign new_lengthhi= apply_wstrb( + wide_length[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + // }}} + + // new_wideaddr, new_widelen + // {{{ + // These are the wide_* adjusted for any write, and then adjusted again + // to make sure they are within the correct number of bits for the + // interface. + always @(*) + begin + new_wideaddr = wide_address; + if (awskd_addr == CMD_ADDRLO) + new_wideaddr[C_AXIL_DATA_WIDTH-1:0] + = new_cmdaddrlo; + if (awskd_addr == CMD_ADDRHI) + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] + = new_cmdaddrhi; + if (!OPT_UNALIGNED) + new_wideaddr[ADDRLSB-1:0] = 0; + + // We only support C_AXI_ADDR_WIDTH address bits + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + + // + /////////////// + // + + new_widelen = wide_length; + if (awskd_addr == CMD_LENLO) + new_widelen[C_AXIL_DATA_WIDTH-1:0] = new_lengthlo; + if (awskd_addr == CMD_LENHI) + new_widelen[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] + = new_lengthhi; + + // We only support integer numbers of words--even if unaligned + new_widelen[ADDRLSB-1:0] = 0; + + // We only support LGLEN length bits + new_widelen[2*C_AXIL_DATA_WIDTH-1:LGLEN] = 0; + end + // }}} + + // cmd_addr, cmd_length_w, r_increment, zero_length, aw_multiple_* + // {{{ + initial r_increment = 1'b1; + initial cmd_addr = 0; + initial cmd_length_w = 0; // Counts in bytes + initial zero_length = 1; + initial aw_multiple_full_bursts = 0; + initial aw_multiple_fixed_bursts = 0; + always @(posedge i_clk) + if (i_reset) + begin + // {{{ + r_increment <= 1'b1; + cmd_addr <= 0; + cmd_length_w <= 0; + zero_length <= 1; + aw_multiple_full_bursts <= 0; + aw_multiple_fixed_bursts <= 0; + // }}} + end else if (axil_write_ready && !r_busy) + begin // Set the command, address, and length prior to operation + // {{{ + case(awskd_addr) + CMD_CONTROL: + r_increment <= !wskd_data[27]; + CMD_ADDRLO: + cmd_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + CMD_ADDRHI: if (C_AXI_ADDR_WIDTH > C_AXIL_DATA_WIDTH) + begin + cmd_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + end + CMD_LENLO: begin + cmd_length_w <= new_widelen[ADDRLSB +: LGLENW]; + zero_length <= (new_widelen[ADDRLSB +: LGLENW] == 0); + aw_multiple_full_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAXBURST)]; + aw_multiple_fixed_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAX_FIXED_BURST)]; + + end + CMD_LENHI: if (LGLEN > C_AXIL_DATA_WIDTH) + begin + cmd_length_w <= new_widelen[ADDRLSB +: LGLENW]; + zero_length <= (new_widelen[ADDRLSB +: LGLENW] == 0); + aw_multiple_full_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAXBURST)]; + aw_multiple_fixed_bursts <= |new_widelen[LGLEN-1:(ADDRLSB+LGMAX_FIXED_BURST)]; + end + default: begin end + endcase + // }}} + end else if (r_busy) + begin // Updated cmd_addr && cmd_length during operation + // {{{ + // Capture the last address written to in case of r_continuous + // (where we'll want to start again from this address), + // cmd_abort (where we'll want to know how far we got), or + // a bus error (where again we'll want to know how far we got) + if (r_continuous||axi_abort_pending) + cmd_addr <= axi_addr; + + // Capture the number of remaining requests on either an error + // or an abort. Need to be careful here that we don't capture + // this address twice--hence the check for + // w_cmd_abort && !cmd_abort, and again for BVALID && !r_err + // + // Note that we can't check for axi_abort_pending here, since + // as soon as axi_abort_pending becomes true then cmd_length_w + // will get set to zero. Hence we need to capture this before + // axi_abort_pending gets set. + // + // Note that this is only an *approximate* length--especially + // in the case of either a bus error or an overflow, in which + // cases we won't really know what writes have been accomplished + // only that the last one failed. In that case, this will + // indicate the amount of writes we haven't requested + // (yet)--knowing that at least one (or more) of those prior + // must've failed. In the case of an overflow error, the + // overflow error may (or may not) have been written to memory + // by this time. + if (!axi_abort_pending && (cmd_abort || r_err + || (M_AXI_BVALID && M_AXI_BRESP[1]))) + begin + cmd_length_w <= aw_requests_remaining; + zero_length <= (aw_requests_remaining == 0); + aw_multiple_full_bursts <= |aw_requests_remaining[LGLENW-1:LGMAXBURST]; + aw_multiple_fixed_bursts <= |aw_requests_remaining[LGLENW-1:LGMAX_FIXED_BURST]; + end + + // Note that, because cmd_addr and cmd_length_w here aren't set + // on the same conditions that it is possible, on an error, + // that the two will not match. + // }}} + end + // }}} + + // w_status_word + // {{{ + always @(*) + begin + w_status_word = 0; + + // The ABORT_KEY needs to be chosen so as not to look + // like these bits, lest someone read from the register + // and write back to it accidentally aborting any transaction + w_status_word[31] = r_busy; + w_status_word[30] = r_err; + w_status_word[29] = r_complete; + w_status_word[28] = r_continuous; + w_status_word[27] = !r_increment; + w_status_word[26] = !tlast_syncd; + w_status_word[25:23] = r_errcode; + w_status_word[22] = cmd_abort; + w_status_word[20:16] = LGFIFO; + end + // }}} + + // axil_read_data + // {{{ + always @(posedge i_clk) + if (!axil_read_valid || S_AXIL_RREADY) + begin + case(arskd_addr) + CMD_CONTROL: axil_read_data <= w_status_word; + CMD_ADDRLO: begin + if (!r_busy) + axil_read_data <= wide_address[C_AXIL_DATA_WIDTH-1:0]; + else + axil_read_data <= wide_current_address[C_AXIL_DATA_WIDTH-1:0]; + end + CMD_ADDRHI: begin + if (!r_busy) + axil_read_data <= wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + else + axil_read_data <= wide_current_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + end + CMD_LENLO: begin + if (!r_busy) + axil_read_data <= wide_length[C_AXIL_DATA_WIDTH-1:0]; + else + axil_read_data <= wide_len_remaining[C_AXIL_DATA_WIDTH-1:0]; + end + CMD_LENHI: begin + if (!r_busy) + axil_read_data <= wide_length[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + else + axil_read_data <= wide_len_remaining[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + end + default: axil_read_data <= 0; + endcase + end + // }}} + + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + // {{{ + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The data FIFO section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Reset the FIFO between bursts, as long as r_continuous isn't set + assign reset_fifo = i_reset || (!r_busy && (!r_continuous || r_err)); + assign write_to_fifo = sskd_valid && sskd_ready && tlast_syncd; + assign read_from_fifo = M_AXI_WVALID && M_AXI_WREADY + && !axi_abort_pending; + + // We are ready if the FIFO isn't full and ... + // if OPT_TREADY_WHILE_IDLE is true + // at which point we ignore incoming data when we aren't + // busy, or + // if we aren't resetting the FIFO--that is, if data is actually + // going into the FIFO, or + // if we are ever out of synchronization--then we can ignore data + // until the next TLAST comes, where we must realign + // ourselves + assign sskd_ready = clk_active && !fifo_full + && (OPT_TREADY_WHILE_IDLE + || !reset_fifo || !tlast_syncd); + + generate if (OPT_TLAST_SYNC) + begin : GEN_TLAST_SYNC + reg r_tlast_syncd; + // If the user has set OPT_TLAST_SYNC, then he wants to make + // certain that we don't start writing until the first stream + // value after the TLAST packet indicating an end of packet. + // For this cause, we'll maintain an r_tlast_syncd value + // indicating that the last value was a TLAST. If, at any + // time afterwards, a value is accepted into the stream but not + // into the FIFO, then the stream is now out of sync and + // r_tlast_syncd will drop. + // + // Note, this doesn't catch the case where the FIFO can't keep + // up. Lost data (might be) caught by overflow below. + initial r_tlast_syncd = 1; + always @(posedge i_clk) + if (!S_AXI_ARESETN) + r_tlast_syncd <= 1; + else if (sskd_valid && sskd_ready) + begin + if (sskd_last) + r_tlast_syncd <= 1; + else if (reset_fifo) + r_tlast_syncd <= 0; + end + + assign tlast_syncd = r_tlast_syncd; + end else begin : NO_TLAST_SYNC + + // + // If the option isn't set, then we are always synchronized. + // + assign tlast_syncd = 1; + + // Verilator lint_off UNUSED + wire unused_tlast_sync; + assign unused_tlast_sync = &{ 1'b0, sskd_last }; + // Verilator lint_on UNUSED + end endgenerate + + // Incoming FIFO + // {{{ + generate if (C_AXIS_TUSER_WIDTH > 0) + begin : FIFO_WITH_USER_DATA + + sfifo #( + // {{{ + .BW(C_AXIS_TUSER_WIDTH + C_AXI_DATA_WIDTH), + .LGFLEN(LGFIFO), .OPT_ASYNC_READ(OPT_ASYNCMEM) + // }}} + ) u_sfifo ( + // {{{ + .i_clk(i_clk), .i_reset(reset_fifo), + .i_wr(write_to_fifo), + .i_data({ sskd_user, sskd_data }), + .o_full(fifo_full), .o_fill(fifo_fill), + .i_rd(read_from_fifo), .o_data(fifo_data), + .o_empty(fifo_empty) + // }}} + ); + + assign { M_AXI_WUSER, M_AXI_WDATA } = fifo_data; + + end else begin : NO_USER_DATA + + sfifo #( + // {{{ + .BW(C_AXI_DATA_WIDTH), + .LGFLEN(LGFIFO), .OPT_ASYNC_READ(OPT_ASYNCMEM) + // }}} + ) u_sfifo ( + // {{{ + .i_clk(i_clk), .i_reset(reset_fifo), + .i_wr(write_to_fifo), .i_data(sskd_data), + .o_full(fifo_full), .o_fill(fifo_fill), + .i_rd(read_from_fifo), .o_data(fifo_data), + .o_empty(fifo_empty) + // }}} + ); + + assign M_AXI_WDATA = fifo_data; + assign M_AXI_WUSER = 0; + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_tuser; + assign unused_tuser = &{ 1'b0, sskd_user }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The outgoing AXI (full) protocol section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // + + // Some counters to keep track of our state + // {{{ + + + // Count the number of word writes left to be requested, starting + // with the overall command length and then reduced by M_AWLEN on + // each address write + // {{{ + always @(*) + begin + // aw_next_remaining = aw_requests_remaining; + // if (phantom_start) + // aw_next_remaining = aw_requests_remaining + // + (~{{ (LGLENW-8){1'b0}}, M_AXI_AWLEN }); + // // - (M_AXI_AWLEN+1) + // // + (~M_AXI_AWLEN+1) - 1 + // // + ~M_AXI_AWLEN + aw_next_remaining = aw_requests_remaining + + { {(LGLENW-8){phantom_start}}, + (phantom_start) ? ~M_AXI_AWLEN : 8'h00}; + end + + initial r_pre_start = 1; + always @(posedge i_clk) + if (!r_busy) + r_pre_start <= 1; + else + r_pre_start <= 0; + + always @(posedge i_clk) + if (!r_busy) + begin + aw_needs_alignment <= 0; + + if (|new_wideaddr[ADDRLSB +: LGMAXBURST]) + begin + if (|new_widelen[LGLEN-1:(LGMAXBURST+ADDRLSB)]) + aw_needs_alignment <= 1; + if (~new_wideaddr[ADDRLSB +: LGMAXBURST] + < new_widelen[ADDRLSB +: LGMAXBURST]) + aw_needs_alignment <= 1; + end + end + + initial aw_none_remaining = 1; + initial aw_requests_remaining = 0; + always @(posedge i_clk) + if (!r_busy) + begin + aw_requests_remaining <= cmd_length_w; + aw_none_remaining <= zero_length; + aw_multiple_bursts_remaining <= |cmd_length_w[LGLENW-1:LGMAXBURST+1]; + end else if (cmd_abort || axi_abort_pending) + begin + aw_requests_remaining <= 0; + aw_none_remaining <= 1; + aw_multiple_bursts_remaining <= 0; + end else if (phantom_start) + begin + aw_requests_remaining <= aw_next_remaining; + aw_none_remaining<= !aw_multiple_bursts_remaining + &&(aw_next_remaining[LGMAXBURST:0] == 0); + aw_multiple_bursts_remaining + <= |aw_next_remaining[LGLENW-1:LGMAXBURST+1]; + end + // }}} + + // Calculate the maximum possible burst length, ignoring 4kB boundaries + // {{{ + always @(*) + addralign = 1+(~cmd_addr[ADDRLSB +: LGMAXBURST]); + + always @(*) + begin + initial_burstlen = (1<<LGMAXBURST); + if (!r_increment) + begin + initial_burstlen = MAX_FIXED_BURST; + if (!aw_multiple_fixed_bursts) + initial_burstlen = { 1'b0, cmd_length_w[LGMAXBURST-1:0] }; + end else if (aw_needs_alignment) + initial_burstlen = { 1'b0, addralign }; + else if (!aw_multiple_full_bursts) + initial_burstlen = { 1'b0, cmd_length_w[LGMAXBURST-1:0] }; + end + + initial r_max_burst = 0; + always @(posedge i_clk) + if (!r_busy || r_pre_start) + begin + // Force us to align ourself early + // That way we don't need to check for + // alignment (again) later + r_max_burst <= initial_burstlen; + end else if (phantom_start) + begin + // Verilator lint_off WIDTH + if (r_increment || LGMAXBURST <= LGMAX_FIXED_BURST) + begin + if (!aw_multiple_bursts_remaining + && aw_next_remaining[LGMAXBURST:0] < (1<<LGMAXBURST)) + r_max_burst <= { 1'b0, aw_next_remaining[7:0] }; + else + r_max_burst <= (1<<LGMAXBURST); + end else begin + if (!aw_multiple_bursts_remaining + && aw_next_remaining[LGMAXBURST:0] < MAX_FIXED_BURST) + r_max_burst <= { 1'b0, aw_next_remaining[7:0] }; + else + r_max_burst <= MAX_FIXED_BURST; + end + // Verilator lint_on WIDTH + end + // }}} + + // Count the number of bursts outstanding--these are the number of + // AWVALIDs that have been accepted, but for which the BVALID has not + // (yet) been returned. + // {{{ + initial aw_last_outstanding = 0; + initial aw_none_outstanding = 1; + initial aw_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset) + begin + aw_bursts_outstanding <= 0; + aw_none_outstanding <= 1; + aw_last_outstanding <= 0; + end else case ({ phantom_start, M_AXI_BVALID && M_AXI_BREADY }) + 2'b01: begin + aw_bursts_outstanding <= aw_bursts_outstanding - 1; + aw_none_outstanding <= (aw_bursts_outstanding == 1); + aw_last_outstanding <= (aw_bursts_outstanding == 2); + end + 2'b10: begin + aw_none_outstanding <= 0; + aw_bursts_outstanding <= aw_bursts_outstanding + 1; + aw_last_outstanding <= (aw_bursts_outstanding == 0); + end + default: begin end + endcase + // }}} + + // Are we there yet? + // {{{ + // We can't just look for the last BVALID, since ... it might be + // possible to receive an abort before the FIFO is full enough to + // initiate the first burst. + always @(*) + if (!r_busy) + w_complete = 0; + else + w_complete = !M_AXI_AWVALID && (aw_none_remaining) + &&((aw_last_outstanding && M_AXI_BVALID) + || aw_none_outstanding); + // }}} + + // Are we stopping early? Aborting something ongoing? + // {{{ + initial axi_abort_pending = 0; + always @(posedge i_clk) + if (i_reset || !r_busy) + axi_abort_pending <= 0; + else begin + if (M_AXI_BVALID && M_AXI_BREADY && M_AXI_BRESP[1]) + axi_abort_pending <= 1; + if (cmd_abort) + axi_abort_pending <= 1; + if (r_err) + axi_abort_pending <= 1; + end + // }}} + + // Count the number of WVALIDs yet to be sent on the write channel + // {{{ + initial wr_none_pending = 1; + initial wr_writes_pending = 0; + always @(posedge i_clk) + if (i_reset) + begin + wr_writes_pending <= 0; + wr_none_pending <= 1; + end else case ({ phantom_start, + M_AXI_WVALID && M_AXI_WREADY }) + 2'b00: begin end + 2'b01: begin + wr_writes_pending <= wr_writes_pending - 1; + wr_none_pending <= (wr_writes_pending == 1); + end + 2'b10: begin + wr_writes_pending <= wr_writes_pending + (M_AXI_AWLEN[LGMAXBURST-1:0] + 1); + wr_none_pending <= 0; + end + 2'b11: begin + wr_writes_pending <= wr_writes_pending + (M_AXI_AWLEN[LGMAXBURST-1:0]); + wr_none_pending <= (M_AXI_WLAST); + end + endcase + // }}} + + // So that we can monitor where we are at, and perhaps restart it + // later, keep track of the current address used by the W-channel + // {{{ + initial axi_addr = 0; + always @(posedge i_clk) + begin + if (!r_busy) + axi_addr <= cmd_addr; + else if (axi_abort_pending || !r_increment) + // Stop incrementing the address following an abort + axi_addr <= axi_addr; + else if (M_AXI_WVALID && M_AXI_WREADY) + axi_addr <= axi_addr + (1<<ADDRLSB); + + if (!OPT_UNALIGNED) + axi_addr[ADDRLSB-1:0] <= 0; + end + // }}} + + // Count the number of words remaining to be written on the W channel + // {{{ + // initial r_none_remaining = 1; + initial r_remaining_w = 0; + always @(posedge i_clk) + if (i_reset) + begin + r_remaining_w <= 0; + // r_none_remaining <= 1; + end else if (!r_busy) + begin + r_remaining_w<= cmd_length_w; + // r_none_remaining <= zero_length; + end else if (M_AXI_WVALID && M_AXI_WREADY) + begin + r_remaining_w <= r_remaining_w - 1; + // r_none_remaining <= (r_remaining_w == 1); + end + // }}} + + // + // }}} + + // Phantom starts + // {{{ + // Since we can't use the xREADY signals in our signaling, we hvae to + // be ready to generate both AWVALID and WVALID on the same cycle, + // and then hold AWVALID until it has been accepted. This means we + // can't use AWVALID as our burst start signal like we could in the + // slave. Instead, we'll use a "phantom" start signal. This signal + // is local here in our code. When this signal goes high, AWVALID + // and WVALID go high at the same time. Then, if AWREADY isn't held, + // we can still update all of our internal counters as though it were, + // based upon the phantom_start signal, and continue as though + // AWVALID were accepted on its first clock period. + + always @(*) + begin + // We start again if there's more information to transfer + w_phantom_start = !aw_none_remaining; + + // But not if the amount of information we need isn't (yet) + // in the FIFO. + if (!sufficiently_filled) + w_phantom_start = 0; + + // Insist on a minimum of one clock between burst starts, + // since our burst length calculation takes a clock to do + if (phantom_start || r_pre_start) + w_phantom_start = 0; + + if (M_AXI_AWVALID && !M_AXI_AWREADY) + w_phantom_start = 0; + + // If we're still writing the last burst, then don't start + // any new ones + if (M_AXI_WVALID && (!M_AXI_WLAST || !M_AXI_WREADY)) + w_phantom_start = 0; + + // Finally, don't start any new bursts if we aren't already + // busy transmitting, or if we are in the process of aborting + // our transfer + if (!r_busy || cmd_abort || axi_abort_pending) + w_phantom_start = 0; + end + + initial phantom_start = 0; + always @(posedge i_clk) + if (i_reset) + phantom_start <= 0; + else + phantom_start <= w_phantom_start; + // }}} + + + // + // WLAST + // {{{ + always @(posedge i_clk) + if (!r_busy) + begin + axi_wlast <= (cmd_length_w == 1); + end else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (w_phantom_start) + axi_wlast <= (r_max_burst == 1); + else if (phantom_start) + axi_wlast <= (M_AXI_AWLEN == 1); + else + axi_wlast <= (wr_writes_pending == 1 + (M_AXI_WVALID ? 1:0)); + end + // }}} + + // Calculate AWLEN and AWADDR for the next AWVALID + // {{{ + // + initial data_available = 0; + always @(posedge i_clk) + if (reset_fifo) + data_available <= 0; + else if (axi_abort_pending) + data_available <= fifo_fill + (write_to_fifo ? 1:0); + else case({ write_to_fifo, phantom_start }) + 2'b10: data_available <= data_available + 1; + // Verilator lint_off WIDTH + 2'b01: data_available <= data_available - (M_AXI_AWLEN+1); + 2'b11: data_available <= data_available - (M_AXI_AWLEN); + // Verilator lint_on WIDTH + default: begin end + endcase + + always @(*) + if (|aw_requests_remaining[LGLENW-1:LGMAXBURST]) + sufficiently_filled = |data_available[LGFIFO:LGMAXBURST]; + else + sufficiently_filled = (data_available[LGMAXBURST-1:0] + >= aw_requests_remaining[LGMAXBURST-1:0]); + + // + // axi_awlen + // {{{ + generate if (LGMAXBURST >= 8) + begin : GEN_BIG_AWLEN + + always @(posedge i_clk) + if (!M_AXI_AWVALID || M_AXI_AWREADY) + axi_awlen <= r_max_burst[7:0] - 8'd1; + + end else begin : GEN_SHORT_AWLEN + + always @(posedge i_clk) + if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + axi_awlen <= { {(8-LGMAXBURST){1'b0}}, r_max_burst } - 8'd1; + axi_awlen[7:LGMAXBURST] <= 0; + end + + end endgenerate + // }}} + + always @(posedge i_clk) + begin + if (M_AXI_AWVALID && M_AXI_AWREADY) + begin + axi_awaddr[ADDRLSB-1:0] <= 0; + // Verilator lint_off WIDTH + if (r_increment) + axi_awaddr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + <= axi_awaddr[C_AXI_ADDR_WIDTH-1:ADDRLSB] + + (M_AXI_AWLEN+1); + end + // Verilator lint_on WIDTH + + if (!r_busy) + axi_awaddr<= cmd_addr; + + if (!OPT_UNALIGNED) + axi_awaddr[ADDRLSB-1:0] <= 0; + end + // }}} + + // AWVALID + // {{{ + initial axi_awvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_awvalid <= 0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + axi_awvalid <= w_phantom_start; + // }}} + + // WVALID + // {{{ + initial axi_wvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_wvalid <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (M_AXI_WVALID && !M_AXI_WLAST) + axi_wvalid <= 1; + else + axi_wvalid <= w_phantom_start; + end + // }}} + + // axi_wstrb + // {{{ + always @(posedge i_clk) + if (!M_AXI_WVALID || M_AXI_WREADY) + axi_wstrb <= (axi_abort_pending) ? 0:-1; + // }}} + + // Fixed bus values + // {{{ + assign M_AXI_AWVALID= axi_awvalid; + assign M_AXI_AWID = AXI_ID; + assign M_AXI_AWADDR = axi_awaddr; + assign M_AXI_AWLEN = axi_awlen; + // Verilator lint_off WIDTH + assign M_AXI_AWSIZE = $clog2(C_AXI_DATA_WIDTH)-3; + // Verilator lint_on WIDTH + assign M_AXI_AWBURST= { 1'b0, r_increment }; + assign M_AXI_AWLOCK = 0; + assign M_AXI_AWCACHE= 4'h3; + assign M_AXI_AWPROT = 0; + assign M_AXI_AWQOS = 0; + + assign M_AXI_WVALID = axi_wvalid; + assign M_AXI_WSTRB = axi_wstrb; + assign M_AXI_WLAST = axi_wlast; + // M_AXI_WLAST = ?? + + assign M_AXI_BREADY = 1; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // (Optional) Clock gating + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_CLKGATE) + begin : CLK_GATING + // {{{ + reg gatep, r_clk_active; + reg gaten /* verilator clock_enable */; + + // clk_active + // {{{ + initial r_clk_active = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_clk_active <= 1'b1; + else begin + r_clk_active <= 1'b0; + + if (r_busy) + r_clk_active <= 1'b1; + if (awskd_valid || wskd_valid || arskd_valid) + r_clk_active <= 1'b1; + if (S_AXIL_BVALID || S_AXIL_RVALID) + r_clk_active <= 1'b1; + + // Activate the clock on incoming data + // reset_fifo = i_reset || (!r_busy && (!r_continuous || r_err)); + // !reset_fifo= r_busy || (r_continuous && !r_err) + // !reset_fifo= (r_continuous && !r_err) + if (sskd_valid && !fifo_full + && (!tlast_syncd || (r_continuous && !r_err))) + r_clk_active <= 1'b1; + end + + assign clk_active = r_clk_active; + // }}} + // Gate the clock here locally + // {{{ + initial gatep = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + gatep <= 1'b1; + else + gatep <= clk_active; + + initial gaten = 1'b1; + always @(negedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + gaten <= 1'b1; + else + gaten <= gatep; + + assign gated_clk = S_AXI_ACLK && gaten; + + assign clk_active = r_clk_active; + // }}} + // }}} + end else begin : NO_CLK_GATING + // {{{ + // Always active + assign clk_active = 1'b1; + assign gated_clk = S_AXI_ACLK; + // }}} + end endgenerate + // }}} + + // Keep Verilator happy + // {{{ + // Verilator coverage_off + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_BID, + M_AXI_BRESP[0], fifo_empty, + wr_none_pending, S_AXIL_ARADDR[AXILLSB-1:0], + S_AXIL_AWADDR[AXILLSB-1:0], + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], + new_widelen }; + // Verilator coverage_on + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // The formal properties for this unit are maintained elsewhere. + // This core does, however, pass a full prove (w/ induction) for all + // bus properties. + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // The AXI-stream data interface + // {{{ + // + //////////////////////////////////////////////////////////////////////// + // + // + + // (These are captured by the FIFO within) + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_AXIL_LGDEPTH = 4; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_rd_outstanding == (S_AXIL_RVALID ? 1:0) + +(S_AXIL_ARREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXIL_BVALID ? 1:0) + +(S_AXIL_WREADY ? 0:1)); + assert(faxil_awr_outstanding== (S_AXIL_BVALID ? 1:0) + +(S_AXIL_AWREADY ? 0:1)); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI master memory interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // ... + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // + .OPT_EXCLUSIVE(1'b0), + .OPT_NARROW_BURST(1'b0), + // + // ... + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awid( M_AXI_AWID), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awlen( M_AXI_AWLEN), + .i_axi_awsize( M_AXI_AWSIZE), + .i_axi_awburst(M_AXI_AWBURST), + .i_axi_awlock( M_AXI_AWLOCK), + .i_axi_awcache(M_AXI_AWCACHE), + .i_axi_awprot( M_AXI_AWPROT), + .i_axi_awqos( M_AXI_AWQOS), + // + .i_axi_wvalid(M_AXI_WVALID), + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + .i_axi_wlast( M_AXI_WLAST), + // + .i_axi_bvalid(M_AXI_BVALID), + .i_axi_bready(M_AXI_BREADY), + .i_axi_bid( M_AXI_BID), + .i_axi_bresp( M_AXI_BRESP), + // + .i_axi_arvalid(1'b0), + .i_axi_arready(1'b0), + .i_axi_arid( M_AXI_AWID), + .i_axi_araddr( M_AXI_AWADDR), + .i_axi_arlen( M_AXI_AWLEN), + .i_axi_arsize( M_AXI_AWSIZE), + .i_axi_arburst(M_AXI_AWBURST), + .i_axi_arlock( M_AXI_AWLOCK), + .i_axi_arcache(M_AXI_AWCACHE), + .i_axi_arprot( M_AXI_AWPROT), + .i_axi_arqos( M_AXI_AWQOS), + // + .i_axi_rvalid(1'b0), + .i_axi_rready(1'b0), + .i_axi_rdata({(C_AXI_DATA_WIDTH){1'b0}}), + .i_axi_rlast(1'b0), + .i_axi_rresp(2'b00) + // + // + // }}} + ); + + // + // ... + // + + always @(*) + assert(aw_bursts_outstanding + == faxi_awr_nbursts + + ((M_AXI_AWVALID&&!phantom_start) ? 1:0)); + + // + // ... + // + + always @(posedge i_clk) + if (M_AXI_AWVALID) + begin + // ... + if (phantom_start) + begin + assert(wr_writes_pending == 0); + assert(wr_none_pending); + end else if ($past(phantom_start)) + begin + assert(wr_writes_pending <= M_AXI_AWLEN+1); + end + end else begin + // ... + assert(wr_none_pending == (wr_writes_pending == 0)); + end + + always @(*) + if (r_busy && !OPT_UNALIGNED) + assert(M_AXI_AWADDR[ADDRLSB-1:0] == 0); + + always @(*) + if (!OPT_UNALIGNED) + assert(cmd_addr[ADDRLSB-1:0] == 0); + + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other formal properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // ... + // + + always @(*) + if (!r_busy) + begin + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assert(!M_AXI_BVALID); + // + // ... + // + end + + always @(*) + assert(zero_length == (cmd_length_w == 0)); + + // + // ... + // + always @(*) + if (phantom_start) + begin + assert(data_available >= (M_AXI_AWLEN+1)); + end else if (M_AXI_AWVALID) + begin + assert(data_available <= (1<<LGFIFO)); + end + + + always @(*) + if (phantom_start) + assert(wr_writes_pending == 0); + + always @(*) + if (phantom_start) + begin + assert(fifo_fill >= (M_AXI_AWLEN+1)); + end else if (!axi_abort_pending) + assert(fifo_fill >= wr_writes_pending); + + always @(*) + if (r_busy) + begin + if (!aw_none_remaining && !phantom_start) + begin + assert(aw_requests_remaining + + wr_writes_pending == r_remaining_w); + + // Make sure we don't wrap + assert(wr_writes_pending <= r_remaining_w); + end else if (!aw_none_remaining) + begin + assert(aw_requests_remaining == r_remaining_w); + + // Make sure we don't wrap + assert(wr_writes_pending == 0); + end + end else + assert(!M_AXI_WVALID); + + always @(*) + assert(aw_none_remaining == (aw_requests_remaining == 0)); + + always @(*) + if (r_busy) + assert(aw_multiple_bursts_remaining == (|aw_requests_remaining[LGLENW-1:LGMAXBURST+1])); + + always @(*) + begin + assert(aw_last_outstanding == (aw_bursts_outstanding == 1)); + assert(aw_none_outstanding == (aw_bursts_outstanding == 0)); + end + + // + // ... + // + + always @(*) + if (r_complete) + assert(!r_busy); + + always @(*) + assert(fifo_fill >= wr_writes_pending); + + always @(*) + if (r_busy) + assert(r_max_burst <= (1<<LGMAXBURST)); + + always @(*) + if (r_busy && !r_pre_start) + assert((r_max_burst > 0) || (aw_requests_remaining == 0)); + + + always @(*) + if (phantom_start) + begin + assert(M_AXI_AWVALID && M_AXI_WVALID); + assert(wr_none_pending); + // assert(drain_triggered); + end + + always @(posedge i_clk) + if (phantom_start) + begin + assert(r_max_burst > 0); + assert(M_AXI_AWLEN == $past(r_max_burst)-1); + end + + always @(*) + if (r_busy && !r_err && !cmd_abort && aw_requests_remaining == f_length) + assert(initial_burstlen > 0); + + always @(*) + if ((LGMAXBURST < 8) && (r_busy)) + assert(M_AXI_AWLEN+1 <= (1<<LGMAXBURST)); + + always @(*) + if (r_busy && !r_increment && (M_AXI_AWVALID + || ((aw_requests_remaining < cmd_length_w) + && (aw_requests_remaining > 0)))) + assert(M_AXI_AWLEN+1 <= MAX_FIXED_BURST); + + always @(*) + if (M_AXI_AWVALID && M_AXI_AWADDR[ADDRLSB +: LGMAXBURST]) + begin + // If we are ever unaligned, our first step should be to + // align ourselves + assert(M_AXI_AWLEN+1 <= + 1 +(~M_AXI_AWADDR[ADDRLSB +: LGMAXBURST])); + end + + always @(*) + if (!wr_none_pending) + begin + // Second alignment check: every burst must end aligned + if (r_increment) + assert(axi_addr[ADDRLSB +: LGMAXBURST] + wr_writes_pending + <= (1<<LGMAXBURST)); + end + + always @(posedge i_clk) + if (phantom_start) + begin + assert(axi_awlen == $past(r_max_burst[7:0]) - 8'd1); + if (r_increment && (cmd_length_w > axi_awlen + 1) + &&(aw_requests_remaining != cmd_length_w)) + assert(M_AXI_AWADDR[ADDRLSB +: LGMAXBURST] == 0); + end + + // + // ... + // + + // }}} + + // + // Synchronization properties + // {{{ + always @(*) + if (fifo_full || !clk_active) + begin + assert(!sskd_ready); + end else if (OPT_TREADY_WHILE_IDLE) + begin + // If we aren't full, and we set TREADY whenever idle, + // then we should otherwise have TREADY set at all times + assert(sskd_ready); + end else if (!tlast_syncd) + begin + // If we aren't syncd, always be ready until we finally sync up + assert(sskd_ready); + end else if (reset_fifo) + begin + // If we aren't accepting any data, but are idling with TREADY + // low, then make sure we drop TREADY when idle + assert(!sskd_ready); + end else + // In all other cases, assert TREADY + assert(sskd_ready); + + + // + // ... + // + // }}} + + // + // Error logic checking + // {{{ + always @(*) + if (!r_err) + begin + assert(r_errcode == 0); + end else + assert(r_errcode != 0); + + // + // ... + // + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN) && $past(w_cmd_start) + &&!$past(overflow && r_continuous)) + assert(!r_err); + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Contract checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // 1. All data values must get sent, and none skipped + // Captured in logic above, since M_AXI_WDATA is registered + // within the FIFO and not our interface + // + // 2. No addresses skipped. + // ... + // + + // 3. If we aren't incrementing addresses, then our current address + // should always be the axi address + always @(*) + if (r_busy && !r_increment) + assert(axi_addr == cmd_addr); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg cvr_aborted, cvr_buserr, cvr_abort_clear; + reg [2:0] cvr_continued; + + initial { cvr_aborted, cvr_buserr } = 0; + always @(posedge i_clk) + if (i_reset) + { cvr_aborted, cvr_buserr } <= 0; + else if (r_busy && !axi_abort_pending) + begin + if (cmd_abort && wr_writes_pending > 0) + cvr_aborted <= 1; + if (M_AXI_BVALID && M_AXI_BRESP[1]) + cvr_buserr <= 1; + end + + always @(posedge i_clk) + if (i_reset) + cvr_abort_clear <= 1'b0; + else if (cvr_aborted && !cvr_buserr && !cmd_abort) + begin + cvr_abort_clear <= 1; + end + + always @(posedge i_clk) + if (!i_reset) + begin + cover(cvr_abort_clear); + end + + initial cvr_continued = 0; + always @(posedge i_clk) + if (i_reset || r_err || cmd_abort) + cvr_continued <= 0; + else begin + // Cover a continued transaction across two separate bursts + if (r_busy && r_continuous) + cvr_continued[0] <= 1; + if (!r_busy && cvr_continued[0]) + cvr_continued[1] <= 1; + if (r_busy && cvr_continued[1]) + cvr_continued[2] <= 1; + + // + // Artificially force us to look for two separate runs + if((!r_busy)&&($changed(cmd_length_w))) + cvr_continued <= 0; + end + + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && !i_reset && $fell(r_busy)) + begin + cover( r_err && cvr_aborted); + cover( r_err && cvr_buserr); + cover(!r_err); + if (!r_err && !axi_abort_pending && !cvr_aborted && !cvr_buserr) + begin + cover(cmd_length_w > 5); + cover(cmd_length_w > 8); + cover((cmd_length_w > 5)&&(cmd_addr[11:0] == 12'hff0)); + cover(&cvr_continued && (cmd_length_w > 5)); + end + end + // }}} + + // This ends our formal property set +`endif + // }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axisafety.v b/rtl/wb2axip/axisafety.v new file mode 100644 index 0000000..ff7e351 --- /dev/null +++ b/rtl/wb2axip/axisafety.v @@ -0,0 +1,2339 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisafety.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Given that 1) AXI interfaces can be difficult to write and to +// get right, 2) my experiences with the AXI infrastructure of an +// ARM+FPGA is that the device needs a power cycle following a bus fault, +// and given that I've now found multiple interfaces that had some bug +// or other within them, the following is a bus fault isolator. It acts +// as a bump in the log between the interconnect and the user core. As +// such, it has two interfaces: The first is the slave interface, +// coming from the interconnect. The second interface is the master +// interface, proceeding to a slave that might be faulty. +// +// INTERCONNECT --> (S) AXISAFETY (M) --> POTENTIALLY FAULTY CORE +// +// The slave interface has been formally verified to be a valid AXI +// slave, independent of whatever the potentially faulty core might do. +// If the user core (i.e. the potentially faulty one) responds validly +// to the requests of a master, then this core will turn into a simple +// delay on the bus. If, on the other hand, the user core suffers from +// a protocol error, then this core will set one of two output +// flags--either o_write_fault or o_read_fault indicating whether a write +// or a read fault was detected. Further attempts to access the user +// core will result in bus errors, generated by the AXISAFETY core. +// +// Assuming the bus master can properly detect and deal with a bus error, +// this should then make it possible to properly recover from a bus +// protocol error without later needing to cycle power. +// +// The core does have a couple of limitations. For example, it can only +// handle one burst transaction, and one ID at a time. This matches the +// performances of Xilinx's example AXI full core, from which many user +// cores have have been copied/modified from. Therefore, there will be +// no performance loss for such a core. +// +// Because of this, it is still possible that the user core might have an +// undetected fault when using this core. For example, if the interconnect +// issues more than one bus request before receiving the response from the +// first request, this safety core will stall the second request, +// preventing the downstream core from seeing this second request. If the +// downstream core would suffer from an error while handling the second +// request, by preventing the user core from seeing the second request this +// core eliminates that potential for error. +// +// Usage: The important part of using this core is to connect the slave +// side to the interconnect, and the master side to the user core. +// +// Some options are available: +// +// 1) C_S_AXI_ADDR_WIDTH The number of address bits in the AXI channel +// 1) C_S_AXI_DATA_WIDTH The number of data bits in the AXI channel +// 3) C_S_AXI_ID_WIDTH The number of bits in an AXI ID. As currently +// written, this number cannot be zero. You can, however, set it +// to '1' and connect all of the relevant ID inputs to zero. +// +// These three parameters have currently been abbreviated with AW, DW, and +// IW. I anticipate returning them to their original meanings, I just +// haven't done so (yet). +// +// 4) OPT_TIMEOUT This is the number of clock periods, from +// interconnect request to interconnect response, that the +// slave needs to respond within. (The actual timeout from the +// user slave's perspective will be shorter than this.) +// +// This timeout is part of the check that a slave core will +// always return a response. From the standpoint of this core, +// it can be set arbitrarily large. (From the standpoint of +// formal verification, it needs to be kept short ...) +// Feel free to set this even as large as 1ms if you would like. +// +// 5) OPT_SELF_RESET If set, will send a reset signal to the slave +// following any write or read fault. This will cause the other +// side of the link (write or read) to fault as well. Once the +// channel then becomes inactive, the slave will be released from +// reset and will be able to interact with the rest of the bus +// again. +// +// 5) OPT_EXCLUSIVE If clear, will prohibit the slave from ever +// receiving an exclusive access request. This saves the logic +// in the firewall necessary to check that an EXOKAY response +// to a write request was truly an allowed response. Since that +// logic can explode with the ID width, sparing it can be quite +// useful. If set, provides full exclusive access checking in +// addition to the normal bus fault checking. +// +// Performance: As mentioned above, this core can handle one read burst and one +// write burst at a time, no more. Further, the core will delay +// an input path by one clock and the output path by another clock, so that +// the latency involved with using this core is a minimum of two extra +// clocks beyond the latency user slave core. +// +// Maximum write latency: N+3 clocks for a burst of N values +// Maximum read latency: N+3 clocks for a burst of N values +// +// Faults detected: +// +// Write channel: +// 1. Raising BVALID prior to the last write value being sent +// to the user/slave core. +// 2. Raising BVALID prior to accepting the write address +// 3. A BID return ID that doesn't match the request AWID +// 4. Sending too many returns, such as not dropping BVALID +// or raising it when there is no outstanding write +// request +// +// While the following technically isn't a violation of the +// AXI protocol, it is treated as such by this fault isolator. +// +// 5. Accepting write data prior to the write address +// +// The fault isolator will guarantee that AWVALID is raised before +// WVALID, so this shouldn't be a problem. +// +// Read channel: +// +// 1. Raising RVALID before accepting the read address, ARVALID +// 2. A return ID that doesn't match the ID that was sent. +// 3. Raising RVALID after the last return value, and so returning +// too many response values +// 4. Setting the RLAST value on anything but the last value from +// the bus. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axisafety #( + // {{{ + parameter C_S_AXI_ID_WIDTH = 1, + parameter C_S_AXI_DATA_WIDTH = 32, + parameter C_S_AXI_ADDR_WIDTH = 16, + // OPT_SELF_RESET: Set to true if the downstream slave should be + // reset upon detecting an error and separate from the upstream reset + // domain + parameter [0:0] OPT_SELF_RESET = 1'b0, + // OPT_EXCLUSIVE: Set to true if the downstream slave might possibly + // offer an exclusive access capability + parameter [0:0] OPT_EXCLUSIVE = 1'b0, + // + // I use the following abbreviations, IW, DW, and AW, to simplify + // the code below (and to help it fit within an 80 col terminal) + localparam IW = C_S_AXI_ID_WIDTH, + localparam DW = C_S_AXI_DATA_WIDTH, + localparam AW = C_S_AXI_ADDR_WIDTH, + // + // OPT_TIMEOUT references the number of clock cycles to wait + // between raising the *VALID signal and when the respective + // *VALID return signal must be high. You might wish to set this + // to a very high number, to allow your core to work its magic. + parameter OPT_TIMEOUT = 20 + // }}} + ) ( + // {{{ + output reg o_read_fault, + output reg o_write_fault, + // User ports ends + // Do not modify the ports beyond this line + + // Global Clock Signal + input wire S_AXI_ACLK, + // Global Reset Signal. This Signal is Active LOW + input wire S_AXI_ARESETN, + output reg M_AXI_ARESETN, + // + // The input side. This is where slave requests come into + // the core. + // {{{ + // + // Write address + input wire [IW-1 : 0] S_AXI_AWID, + input wire [AW-1 : 0] S_AXI_AWADDR, + input wire [7 : 0] S_AXI_AWLEN, + input wire [2 : 0] S_AXI_AWSIZE, + input wire [1 : 0] S_AXI_AWBURST, + input wire S_AXI_AWLOCK, + input wire [3 : 0] S_AXI_AWCACHE, + input wire [2 : 0] S_AXI_AWPROT, + input wire [3 : 0] S_AXI_AWQOS, + input wire S_AXI_AWVALID, + output reg S_AXI_AWREADY, + // Write data + input wire [DW-1 : 0] S_AXI_WDATA, + input wire [(DW/8)-1 : 0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + input wire S_AXI_WVALID, + output reg S_AXI_WREADY, + // Write return + output reg [IW-1 : 0] S_AXI_BID, + output reg [1 : 0] S_AXI_BRESP, + output reg S_AXI_BVALID, + input wire S_AXI_BREADY, + // Read address + input wire [IW-1 : 0] S_AXI_ARID, + input wire [AW-1 : 0] S_AXI_ARADDR, + input wire [7 : 0] S_AXI_ARLEN, + input wire [2 : 0] S_AXI_ARSIZE, + input wire [1 : 0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [3 : 0] S_AXI_ARCACHE, + input wire [2 : 0] S_AXI_ARPROT, + input wire [3 : 0] S_AXI_ARQOS, + input wire S_AXI_ARVALID, + output reg S_AXI_ARREADY, + // Read data + output reg [IW-1 : 0] S_AXI_RID, + output reg [DW-1 : 0] S_AXI_RDATA, + output reg [1 : 0] S_AXI_RRESP, + output reg S_AXI_RLAST, + output reg S_AXI_RVALID, + input wire S_AXI_RREADY, + // }}} + // + // The output side, where slave requests are forwarded to the + // actual slave + // {{{ + // Write address + // {{{ + output reg [IW-1 : 0] M_AXI_AWID, + output reg [AW-1 : 0] M_AXI_AWADDR, + output reg [7 : 0] M_AXI_AWLEN, + output reg [2 : 0] M_AXI_AWSIZE, + output reg [1 : 0] M_AXI_AWBURST, + output reg M_AXI_AWLOCK, + output reg [3 : 0] M_AXI_AWCACHE, + output reg [2 : 0] M_AXI_AWPROT, + output reg [3 : 0] M_AXI_AWQOS, + output reg M_AXI_AWVALID, + input wire M_AXI_AWREADY, + // Write data + output reg [DW-1 : 0] M_AXI_WDATA, + output reg [(DW/8)-1 : 0] M_AXI_WSTRB, + output reg M_AXI_WLAST, + output reg M_AXI_WVALID, + input wire M_AXI_WREADY, + // Write return + input wire [IW-1 : 0] M_AXI_BID, + input wire [1 : 0] M_AXI_BRESP, + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + // }}} + // Read address + // {{{ + output reg [IW-1 : 0] M_AXI_ARID, + output reg [AW-1 : 0] M_AXI_ARADDR, + output reg [7 : 0] M_AXI_ARLEN, + output reg [2 : 0] M_AXI_ARSIZE, + output reg [1 : 0] M_AXI_ARBURST, + output reg M_AXI_ARLOCK, + output reg [3 : 0] M_AXI_ARCACHE, + output reg [2 : 0] M_AXI_ARPROT, + output reg [3 : 0] M_AXI_ARQOS, + output reg M_AXI_ARVALID, + input wire M_AXI_ARREADY, + // Read data + input wire [IW-1 : 0] M_AXI_RID, + input wire [DW-1 : 0] M_AXI_RDATA, + input wire [1 : 0] M_AXI_RRESP, + input wire M_AXI_RLAST, + input wire M_AXI_RVALID, + output wire M_AXI_RREADY + // }}} + // }}} + // }}} + ); + + localparam LGTIMEOUT = $clog2(OPT_TIMEOUT+1); + localparam [1:0] OKAY = 2'b00, EXOKAY = 2'b01; + localparam SLAVE_ERROR = 2'b10; + // + // + // Register declarations + // {{{ + reg faulty_write_return, faulty_read_return; + reg clear_fault; + // + // Timer/timeout variables + reg [LGTIMEOUT-1:0] write_timer, read_timer; + reg write_timeout, read_timeout; + + // + // Double buffer the write address channel + reg r_awvalid, m_awvalid; + reg [IW-1:0] r_awid, m_awid; + reg [AW-1:0] r_awaddr, m_awaddr; + reg [7:0] r_awlen, m_awlen; + reg [2:0] r_awsize, m_awsize; + reg [1:0] r_awburst, m_awburst; + reg r_awlock, m_awlock; + reg [3:0] r_awcache, m_awcache; + reg [2:0] r_awprot, m_awprot; + reg [3:0] r_awqos, m_awqos; + + // + // Double buffer for the write channel + reg r_wvalid,m_wvalid; + reg [DW-1:0] r_wdata, m_wdata; + reg [DW/8-1:0] r_wstrb, m_wstrb; + reg r_wlast, m_wlast; + + // + // Double buffer the write response channel + reg m_bvalid; // r_bvalid == 0 + reg [IW-1:0] m_bid; + reg [1:0] m_bresp; + + // + // Double buffer the read address channel + reg r_arvalid, m_arvalid; + reg [IW-1:0] r_arid, m_arid; + reg [AW-1:0] r_araddr, m_araddr; + reg [7:0] r_arlen, m_arlen; + reg [2:0] r_arsize, m_arsize; + reg [1:0] r_arburst, m_arburst; + reg r_arlock, m_arlock; + reg [3:0] r_arcache, m_arcache; + reg [2:0] r_arprot, m_arprot; + reg [3:0] r_arqos, m_arqos; + + // + // Double buffer the read data response channel + reg r_rvalid,m_rvalid; + reg [IW-1:0] m_rid; + reg [1:0] r_rresp, m_rresp; + reg m_rlast; + reg [DW-1:0] r_rdata, m_rdata; + + // + // Write FIFO data + wire [IW-1:0] wfifo_id; + wire wfifo_lock; + + // + // Read FIFO data + reg [IW-1:0] rfifo_id; + reg rfifo_lock; + reg [8:0] rfifo_counter; + reg rfifo_empty, rfifo_last, rfifo_penultimate; + + // + // + reg [0:0] s_wbursts; + reg [8:0] m_wpending; + reg m_wempty, m_wlastctr; + wire waddr_valid, raddr_valid; + + wire lock_enabled, lock_failed; + wire [(1<<IW)-1:0] lock_active; + reg rfifo_first, exread; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write channel processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Write address processing + // {{{ + // S_AXI_AWREADY + // {{{ + initial S_AXI_AWREADY = (OPT_SELF_RESET) ? 0:1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_AWREADY <= !OPT_SELF_RESET; + else if (S_AXI_AWVALID && S_AXI_AWREADY) + S_AXI_AWREADY <= 0; + // else if (clear_fault) + // S_AXI_AWREADY <= 1; + else if (!S_AXI_AWREADY) + S_AXI_AWREADY <= S_AXI_BVALID && S_AXI_BREADY; + // }}} + + generate if (OPT_EXCLUSIVE) + begin : GEN_LOCK_ENABLED + // {{{ + reg r_lock_enabled, r_lock_failed; + + // lock_enabled + // {{{ + initial r_lock_enabled = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_EXCLUSIVE) + r_lock_enabled <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY) + r_lock_enabled <= S_AXI_AWLOCK && lock_active[S_AXI_AWID]; + else if (S_AXI_BVALID && S_AXI_BREADY) + r_lock_enabled <= 0; + // }}} + + // lock_failed + // {{{ + initial r_lock_failed = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_EXCLUSIVE) + r_lock_failed <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY) + r_lock_failed <= S_AXI_AWLOCK && !lock_active[S_AXI_AWID]; + else if (S_AXI_BVALID && S_AXI_BREADY) + r_lock_failed <= 0; + // }}} + + assign lock_enabled = r_lock_enabled; + assign lock_failed = r_lock_failed; + // }}} + end else begin : NO_LOCK_ENABLE + // {{{ + assign lock_enabled = 0; + assign lock_failed = 0; + // }}} + end endgenerate + + // waddr_valid + // {{{ + generate if (OPT_SELF_RESET) + begin : GEN_LCL_RESET + reg r_waddr_valid; + + initial r_waddr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_waddr_valid <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY) + r_waddr_valid <= 1; + else if (waddr_valid) + r_waddr_valid <= !S_AXI_BVALID || !S_AXI_BREADY; + + assign waddr_valid = r_waddr_valid; + end else begin : NO_LCL_RESET + assign waddr_valid = !S_AXI_AWREADY; + end endgenerate + // }}} + + // r_aw* + // {{{ + // Double buffer for the AW* channel + // + initial r_awvalid = 0; + always @(posedge S_AXI_ACLK) + begin + if (S_AXI_AWVALID && S_AXI_AWREADY) + begin + r_awvalid <= 1'b0; + r_awid <= S_AXI_AWID; + r_awaddr <= S_AXI_AWADDR; + r_awlen <= S_AXI_AWLEN; + r_awsize <= S_AXI_AWSIZE; + r_awburst <= S_AXI_AWBURST; + r_awlock <= S_AXI_AWLOCK; + r_awcache <= S_AXI_AWCACHE; + r_awprot <= S_AXI_AWPROT; + r_awqos <= S_AXI_AWQOS; + end else if (M_AXI_AWREADY) + r_awvalid <= 0; + + if (!S_AXI_ARESETN) + begin + r_awvalid <= 0; + r_awlock <= 0; + end + + if (!OPT_EXCLUSIVE) + r_awlock <= 1'b0; + end + // }}} + + // m_aw* + // {{{ + // Second half of the AW* double-buffer. The m_* terms reference + // either the value in the double buffer (assuming one is in there), + // or the incoming value (if the double buffer is empty) + // + always @(*) + if (r_awvalid) + begin + m_awvalid = r_awvalid; + m_awid = r_awid; + m_awaddr = r_awaddr; + m_awlen = r_awlen; + m_awsize = r_awsize; + m_awburst = r_awburst; + m_awlock = r_awlock; + m_awcache = r_awcache; + m_awprot = r_awprot; + m_awqos = r_awqos; + end else begin + m_awvalid = S_AXI_AWVALID && S_AXI_AWREADY; + m_awid = S_AXI_AWID; + m_awaddr = S_AXI_AWADDR; + m_awlen = S_AXI_AWLEN; + m_awsize = S_AXI_AWSIZE; + m_awburst = S_AXI_AWBURST; + m_awlock = S_AXI_AWLOCK && OPT_EXCLUSIVE; + m_awcache = S_AXI_AWCACHE; + m_awprot = S_AXI_AWPROT; + m_awqos = S_AXI_AWQOS; + end + // }}} + + // M_AXI_AW* + // {{{ + // Set the output AW* channel outputs--but only on no fault + // + initial M_AXI_AWVALID = 0; + always @(posedge S_AXI_ACLK) + begin + if (!M_AXI_AWVALID || M_AXI_AWREADY) + begin + + if (o_write_fault) + M_AXI_AWVALID <= 0; + else + M_AXI_AWVALID <= m_awvalid; + + M_AXI_AWID <= m_awid; + M_AXI_AWADDR <= m_awaddr; + M_AXI_AWLEN <= m_awlen; + M_AXI_AWSIZE <= m_awsize; + M_AXI_AWBURST <= m_awburst; + M_AXI_AWLOCK <= m_awlock && lock_active[m_awid]; + M_AXI_AWCACHE <= m_awcache; + M_AXI_AWPROT <= m_awprot; + M_AXI_AWQOS <= m_awqos; + end + + if (!OPT_EXCLUSIVE) + M_AXI_AWLOCK <= 0; + if (!M_AXI_ARESETN) + M_AXI_AWVALID <= 0; + end + // }}} + // }}} + + // s_wbursts + // {{{ + // Count write bursts outstanding from the standpoint of + // the incoming (slave) channel + // + // Notice that, as currently built, the count can only ever be one + // or zero. + // + // We'll use this count in a moment to determine if a response + // has taken too long, or if a response is returned when there's + // no outstanding request + initial s_wbursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_wbursts <= 0; + else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) + s_wbursts <= 1; + else if (S_AXI_BVALID && S_AXI_BREADY) + s_wbursts <= 0; + // }}} + + // wfifo_id, wfifo_lock + // {{{ + // Keep track of the ID of the last transaction. Since we only + // ever have one write transaction outstanding, this will need to be + // the ID of the returned value. + + assign wfifo_id = r_awid; + assign wfifo_lock = r_awlock; + // }}} + + // m_wpending, m_wempty, m_wlastctr + // {{{ + // m_wpending counts the number of (remaining) write data values that + // need to be sent to the slave. It counts this number with respect + // to the *SLAVE*, not the master. When m_wpending == 1, WLAST shall + // be true. To make comparisons of (m_wpending == 0) or (m_wpending>0), + // m_wempty is assigned to (m_wpending). Similarly, m_wlastctr is + // assigned to (m_wpending == 1). + // + initial m_wpending = 0; + initial m_wempty = 1; + initial m_wlastctr = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_write_fault) + begin + // {{{ + m_wpending <= 0; + m_wempty <= 1; + m_wlastctr <= 0; + // }}} + end else if (M_AXI_AWVALID && M_AXI_AWREADY) + begin + // {{{ + if (M_AXI_WVALID && M_AXI_WREADY) + begin + // {{{ + // Accepting AW* and W* packets on the same + // clock + if (m_wpending == 0) + begin + // The AW* and W* packets go together + m_wpending <= {1'b0, M_AXI_AWLEN}; + m_wempty <= (M_AXI_AWLEN == 0); + m_wlastctr <= (M_AXI_AWLEN == 1); + end else begin + // The W* packet goes with the last + // AW* command, the AW* packet with a + // new one + m_wpending <= M_AXI_AWLEN+1; + m_wempty <= 0; + m_wlastctr <= (M_AXI_AWLEN == 0); + end + // }}} + end else begin + // {{{ + m_wpending <= M_AXI_AWLEN+1; + m_wempty <= 0; + m_wlastctr <= (M_AXI_AWLEN == 0); + // }}} + end + // }}} + end else if (M_AXI_WVALID && M_AXI_WREADY && (!m_wempty)) + begin + // {{{ + // The AW* channel is idle, and we just accepted a value + // on the W* channel + m_wpending <= m_wpending - 1; + m_wempty <= (m_wpending <= 1); + m_wlastctr <= (m_wpending == 2); + // }}} + end + // }}} + + // Write data processing + // {{{ + // S_AXI_WREADY + // {{{ + // The S_AXI_WREADY or write channel stall signal + // + // For this core, we idle at zero (stalled) until an AW* packet + // comes through + // + initial S_AXI_WREADY = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_WREADY <= 0; + else if (S_AXI_WVALID && S_AXI_WREADY) + begin + // {{{ + if (S_AXI_WLAST) + S_AXI_WREADY <= 0; + else if (o_write_fault) + S_AXI_WREADY <= 1; + else if (!M_AXI_WVALID || M_AXI_WREADY) + S_AXI_WREADY <= 1; + else + S_AXI_WREADY <= 0; + // }}} + end else if ((s_wbursts == 0)&&(waddr_valid) + &&(o_write_fault || M_AXI_WREADY)) + S_AXI_WREADY <= 1; + else if (S_AXI_AWVALID && S_AXI_AWREADY) + S_AXI_WREADY <= 1; + // }}} + + // r_w* + // {{{ + // Double buffer for the write channel + // + // As before, the r_* values contain the values in the double + // buffer itself + // + initial r_wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_wvalid <= 0; + else if (!r_wvalid) + begin + // {{{ + r_wvalid <= (S_AXI_WVALID && S_AXI_WREADY + && M_AXI_WVALID && !M_AXI_WREADY); + r_wdata <= S_AXI_WDATA; + r_wstrb <= S_AXI_WSTRB; + r_wlast <= S_AXI_WLAST; + // }}} + end else if (o_write_fault || M_AXI_WREADY || !M_AXI_ARESETN) + r_wvalid <= 0; + // }}} + + // m_w* + // {{{ + // Here's the result of our double buffer + // + // m_* references the value within the double buffer in the case that + // something is in the double buffer. Otherwise, it is the values + // directly from the inputs. In the case of a fault, neither is true. + // Write faults override. + // + always @(*) + if (o_write_fault) + begin + // {{{ + m_wvalid = !m_wempty; + m_wlast = m_wlastctr; + m_wstrb = 0; + // }}} + end else if (r_wvalid) + begin + // {{{ + m_wvalid = 1; + m_wstrb = r_wstrb; + m_wlast = r_wlast; + // }}} + end else begin + // {{{ + m_wvalid = S_AXI_WVALID && S_AXI_WREADY; + m_wstrb = S_AXI_WSTRB; + m_wlast = S_AXI_WLAST; + // }}} + end + // }}} + + // m_wdata + // {{{ + // The logic for the DATA output of the double buffer doesn't + // matter so much in the case of o_write_fault + always @(*) + if (r_wvalid) + m_wdata = r_wdata; + else + m_wdata = S_AXI_WDATA; + // }}} + + // M_AXI_W* + // {{{ + // Set the downstream write channel values + // + // As per AXI spec, these values *must* be registered. Note that our + // source here is the m_* double buffer/incoming write data switch. + initial M_AXI_WVALID = 0; + always @(posedge S_AXI_ACLK) + begin + if (!M_AXI_WVALID || M_AXI_WREADY) + begin + M_AXI_WVALID <= m_wvalid; + M_AXI_WDATA <= m_wdata; + M_AXI_WSTRB <= m_wstrb; + if (OPT_EXCLUSIVE && lock_failed) + M_AXI_WSTRB <= 0; + M_AXI_WLAST <= m_wlast; + end + + // Override the WVALID signal (only) on reset, voiding any + // output we might otherwise send. + if (!M_AXI_ARESETN) + M_AXI_WVALID <= 0; + end + // }}} + // }}} + + // Write fault detection + // {{{ + // write_timer + // {{{ + // The write timer + // + // The counter counts up to saturation. It is reset any time + // the write channel is either clear, or a value is accepted + // at the *MASTER* (not slave) side. Why the master side? Simply + // because it makes the proof below easier. (At one time I checked + // both, but then couldn't prove that the faults wouldn't get hit + // if the slave responded in time.) + initial write_timer = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !waddr_valid) + write_timer <= 0; + else if (!o_write_fault && M_AXI_BVALID) + write_timer <= 0; + else if (S_AXI_WREADY) + write_timer <= 0; + else if (!(&write_timer)) + write_timer <= write_timer + 1; + // }}} + + // write_timeout + // {{{ + // Write timeout detector + // + // If the write_timer reaches the OPT_TIMEOUT, then the write_timeout + // will get set true. This will force a fault, taking the write + // channel off line. + initial write_timeout = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_fault) + write_timeout <= 0; + else if (M_AXI_BVALID) + write_timeout <= write_timeout; + else if (S_AXI_WVALID && S_AXI_WREADY) + write_timeout <= write_timeout; + else if (write_timer >= OPT_TIMEOUT) + write_timeout <= 1; + // }}} + + // faulty_write_return + // {{{ + // fault_write_return + // + // This combinational logic is used to catch an invalid return, and + // so take the slave off-line before the return can corrupt the master + // channel. Reasons for taking the write return off line are listed + // below. + always @(*) + begin + faulty_write_return = 0; + if (M_AXI_WVALID && M_AXI_WREADY + && M_AXI_AWVALID && !M_AXI_AWREADY) + // Accepting the write *data* prior to the write + // *address* is a fault + faulty_write_return = 1; + if (M_AXI_BVALID) + begin + if (M_AXI_AWVALID || M_AXI_WVALID) + // Returning a B* acknowledgement while the + // request remains outstanding is also a fault + faulty_write_return = 1; + if (!m_wempty) + // Same as above, but this time the write + // channel is neither complete, nor is *WVALID + // active. Values remain to be written, + // and so a return is a fault. + faulty_write_return = 1; + if (s_wbursts <= (S_AXI_BVALID ? 1:0)) + // Too many acknowledgments + // + // Returning more than one BVALID&BREADY for + // every AWVALID & AWREADY is a fault. + faulty_write_return = 1; + if (M_AXI_BID != wfifo_id) + // An attempt to return the wrong ID + faulty_write_return = 1; + if (M_AXI_BRESP == EXOKAY && (!OPT_EXCLUSIVE + || !lock_enabled)) + // An attempt to return a valid lock, without + // a prior request + faulty_write_return = 1; + end + end + // }}} + + // o_write_fault + // {{{ + // On a write fault, we're going to disconnect the write port from + // the slave, and return errors on each write connect. o_write_fault + // is our signal determining if the write channel is disconnected. + // + // Most of this work is determined within faulty_write_return above. + // Here we do just a bit more: + initial o_write_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_fault) + o_write_fault <= 0; + else if ((!M_AXI_ARESETN&&o_read_fault) || write_timeout) + o_write_fault <= 1; + else if (M_AXI_BVALID && M_AXI_BREADY) + o_write_fault <= (o_write_fault) || faulty_write_return; + else if (M_AXI_WVALID && M_AXI_WREADY + && M_AXI_AWVALID && !M_AXI_AWREADY) + // Accepting the write data prior to the write address + // is a fault + o_write_fault <= 1; + // }}} + // }}} + + // Write return generation + // {{{ + // m_b* + // {{{ + // Since we only ever allow a single write burst at a time, we don't + // need to double buffer the return channel. Hence, we'll set our + // return channel based upon the incoming values alone. Note that + // we're overriding the M_AXI_BID below, in order to make certain that + // the return goes to the right source. + // + always @(*) + if (o_write_fault) + begin + m_bvalid = (s_wbursts > (S_AXI_BVALID ? 1:0)); + m_bid = wfifo_id; + m_bresp = SLAVE_ERROR; + end else begin + m_bvalid = M_AXI_BVALID; + if (faulty_write_return) + m_bvalid = 0; + m_bid = wfifo_id; + m_bresp = M_AXI_BRESP; + end + // }}} + + // S_AXI_B* + // {{{ + // We'll *never* stall the slaves BREADY channel + // + // If the slave returns the response we are expecting, then S_AXI_BVALID + // will be low and it can go directly into the S_AXI_BVALID slot. If + // on the other hand the slave returns M_AXI_BVALID at the wrong time, + // then we'll quietly accept it and send the write interface into + // fault detected mode, setting o_write_fault. + // + // Sadly, this will create a warning in Vivado. If/when you see it, + // see this note and then just ignore it. + assign M_AXI_BREADY = 1; + + // + // Return a write acknowlegement at the end of every write + // burst--regardless of whether or not the slave does so + // + initial S_AXI_BVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_BVALID <= 0; + else if (!S_AXI_BVALID || S_AXI_BREADY) + S_AXI_BVALID <= m_bvalid; + + // + // Set the values associated with the response + // + always @(posedge S_AXI_ACLK) + if (!S_AXI_BVALID || S_AXI_BREADY) + begin + S_AXI_BID <= m_bid; + S_AXI_BRESP <= m_bresp; + end + // }}} + + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read channel processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Read address channel + // {{{ + + // S_AXI_ARREADY + // {{{ + initial S_AXI_ARREADY = (OPT_SELF_RESET) ? 0:1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_ARREADY <= !OPT_SELF_RESET; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + S_AXI_ARREADY <= 0; + // else if (clear_fault) + // S_AXI_ARREADY <= 1; + else if (!S_AXI_ARREADY) + S_AXI_ARREADY <= (S_AXI_RVALID && S_AXI_RLAST && S_AXI_RREADY); + // }}} + + // raddr_valid + // {{{ + generate if (OPT_SELF_RESET) + begin : GEN_READ_RESET + assign raddr_valid = !rfifo_empty; + end else begin : SIMPLE_RADDR_VALID + assign raddr_valid = !S_AXI_ARREADY; + end endgenerate + // }}} + + // r_ar* + // {{{ + // Double buffer the values associated with any read address request + // + initial r_arvalid = 0; + always @(posedge S_AXI_ACLK) + begin + if (S_AXI_ARVALID && S_AXI_ARREADY) + begin + r_arvalid <= 0; // (M_AXI_ARVALID && !M_AXI_ARREADY); + r_arid <= S_AXI_ARID; + r_araddr <= S_AXI_ARADDR; + r_arlen <= S_AXI_ARLEN; + r_arsize <= S_AXI_ARSIZE; + r_arburst <= S_AXI_ARBURST; + r_arlock <= S_AXI_ARLOCK; + r_arcache <= S_AXI_ARCACHE; + r_arprot <= S_AXI_ARPROT; + r_arqos <= S_AXI_ARQOS; + end else if (M_AXI_ARREADY) + r_arvalid <= 0; + + if (!M_AXI_ARESETN) + r_arvalid <= 0; + if (!OPT_EXCLUSIVE || !S_AXI_ARESETN) + r_arlock <= 1'b0; + end + // }}} + + // m_ar* + // {{{ + always @(*) + if (r_arvalid) + begin + m_arvalid = r_arvalid; + m_arid = r_arid; + m_araddr = r_araddr; + m_arlen = r_arlen; + m_arsize = r_arsize; + m_arburst = r_arburst; + m_arlock = r_arlock; + m_arcache = r_arcache; + m_arprot = r_arprot; + m_arqos = r_arqos; + end else begin + m_arvalid = S_AXI_ARVALID && S_AXI_ARREADY; + m_arid = S_AXI_ARID; + m_araddr = S_AXI_ARADDR; + m_arlen = S_AXI_ARLEN; + m_arsize = S_AXI_ARSIZE; + m_arburst = S_AXI_ARBURST; + m_arlock = S_AXI_ARLOCK && OPT_EXCLUSIVE; + m_arcache = S_AXI_ARCACHE; + m_arprot = S_AXI_ARPROT; + m_arqos = S_AXI_ARQOS; + end + // }}} + + // M_AXI_AR* + // {{{ + // Set the downstream values according to the transaction we've just + // received. + // + initial M_AXI_ARVALID = 0; + always @(posedge S_AXI_ACLK) + begin + if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + + if (o_read_fault) + M_AXI_ARVALID <= 0; + else + M_AXI_ARVALID <= m_arvalid; + + M_AXI_ARID <= m_arid; + M_AXI_ARADDR <= m_araddr; + M_AXI_ARLEN <= m_arlen; + M_AXI_ARSIZE <= m_arsize; + M_AXI_ARBURST <= m_arburst; + M_AXI_ARLOCK <= m_arlock && OPT_EXCLUSIVE; + M_AXI_ARCACHE <= m_arcache; + M_AXI_ARPROT <= m_arprot; + M_AXI_ARQOS <= m_arqos; + end + + if (!M_AXI_ARESETN) + M_AXI_ARVALID <= 0; + end + // }}} + + // }}} + + // + // Read fault detection + // {{{ + + // read_timer + // {{{ + // First step: a timer. The timer starts as soon as the S_AXI_ARVALID + // is accepted. Once that happens, rfifo_empty will no longer be true. + // The count is reset any time the slave produces a value for us to + // read. Once the count saturates, it stops counting. + initial read_timer = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_fault) + read_timer <= 0; + else if (rfifo_empty||(S_AXI_RVALID)) + read_timer <= 0; + else if (M_AXI_RVALID) + read_timer <= 0; + else if (!(&read_timer)) + read_timer <= read_timer + 1; + // }}} + + // read_timeout + // {{{ + // Once the counter > OPT_TIMEOUT, we have a timeout condition. + // We'll detect this one clock earlier below. If we ever enter + // a read time out condition, we'll set the read fault. The read + // timeout condition can only be cleared by a reset. + initial read_timeout = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_fault) + read_timeout <= 0; + else if (rfifo_empty||S_AXI_RVALID) + read_timeout <= read_timeout; + else if (M_AXI_RVALID) + read_timeout <= read_timeout; + else if (read_timer == OPT_TIMEOUT) + read_timeout <= 1; + // }}} + + // + // faulty_read_return + // {{{ + // To avoid returning a fault from the slave, it is important to + // determine within a single clock period whether or not the slaves + // return value is at fault or not. That's the purpose of the code + // below. + always @(*) + begin + faulty_read_return = 0; + if (M_AXI_RVALID) + begin + if (M_AXI_ARVALID) + // It is a fault to return data before the + // request has been accepted + faulty_read_return = 1; + if (M_AXI_RID != rfifo_id) + // It is a fault to return data from a + // different ID + faulty_read_return = 1; + if (rfifo_last && (S_AXI_RVALID || !M_AXI_RLAST)) + faulty_read_return = 1; + if (rfifo_penultimate && S_AXI_RVALID && (r_rvalid || !M_AXI_RLAST)) + faulty_read_return = 1; + if (M_AXI_RRESP == EXOKAY && (!OPT_EXCLUSIVE || !rfifo_lock)) + faulty_read_return = 1; + if (OPT_EXCLUSIVE && exread && M_AXI_RRESP == OKAY) + // Can't switch from EXOKAY to OKAY + faulty_read_return = 1; + if ((!OPT_EXCLUSIVE || (!rfifo_first && !exread)) + && M_AXI_RRESP == EXOKAY) + // Can't switch from OKAY to EXOKAY + faulty_read_return = 1; + end + end + // }}} + + // o_read_fault + // {{{ + initial o_read_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || clear_fault) + o_read_fault <= 0; + else if ((!M_AXI_ARESETN && o_write_fault) || read_timeout) + o_read_fault <= 1; + else if (M_AXI_RVALID) + begin + if (faulty_read_return) + o_read_fault <= 1; + if (!raddr_valid) + // It is a fault if we haven't issued an AR* request + // yet, and a value is returned + o_read_fault <= 1; + end + // }}} + + // }}} + + // + // Read return/acknowledgment processing + // {{{ + + + // exread + // {{{ + always @(posedge S_AXI_ACLK) + if (!M_AXI_ARESETN || !OPT_EXCLUSIVE) + exread <= 0; + else if (M_AXI_RVALID && M_AXI_RREADY) + begin + if (!M_AXI_RRESP[1]) + exread <= (M_AXI_RRESP == EXOKAY); + end else if (S_AXI_RVALID && S_AXI_RREADY) + begin + if (S_AXI_RLAST) + exread <= 1'b0; + end + // }}} + + // r_rvalid + // {{{ + // Step one, set/create the read return double buffer. If r_rvalid + // is true, there's a valid value in the double buffer location. + // + initial r_rvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_read_fault) + r_rvalid <= 0; + else if (!r_rvalid) + begin + // {{{ + // Refuse to set the double-buffer valid on any fault. + // That will also keep (below) M_AXI_RREADY high--so that + // following the fault the slave might still (pretend) to + // respond properly + if (faulty_read_return) + r_rvalid <= 0; + else if (o_read_fault) + r_rvalid <= 0; + // If there's nothing in the double buffer, then we might + // place something in there. The double buffer only gets + // filled under two conditions: 1) something is pending to be + // returned, and 2) there's another return that we can't + // stall and so need to accept here. + r_rvalid <= M_AXI_RVALID && M_AXI_RREADY + && (S_AXI_RVALID && !S_AXI_RREADY); + // }}} + end else if (S_AXI_RREADY) + // Once the up-stream read-channel clears, we can always + // clear the double buffer + r_rvalid <= 0; + // }}} + + // r_rresp, r_rdata + // {{{ + // Here's the actual values kept whenever r_rvalid is true. This is + // the double buffer. Notice that r_rid and r_last are generated + // locally, so not recorded here. + // + always @(posedge S_AXI_ACLK) + if (!r_rvalid) + begin + r_rresp <= M_AXI_RRESP; + r_rdata <= M_AXI_RDATA; + end + // }}} + + // M_AXI_RREADY + // {{{ + // Stall the downstream channel any time there's something in the + // double buffer. In spite of the ! here, this is a registered value. + assign M_AXI_RREADY = !r_rvalid; + // }}} + + // rfifo_id, rfifo_lock + // {{{ + // Copy the ID for later comparisons on the return + always @(*) + rfifo_id = r_arid; + always @(*) + rfifo_lock = r_arlock; + // }}} + + // rfifo_[counter|empty|last|penultimate + // {{{ + // Count the number of outstanding read elements. This is the number + // of read returns we still expect--from the upstream perspective. The + // downstream perspective will be off by both what's waiting for + // S_AXI_RREADY and what's in the double buffer + // + initial rfifo_counter = 0; + initial rfifo_empty = 1; + initial rfifo_last = 0; + initial rfifo_penultimate= 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rfifo_counter <= 0; + rfifo_empty <= 1; + rfifo_last <= 0; + rfifo_penultimate<= 0; + end else if (S_AXI_ARVALID && S_AXI_ARREADY) + begin + rfifo_counter <= S_AXI_ARLEN+1; + rfifo_empty <= 0; + rfifo_last <= (S_AXI_ARLEN == 0); + rfifo_penultimate <= (S_AXI_ARLEN == 1); + end else if (!rfifo_empty) + begin + if (S_AXI_RVALID && S_AXI_RREADY) + begin + rfifo_counter <= rfifo_counter - 1; + rfifo_empty <= (rfifo_counter <= 1); + rfifo_last <= (rfifo_counter == 2); + rfifo_penultimate <= (rfifo_counter == 3); + end + end + // }}} + + // lock_active + // {{{ + generate if (OPT_EXCLUSIVE) + begin : CALC_LOCK_ACTIVE + // {{{ + genvar gk; + + for(gk=0; gk<(1<<IW); gk=gk+1) + begin : LOCK_PER_ID + reg r_lock_active; + + initial r_lock_active = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !M_AXI_ARESETN + || faulty_read_return || faulty_write_return) + r_lock_active <= 0; + else if (M_AXI_RVALID && M_AXI_RREADY && rfifo_lock + && !S_AXI_ARREADY + && r_arid == gk[IW-1:0] + &&(!faulty_read_return && !o_read_fault) + && M_AXI_RRESP == EXOKAY) + r_lock_active <= 1; + else if (M_AXI_BVALID && M_AXI_BREADY && wfifo_lock + && r_awid == gk[IW-1:0] + && (S_AXI_ARREADY + || r_arid != gk[IW-1:0] + || !r_arlock) + && M_AXI_BRESP == OKAY) + r_lock_active <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLOCK + && S_AXI_ARID == gk[IW-1:0]) + r_lock_active <= 0; + + assign lock_active[gk] = r_lock_active; + end + // }}} + end else begin : LOCK_NEVER_ACTIVE + // {{{ + assign lock_active = 0; + + // Keep Verilator happy + // Verilator lint_off UNUSED + wire unused_lock; + assign unused_lock = &{ 1'b0, wfifo_lock }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + // }}} + + // rfifo_first + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_EXCLUSIVE) + rfifo_first <= 1'b0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + rfifo_first <= 1'b1; + else if (M_AXI_RVALID && M_AXI_RREADY) + rfifo_first <= 1'b0; + else if (o_read_fault) + rfifo_first <= 1'b0; + // }}} + + // m_rvalid, m_rresp + // {{{ + // Determine values to send back and return + // + // This data set includes all the return values, even though only + // RRESP and RVALID are set in this block. + always @(*) + if (o_read_fault || (!M_AXI_ARESETN && OPT_SELF_RESET)) + begin + m_rvalid = !rfifo_empty; + if (S_AXI_RVALID && rfifo_last) + m_rvalid = 0; + m_rresp = SLAVE_ERROR; + end else if (r_rvalid) + begin + m_rvalid = r_rvalid; + m_rresp = r_rresp; + end else begin + m_rvalid = M_AXI_RVALID && raddr_valid && !faulty_read_return; + m_rresp = M_AXI_RRESP; + end + // }}} + + // m_rid + // {{{ + // We've stored the ID locally, so that our response will never be in + // error + always @(*) + m_rid = rfifo_id; + // }}} + + // m_rlast + // {{{ + // Ideally, we'd want to say m_rlast = rfifo_last. However, we might + // have a value in S_AXI_RVALID already. In that case, the last + // value can only be true if we are one further away from the end. + // (Remember, rfifo_last and rfifo_penultimate are both dependent upon + // the *upstream* number of read values outstanding, not the downstream + // number which we can't trust in all cases) + always @(*) + if (S_AXI_RVALID) + m_rlast = rfifo_penultimate; + else + m_rlast = (rfifo_last); + // }}} + + // m_rdata + // {{{ + // In the case of a fault, rdata is a don't care. Therefore we can + // always set it based upon the values returned from the slave. + always @(*) + if (r_rvalid) + m_rdata = r_rdata; + else + m_rdata = M_AXI_RDATA; + // }}} + + // S_AXI_R* + // {{{ + // Record our return data values + // + // These are the values from either the slave, the double buffer, + // or an error value bypassing either as determined by the m_* values. + // Per spec, all of these values must be registered. First the valid + // signal + initial S_AXI_RVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXI_RVALID <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + S_AXI_RVALID <= m_rvalid; + + // Then the various slave data channels + always @(posedge S_AXI_ACLK) + if (!S_AXI_RVALID || S_AXI_RREADY) + begin + S_AXI_RID <= m_rid; + S_AXI_RRESP <= m_rresp; + S_AXI_RLAST <= m_rlast; + S_AXI_RDATA <= m_rdata; + end + // }}} + + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Self-reset + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_SELF_RESET) + begin : GEN_SELFRESET + // {{{ + // Declarations + // {{{ + reg [4:0] reset_counter; + reg r_clear_fault, w_clear_fault; + wire reset_timeout; + // }}} + + // M_AXI_ARESETN + // {{{ + initial M_AXI_ARESETN = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXI_ARESETN <= 0; + else if (clear_fault) + M_AXI_ARESETN <= 1; + else if (o_read_fault || o_write_fault) + M_AXI_ARESETN <= 0; + // }}} + + // reset_counter + // {{{ + initial reset_counter = 0; + always @(posedge S_AXI_ACLK) + if (M_AXI_ARESETN) + reset_counter <= 0; + else if (!reset_timeout) + reset_counter <= reset_counter+1; + // }}} + + assign reset_timeout = reset_counter[4]; + + // r_clear_fault + // {{{ + initial r_clear_fault <= 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_clear_fault <= 1; + else if (!M_AXI_ARESETN && !reset_timeout) + r_clear_fault <= 0; + else if (!o_read_fault && !o_write_fault) + r_clear_fault <= 0; + else if (raddr_valid || waddr_valid) + r_clear_fault <= 0; + else + r_clear_fault <= 1; + // }}} + + // clear_fault + // {{{ + always @(*) + if (S_AXI_AWVALID || S_AXI_ARVALID) + w_clear_fault = 0; + else if (raddr_valid || waddr_valid) + w_clear_fault = 0; + else + w_clear_fault = r_clear_fault; + + assign clear_fault = w_clear_fault; + // }}} + + // }}} + end else begin : NO_SELFRESET + // {{{ + always @(*) + M_AXI_ARESETN = S_AXI_ARESETN; + assign clear_fault = 0; + // }}} + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // {{{ + // Below are just some of the formal properties used to verify + // this core. The full property set is maintained elsewhere. + // + parameter [0:0] F_OPT_FAULTLESS = 1; + + // + // ... + // }}} + + faxi_slave #( + // {{{ + .C_AXI_ID_WIDTH(C_S_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_S_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_S_AXI_ADDR_WIDTH) + // ... + // }}} + ) f_slave( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + // Address write channel + // {{{ + .i_axi_awid(S_AXI_AWID), + .i_axi_awaddr(S_AXI_AWADDR), + .i_axi_awlen(S_AXI_AWLEN), + .i_axi_awsize(S_AXI_AWSIZE), + .i_axi_awburst(S_AXI_AWBURST), + .i_axi_awlock(S_AXI_AWLOCK), + .i_axi_awcache(S_AXI_AWCACHE), + .i_axi_awprot(S_AXI_AWPROT), + .i_axi_awqos(S_AXI_AWQOS), + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + // }}} + // Write Data Channel + // {{{ + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + .i_axi_wlast(S_AXI_WLAST), + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + // }}} + // Write response channel + // {{{ + .i_axi_bid(S_AXI_BID), + .i_axi_bresp(S_AXI_BRESP), + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + // }}} + // Read address channel + // {{{ + .i_axi_arid(S_AXI_ARID), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arlen(S_AXI_ARLEN), + .i_axi_arsize(S_AXI_ARSIZE), + .i_axi_arburst(S_AXI_ARBURST), + .i_axi_arlock(S_AXI_ARLOCK), + .i_axi_arcache(S_AXI_ARCACHE), + .i_axi_arprot(S_AXI_ARPROT), + .i_axi_arqos(S_AXI_ARQOS), + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + // }}} + // Read data return channel + // {{{ + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rid(S_AXI_RID), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + .i_axi_rlast(S_AXI_RLAST), + // }}} + // Formal induction values + // {{{ + .f_axi_awr_nbursts(f_axi_awr_nbursts), + .f_axi_wr_pending(f_axi_wr_pending), + .f_axi_rd_nbursts(f_axi_rd_nbursts), + .f_axi_rd_outstanding(f_axi_rd_outstanding) + // + // ... + // }}} + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Write induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // 1. We will only ever handle a single burst--never any more + always @(*) + assert(f_axi_awr_nbursts <= 1); + + always @(*) + if (f_axi_awr_nbursts == 1) + begin + assert(!S_AXI_AWREADY); + if (S_AXI_BVALID) + assert(f_axi_wr_pending == 0); + if (!o_write_fault && M_AXI_AWVALID) + assert(f_axi_wr_pending == M_AXI_AWLEN + 1 + - (M_AXI_WVALID ? 1:0) + - (r_wvalid ? 1 : 0)); + if (!o_write_fault && f_axi_wr_pending > 0) + assert((!M_AXI_WVALID || !M_AXI_WLAST) + &&(!r_wvalid || !r_wlast)); + if (!o_write_fault && M_AXI_WVALID && M_AXI_WLAST) + assert(!r_wvalid || !r_wlast); + end else begin + assert(s_wbursts == 0); + assert(!S_AXI_WREADY); + if (OPT_SELF_RESET) + begin + assert(1 || S_AXI_AWREADY || !M_AXI_ARESETN || !S_AXI_ARESETN); + end else + assert(S_AXI_AWREADY); + end + + // + // ... + // + + // + // AWREADY will always be low mid-burst + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assert(S_AXI_AWREADY == !OPT_SELF_RESET); + end else if (f_axi_wr_pending > 0) + begin + assert(!S_AXI_AWREADY); + end else if (s_wbursts) + begin + assert(!S_AXI_AWREADY); + end else if (!OPT_SELF_RESET) + begin + assert(S_AXI_AWREADY); + end else if (!o_write_fault && !o_read_fault) + assert(S_AXI_AWREADY || OPT_SELF_RESET); + + always @(*) + if (f_axi_wr_pending > 0) + begin + assert(s_wbursts == 0); + end else if (f_axi_awr_nbursts > 0) + begin + assert((s_wbursts ? 1:0) == f_axi_awr_nbursts); + end else + assert(s_wbursts == 0); + + always @(*) + if (!o_write_fault && S_AXI_AWREADY) + assert(!M_AXI_AWVALID); + + always @(*) + if (M_AXI_ARESETN && (f_axi_awr_nbursts == 0)) + begin + assert(o_write_fault || !M_AXI_AWVALID); + assert(!S_AXI_BVALID); + assert(s_wbursts == 0); + end else if (M_AXI_ARESETN && s_wbursts == 0) + assert(f_axi_wr_pending > 0); + + always @(*) + if (S_AXI_WREADY) + begin + assert(waddr_valid); + end else if (f_axi_wr_pending > 0) + begin + if (!o_write_fault) + assert(M_AXI_WVALID && r_wvalid); + end + + always @(*) + if (!OPT_SELF_RESET) + begin + assert(waddr_valid == !S_AXI_AWREADY); + end else begin + // ... + assert(waddr_valid == (f_axi_awr_nbursts > 0)); + end + + always @(*) + if (S_AXI_ARESETN && !o_write_fault && f_axi_wr_pending > 0 && !S_AXI_WREADY) + assert(M_AXI_WVALID); + + always @(*) + if (S_AXI_ARESETN && !o_write_fault && m_wempty) + begin + assert(M_AXI_AWVALID || !M_AXI_WVALID); + assert(M_AXI_AWVALID || f_axi_wr_pending == 0); + end + + always @(*) + if (S_AXI_ARESETN && M_AXI_AWVALID) + begin + if (!o_write_fault && !M_AXI_AWVALID) + begin + assert(f_axi_wr_pending + (M_AXI_WVALID ? 1:0) + (r_wvalid ? 1:0) == m_wpending); + end else if (!o_write_fault) + begin + assert(f_axi_wr_pending == M_AXI_AWLEN + 1 - (M_AXI_WVALID ? 1:0) - (r_wvalid ? 1:0)); + assert(m_wpending == 0); + end + end + + always @(*) + assert(m_wpending <= 9'h100); + + always @(*) + if (M_AXI_ARESETN && (!o_write_fault)&&(f_axi_awr_nbursts == 0)) + assert(!M_AXI_AWVALID); + + always @(*) + if (S_AXI_ARESETN && !o_write_fault) + begin + if (f_axi_awr_nbursts == 0) + begin + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + end + + if (!M_AXI_AWVALID) + assert(f_axi_wr_pending + +(M_AXI_WVALID ? 1:0) + + (r_wvalid ? 1:0) + == m_wpending); + if (f_axi_awr_nbursts == (S_AXI_BVALID ? 1:0)) + begin + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + end + + if (S_AXI_BVALID) + assert(f_axi_awr_nbursts == 1); + + if (M_AXI_AWVALID) + assert(m_wpending == 0); + + if (S_AXI_AWREADY) + assert(!M_AXI_AWVALID); + end + + always @(*) + assert(!r_awvalid); + + always @(*) + assert(m_wempty == (m_wpending == 0)); + always @(*) + assert(m_wlastctr == (m_wpending == 1)); + + // + // Verify the contents of the skid buffers + // + + // + // ... + // + + always @(*) + if (write_timer > OPT_TIMEOUT) + assert(o_write_fault || write_timeout); + + always @(*) + if (!OPT_SELF_RESET) + begin + assert(waddr_valid == !S_AXI_AWREADY); + end else if (waddr_valid) + assert(!S_AXI_AWREADY); + + always @(*) + if (!o_write_fault && M_AXI_ARESETN) + assert(waddr_valid == !S_AXI_AWREADY); + + always @(*) + if (f_axi_wr_pending == 0) + assert(!S_AXI_WREADY); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + assert(f_axi_rd_nbursts <= 1); + always @(*) + assert(raddr_valid == (f_axi_rd_nbursts>0)); + always @(*) + if (f_axi_rdid_nbursts > 0) + begin + assert(rfifo_id == f_axi_rd_checkid); + end else if (f_axi_rd_nbursts > 0) + assert(rfifo_id != f_axi_rd_checkid); + + always @(*) + if (f_axi_rd_nbursts > 0) + assert(raddr_valid); + + always @(*) + if (raddr_valid) + assert(!S_AXI_ARREADY); + + always @(*) + if (!OPT_SELF_RESET) + begin + assert(raddr_valid == !S_AXI_ARREADY); + end else begin + // ... + assert(raddr_valid == (f_axi_rd_nbursts > 0)); + end + + // + // ... + // + + always @(*) + if (f_axi_rd_outstanding == 0) + assert(!raddr_valid || OPT_SELF_RESET); + + always @(*) + if (!o_read_fault && S_AXI_ARREADY) + assert(!M_AXI_ARVALID); + + // Our "FIFO" counter + always @(*) + assert(rfifo_counter == f_axi_rd_outstanding); + + always @(*) + begin + assert(rfifo_empty == (rfifo_counter == 0)); + assert(rfifo_last == (rfifo_counter == 1)); + assert(rfifo_penultimate == (rfifo_counter == 2)); + end + + // + // ... + // + + always @(*) + if (r_arvalid) + assert(skid_arvalid); + + always @(*) + if (read_timer > OPT_TIMEOUT) + assert(read_timeout); + + + always @(posedge S_AXI_ACLK) + if (OPT_SELF_RESET && (!f_past_valid || !$past(M_AXI_ARESETN))) + begin + assume(!M_AXI_BVALID); + assume(!M_AXI_RVALID); + end + + always @(*) + if (!o_read_fault && M_AXI_ARESETN) + assert(raddr_valid == !S_AXI_ARREADY); + + always @(*) + if (f_axi_rd_nbursts > 0) + assert(raddr_valid); + + always @(*) + if (S_AXI_ARESETN && OPT_SELF_RESET) + begin + if (!M_AXI_ARESETN) + assert(o_read_fault || o_write_fault /* ... */ ); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Exclusive access property checking + // {{{ + // + //////////////////////////////////////////////////////////////////////// + // + // ... + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + //////////////////////////////////////////////////////////////////////// + // + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Good interface check + // + //////////////////////////////////////////////////////////////////////// + // + // + generate if (F_OPT_FAULTLESS) + begin : ASSUME_FAULTLESS + // {{{ + // + // ... + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_S_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_S_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_S_AXI_ADDR_WIDTH) + // ... + // }}} + ) f_master( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(M_AXI_ARESETN), + // + // Address write channel + // {{{ + .i_axi_awid(M_AXI_AWID), + .i_axi_awaddr(M_AXI_AWADDR), + .i_axi_awlen(M_AXI_AWLEN), + .i_axi_awsize(M_AXI_AWSIZE), + .i_axi_awburst(M_AXI_AWBURST), + .i_axi_awlock(M_AXI_AWLOCK), + .i_axi_awcache(M_AXI_AWCACHE), + .i_axi_awprot(M_AXI_AWPROT), + .i_axi_awqos(M_AXI_AWQOS), + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + // }}} + // Write Data Channel + // {{{ + .i_axi_wvalid(M_AXI_WVALID), + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata(M_AXI_WDATA), + .i_axi_wstrb(M_AXI_WSTRB), + .i_axi_wlast(M_AXI_WLAST), + // }}} + // Write response channel + // {{{ + .i_axi_bvalid(M_AXI_BVALID), + .i_axi_bready(M_AXI_BREADY), + .i_axi_bid(M_AXI_BID), + .i_axi_bresp(M_AXI_BRESP), + // }}} + // Read address channel + // {{{ + .i_axi_arid(M_AXI_ARID), + .i_axi_araddr(M_AXI_ARADDR), + .i_axi_arlen(M_AXI_ARLEN), + .i_axi_arsize(M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock(M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot(M_AXI_ARPROT), + .i_axi_arqos(M_AXI_ARQOS), + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + // }}} + // Read data return channel + // {{{ + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rid(M_AXI_RID), + .i_axi_rdata(M_AXI_RDATA), + .i_axi_rresp(M_AXI_RRESP), + .i_axi_rlast(M_AXI_RLAST), + // }}} + // Formal outputs + // {{{ + .f_axi_awr_nbursts(fm_axi_awr_nbursts), + .f_axi_wr_pending(fm_axi_wr_pending), + .f_axi_rd_nbursts(fm_axi_rd_nbursts), + .f_axi_rd_outstanding(fm_axi_rd_outstanding) + // + // ... + // }}} + // }}} + ); + + //////////////////////////////////////////////////////////////// + // + // Contract: If the downstream has no faults, then we + // shouldn't detect any here. + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + always @(*) + if (OPT_SELF_RESET) + begin + assert(!o_write_fault || !M_AXI_ARESETN); + end else + assert(!o_write_fault); + + always @(*) + if (OPT_SELF_RESET) + begin + assert(!o_read_fault || !M_AXI_ARESETN); + end else + assert(!o_read_fault); + + always @(*) + if (OPT_SELF_RESET) + begin + assert(!read_timeout || !M_AXI_ARESETN); + end else + assert(!read_timeout); + + always @(*) + if (OPT_SELF_RESET) + begin + assert(!write_timeout || !M_AXI_ARESETN); + end else + assert(!write_timeout); + + always @(*) + if (S_AXI_AWREADY) + assert(!M_AXI_AWVALID); + + // + // ... + // + + always @(*) + if (M_AXI_ARESETN && f_axi_rd_nbursts > 0) + assert(f_axi_rd_nbursts == fm_axi_rd_nbursts + + (M_AXI_ARVALID ? 1 : 0) + + (r_rvalid&&m_rlast ? 1 : 0) + + (S_AXI_RVALID&&S_AXI_RLAST ? 1 : 0)); + + always @(*) + if (M_AXI_ARESETN && f_axi_rd_outstanding > 0) + assert(f_axi_rd_outstanding == fm_axi_rd_outstanding + + (M_AXI_ARVALID ? M_AXI_ARLEN+1 : 0) + + (r_rvalid ? 1 : 0) + + (S_AXI_RVALID ? 1 : 0)); + + // + // ... + // + always @(*) + if (M_AXI_RVALID) + assert(!M_AXI_ARVALID); + + always @(*) + if (S_AXI_ARESETN) + assert(m_wpending == fm_axi_wr_pending); + + always @(*) + if (S_AXI_ARESETN && fm_axi_awr_nbursts > 0) + begin + assert(fm_axi_awr_nbursts== f_axi_awr_nbursts); + assert(fm_axi_awr_nbursts == 1); + // + // ... + // + end + // }}} + //////////////////////////////////////////////////////////////// + // + // Exclusive address checking + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // + // + // ... + // + + //////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////// + // + // "Careless" assumptions + // {{{ + //////////////////////////////////////////////////////////////// + // + // + end endgenerate + + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Never path check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + (* anyconst *) reg [C_S_AXI_ADDR_WIDTH-1:0] fc_never_write_addr, + fc_never_read_addr; + (* anyconst *) reg [C_S_AXI_DATA_WIDTH + C_S_AXI_DATA_WIDTH/8-1:0] + fc_never_write_data; + (* anyconst *) reg [C_S_AXI_DATA_WIDTH-1:0] fc_never_read_data; + + // Write address + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && S_AXI_AWVALID) + assume(S_AXI_AWADDR != fc_never_write_addr); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && !o_write_fault && M_AXI_ARESETN && M_AXI_AWVALID) + assert(M_AXI_AWADDR != fc_never_write_addr); + // }}} + + // Write data + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && S_AXI_WVALID) + assume({ S_AXI_WDATA, S_AXI_WSTRB } != fc_never_write_data); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && !o_write_fault && M_AXI_ARESETN && M_AXI_WVALID) + begin + if (lock_failed) + assert(M_AXI_WSTRB == 0); + else + assert({ M_AXI_WDATA, M_AXI_WSTRB } != fc_never_write_data); + end + // }}} + + // Read address + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && S_AXI_ARVALID) + assume(S_AXI_ARADDR != fc_never_read_addr); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && r_arvalid) + assume(r_araddr != fc_never_read_addr); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && !o_read_fault && M_AXI_ARESETN && M_AXI_ARVALID) + assert(M_AXI_ARADDR != fc_never_read_addr); + // }}} + + // Read data + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && M_AXI_ARESETN && M_AXI_RVALID) + assume(M_AXI_RDATA != fc_never_read_data); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && S_AXI_RVALID && S_AXI_RRESP != SLAVE_ERROR) + assert(S_AXI_RDATA != fc_never_read_data); + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // We'll use these to determine the best performance this core + // can achieve + // + reg [4:0] f_dbl_rd_count, f_dbl_wr_count; + reg [4:0] f_rd_count, f_wr_count; + + // f_wr_count + // {{{ + initial f_wr_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_write_fault) + f_wr_count <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY) + begin + if (!(&f_wr_count)) + f_wr_count <= f_wr_count + 1; + end + // }}} + + // f_dbl_wr_count + // {{{ + initial f_dbl_wr_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_write_fault||(S_AXI_AWVALID && S_AXI_AWLEN < 3)) + f_dbl_wr_count <= 0; + else if (S_AXI_BVALID && S_AXI_BREADY) + begin + if (!(&f_dbl_wr_count)) + f_dbl_wr_count <= f_dbl_wr_count + 1; + end + // }}} + + // f_rd_count + // {{{ + initial f_rd_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_read_fault) + f_rd_count <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + begin + if (!(&f_rd_count)) + f_rd_count <= f_rd_count + 1; + end + // }}} + + // f_dbl_rd_count + // {{{ + initial f_dbl_rd_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || o_read_fault||(S_AXI_ARVALID && S_AXI_ARLEN < 3)) + f_dbl_rd_count <= 0; + else if (S_AXI_RVALID && S_AXI_RREADY && S_AXI_RLAST) + begin + if (!(&f_dbl_rd_count)) + f_dbl_rd_count <= f_dbl_rd_count + 1; + end + // }}} + + // Can we complete a single write burst without fault? + // {{{ + always @(*) + if (S_AXI_ARESETN) + cover((f_wr_count > 1) && (!o_write_fault)); + + always @(*) + if (S_AXI_ARESETN) + cover((f_dbl_wr_count > 1) && (!o_write_fault)); + // }}} + + // Can we complete four write bursts without fault? (and return to idle) + // {{{ + always @(*) + if (S_AXI_ARESETN) + cover((f_wr_count > 3) && (!o_write_fault) + &&(!S_AXI_AWVALID && !S_AXI_WVALID + && !S_AXI_BVALID) + && (f_axi_awr_nbursts == 0) + && (f_axi_wr_pending == 0)); + + always @(*) + if (S_AXI_ARESETN) + cover((f_dbl_wr_count > 3) && (!o_write_fault) + &&(!S_AXI_AWVALID && !S_AXI_WVALID + && !S_AXI_BVALID) + && (f_axi_awr_nbursts == 0) + && (f_axi_wr_pending == 0)); + // }}} + + // Can we complete a single read burst without fault? + // {{{ + always @(*) + if (S_AXI_ARESETN) + cover((f_rd_count > 1) && (!o_read_fault)); + + always @(*) + if (S_AXI_ARESETN) + cover((f_dbl_rd_count > 1) && (!o_read_fault)); + // }}} + + // Can we complete four read bursts without fault? + // {{{ + always @(*) + if (S_AXI_ARESETN) + cover((f_rd_count > 3) && (f_axi_rd_nbursts == 0) + && !S_AXI_ARVALID && !S_AXI_RVALID); + + always @(*) + if (S_AXI_ARESETN) + cover((f_dbl_rd_count > 3) && (f_axi_rd_nbursts == 0) + && !S_AXI_ARVALID && !S_AXI_RVALID); + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axisbroadcast.v b/rtl/wb2axip/axisbroadcast.v new file mode 100644 index 0000000..719108f --- /dev/null +++ b/rtl/wb2axip/axisbroadcast.v @@ -0,0 +1,192 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisbroadcast +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: AXI-Stream broadcaster: one slave input port gets broadcast to +// multiple AXI-Stream master ports. +// +// This design does not explicitly implement TLAST, TUSER, TID, or any +// other T* structure. These can be implemented by simply incorporating +// them into TDATA. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2021-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axisbroadcast #( + parameter C_AXIS_DATA_WIDTH = 16, + parameter NM = 4, // Number of (outgoing) master ports + parameter LGFIFO = 4 // Size of outgoing FIFOs + ) ( + input wire S_AXI_ACLK, S_AXI_ARESETN, + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + output wire [NM-1:0] M_AXIS_TVALID, + input wire [NM-1:0] M_AXIS_TREADY, + output wire [NM*C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA + ); + + // Local declarations + // {{{ + localparam DW = C_AXIS_DATA_WIDTH; + genvar gk; + wire [NM-1:0] fifo_full; + wire skd_valid, axis_ready; + wire [DW-1:0] skd_data; + wire [NM*(LGFIFO+1)-1:0] ign_fifo_fill; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming skid buffer + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // This makes it so that we can control VALID && DATA (and so + // backpressure) with a combinatorial value, something that would + // otherwise be against protocol. + // + skidbuffer #( + .DW(DW), + .OPT_OUTREG(1'b0) + ) tskd ( + .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIS_TVALID), .o_ready(S_AXIS_TREADY), + .i_data(S_AXIS_TDATA), + .o_valid(skd_valid), .i_ready(axis_ready), + .o_data(skd_data) + ); + + assign axis_ready = skd_valid && fifo_full == 0; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outgoing FIFOs + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate for(gk=0; gk<NM; gk=gk+1) + begin : OUTGOING_FIFOS + wire fifo_empty; + sfifo #( + .BW(DW), + .LGFLEN(LGFIFO) + ) fifo ( + .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_wr(axis_ready), .i_data(skd_data), + .o_full(fifo_full[gk]), + .o_fill(ign_fifo_fill[gk*(LGFIFO+1)+:LGFIFO+1]), + .i_rd(M_AXIS_TVALID[gk] && M_AXIS_TREADY[gk]), + .o_data(M_AXIS_TDATA[gk*DW +: DW]), + .o_empty(fifo_empty) + ); + + assign M_AXIS_TVALID[gk] = !fifo_empty; + + end endgenerate + // }}} + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, ign_fifo_fill }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = 31; + reg [F_LGDEPTH-1:0] icount; + (* anyconst *) reg [$clog2(NM)-1:0] fc_channel; + reg [F_LGDEPTH-1:0] ocount; + reg f_past_valid; + reg [LGFIFO:0] fifo_fill; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + icount <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + icount <= icount + 1; + + assign fifo_fill = ign_fifo_fill[fc_channel * (LGFIFO+1) +: LGFIFO+1]; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + ocount <= 0; + else if (M_AXIS_TVALID[fc_channel] && M_AXIS_TREADY[fc_channel]) + ocount <= ocount + 1; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !$past(S_AXI_ARESETN)) + assume(!S_AXIS_TVALID); + else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY)) + begin + assume(S_AXIS_TVALID); + assume($stable(S_AXIS_TDATA)); + end + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN) + begin + if (!$past(S_AXI_ARESETN)) + assert(!M_AXIS_TVALID[fc_channel]); + else if ($past(M_AXIS_TVALID[fc_channel] && !M_AXIS_TREADY[fc_channel])) + begin + assert(M_AXIS_TVALID[fc_channel]); + assert($stable(M_AXIS_TDATA[fc_channel * DW +: DW])); + end + end + + always @(*) + if (S_AXI_ARESETN) + assert(icount == ocount + (S_AXIS_TREADY ? 0:1) + fifo_fill); + always @(*) + assume(!icount[F_LGDEPTH-1]); +`endif // FORMAL +// }}} +endmodule diff --git a/rtl/wb2axip/axisgdma.v b/rtl/wb2axip/axisgdma.v new file mode 100644 index 0000000..dac2cbc --- /dev/null +++ b/rtl/wb2axip/axisgdma.v @@ -0,0 +1,1120 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisgdma.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Scripts an AXI DMA via in-memory tables: reads from the tables, +// commands the DMA. +// +// Registers: +// +// 0. Control +// 8b KEY +// 3'b PROT +// 4'b QOS +// 1b Abort: Either aborting or aborted +// 1b Err: Ended on an error +// 1b Busy +// 1b Interrupt Enable +// 1b Interrupt Set +// 1b Start +// 1. Reserved +// 2-3. First table entry address +// (Current) table entry address on read, if in progress +// (Optional) +// 4-5. Current read address +// 6-7. Current write address +// 1. Remaining amount to be written (this entry) +// +// Table entries (must be 32-bit aligned): +// If (address_width > 30) +// 64b: { 2'bflags, 62'b SOURCE ADDRESS (bytes) } +// 00: Continue after this to next +// 01: Skip this address +// 10: Jump to new address +// 11: Last item in chain +// 64b: { int_en, 1'b0, DESTINATION ADDRESS (bytes) } +// 32b LENGTH (in bytes) +// else +// 32b: { 2'bflags, 30'b SOURCE ADDRESS (bytes) } +// 32b: { int_en, 1'b0, 30'b DESTINATION ADDRESS (bytes) } +// 32b LENGTH (in bytes) +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// `define AXI3 +// }}} +module axisgdma #( + // {{{ + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 64, + // + localparam C_AXIL_ADDR_WIDTH = 4, + localparam C_AXIL_DATA_WIDTH = 32, + // + // OPT_UNALIGNED turns on support for unaligned addresses, + // whether source, destination, or length parameters. + parameter [0:0] OPT_UNALIGNED = 1'b1, + // + // OPT_WRAPMEM controls what happens if the transfer runs off + // of the end of memory. If set, the transfer will continue + // again from the beginning of memory. If clear, the transfer + // will be aborted with an error if either read or write + // address ever get this far. + parameter [0:0] OPT_WRAPMEM = 1'b1, + // + // LGMAXBURST controls the size of the maximum burst produced + // by this core. Specifically, its the log (based 2) of that + // maximum size. Hence, for AXI4, this size must be 8 + // (i.e. 2^8 or 256 beats) or less. For AXI3, the size must + // be 4 or less. Tests have verified performance for + // LGMAXBURST as low as 2. While I expect it to fail at + // LGMAXBURST=0, I haven't verified at what value this burst + // parameter is too small. +`ifdef AXI3 + parameter LGMAXBURST=4, // 16 beats max +`else + parameter LGMAXBURST=8, // 256 beats +`endif + // + // LGFIFO: This is the (log-based-2) size of the internal FIFO. + // Hence if LGFIFO=8, the internal FIFO will have 256 elements + // (words) in it. High throughput transfers are accomplished + // by first storing data into a FIFO, then once a full burst + // size is available bursting that data over the bus. In + // order to be able to keep receiving data while bursting it + // out, the FIFO size must be at least twice the size of the + // maximum burst size. Larger sizes are possible as well. + parameter LGFIFO = LGMAXBURST+1, // 512 element FIFO + // + // LGLEN: specifies the number of bits in the transfer length + // register. If a transfer cannot be specified in LGLEN bits, + // it won't happen. LGLEN must be less than or equal to the + // address width. + parameter LGLEN = C_AXI_ADDR_WIDTH, + // + // AXI uses ID's to transfer information. This core rather + // ignores them. Instead, it uses a constant ID for all + // transfers. The following two parameters control that ID. + parameter [C_AXI_ID_WIDTH-1:0] DMA_READ_ID = 0, + parameter [C_AXI_ID_WIDTH-1:0] DMA_WRITE_ID = 0, + parameter [C_AXI_ID_WIDTH-1:0] PF_READ_ID = DMA_READ_ID+1, + // + // The "ABORT_KEY" is a byte that, if written to the control + // word while the core is running, will cause the data transfer + // to be aborted. + parameter [7:0] ABORT_KEY = 8'h6d, + // + // OPT_LOWPOWER + parameter [0:0] OPT_LOWPOWER = 1'b0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The AXI4-lite control interface + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output reg S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output reg S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output reg [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // + // + // The AXI Master (DMA) interface + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, +`ifdef AXI3 + output wire [3:0] M_AXI_AWLEN, +`else + output wire [7:0] M_AXI_AWLEN, +`endif + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, +`ifdef AXI3 + output wire [1:0] M_AXI_AWLOCK, +`else + output wire M_AXI_AWLOCK, +`endif + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, +`ifdef AXI3 + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_WID, +`endif + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + output reg M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, +`ifdef AXI3 + output wire [3:0] M_AXI_ARLEN, +`else + output wire [7:0] M_AXI_ARLEN, +`endif + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, +`ifdef AXI3 + output wire [1:0] M_AXI_ARLOCK, +`else + output wire M_AXI_ARLOCK, +`endif + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP, + // + output reg o_int + // }}} + ); + + // Local parameter definitions + // {{{ + // The number of beats in this maximum burst size is + // automatically determined from LGMAXBURST, and so its + // forced to be a power of two this way. + // localparam MAXBURST=(1<<LGMAXBURST); + // + localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3; + localparam AXILLSB= $clog2(C_AXIL_DATA_WIDTH)-3; + // localparam LGLENW= LGLEN-ADDRLSB; + + localparam [1:0] CTRL_ADDR = 2'b00, + // UNUSED_ADDR = 2'b01, + TBLLO_ADDR = 2'b10, + TBLHI_ADDR = 2'b11; + localparam CTRL_START_BIT = 0, + CTRL_BUSY_BIT = 0, + CTRL_INT_BIT = 1, + CTRL_INTEN_BIT = 2, + CTRL_ABORT_BIT = 3, + CTRL_ERR_BIT = 4; + // CTRL_INTERIM_BIT= 5; + localparam [1:0] AXI_INCR = 2'b01, AXI_OKAY = 2'b00; + +`ifdef AXI3 + localparam LENWIDTH = 4; +`else + localparam LENWIDTH = 8; +`endif + + // DMA device internal addresses + // {{{ + localparam [4:0] DMA_CONTROL= 5'b00000; + // }}} + + // localparam [C_AXI_ADDR_WIDTH-1:0] TBL_SIZE + // = (C_AXI_ADDR_WIDTH < 30) ? (4*5) : (4*7); + + // }}} + + // Register/net declarations + // {{{ + reg axil_write_ready, axil_read_ready; + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_tbl, new_widetbl; + reg [C_AXI_ADDR_WIDTH-1:0] tbl_addr, r_tbl_addr; + reg r_int_enable, r_int, r_err, r_abort; + wire w_int, fsm_err; + + reg [3:0] r_qos; + reg [2:0] r_prot; + reg r_start; + wire r_done, r_busy; + + wire awskd_valid; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + + wire arskd_valid; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + + // Prefetch interface registers + // {{{ + wire new_pc, pf_ready, pf_clear_cache; + wire [C_AXI_ADDR_WIDTH-1:0] ipc; + wire [31:0] pf_insn; + wire pf_valid, pf_illegal; + wire pf_axi_arvalid; + reg pf_axi_arready; + wire [C_AXI_ADDR_WIDTH-1:0] pf_axi_araddr, pf_pc; + wire pf_axi_rready_ignored; + wire [C_AXI_ID_WIDTH-1:0] pf_axi_arid; + wire [LENWIDTH-1:0] pf_axi_arlen; + wire [2:0] pf_axi_arsize; + wire [1:0] pf_axi_arburst; + wire [3:0] pf_axi_arcache; + wire [2:0] pf_axi_arprot; + wire [3:0] pf_axi_arqos; + // }}} + + // DMA control registers/AXI-lite interface + // {{{ + wire dmac_awready_ignored; + reg [4:0] dmac_waddr; + // + reg dmac_wvalid; + wire dmac_wready; + reg [31:0] dmac_wdata; + reg [3:0] dmac_wstrb; + // + wire dmac_bvalid; + wire [1:0] dmac_bresp; + // + wire dmac_arready; + wire dmac_rvalid; + wire [31:0] dmac_rdata; + wire [1:0] dmac_rresp; + // }}} + + // DMA AXI nets + // {{{ + wire sdma_arvalid; + wire [C_AXI_ID_WIDTH-1:0] sdma_arid; + wire [C_AXI_ADDR_WIDTH-1:0] sdma_araddr; + wire [LENWIDTH-1:0] sdma_arlen; + wire [2:0] sdma_arsize; + wire [1:0] sdma_arburst; + wire [0:0] sdma_arlock; + wire [3:0] sdma_arcache; + wire [2:0] sdma_arprot; + wire [3:0] sdma_arqos; + reg sdma_arready; + wire sdma_rready_ignored; + wire dma_complete; + + wire unused_dma_lock; + // }}} + + // Combined AXI nets + // {{{ + reg m_axi_arvalid; + reg [C_AXI_ID_WIDTH-1:0] m_axi_arid; + reg [C_AXI_ADDR_WIDTH-1:0] m_axi_araddr; + reg [LENWIDTH-1:0] m_axi_arlen; + reg [2:0] m_axi_arsize; + reg [1:0] m_axi_arburst; + reg [3:0] m_axi_arcache; + reg [2:0] m_axi_arprot; + reg [3:0] m_axi_arqos; + // }}} + + reg pf_wins_arbitration; + wire m_axi_arready; + + // }}} + + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // AXI-Lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // + + + //////////////////////////////////////////////////////////////////////// + // + // Write control logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // axil AW skid buffer + // {{{ + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB) + // }}} + ) axilawskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr) + // }}} + ); + // }}} + + // axil W skid buffer + // {{{ + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8) + // }}} + ) axilwskid ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WSTRB, S_AXIL_WDATA }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_strb, wskd_data }) + // }}} + ); + // }}} + + // axil_write_ready + // {{{ + always @(*) + begin + axil_write_ready = !S_AXIL_BVALID || S_AXIL_BREADY; + if (!awskd_valid || !wskd_valid) + axil_write_ready = 0; + end + // }}} + + // S_AXIL_BVALID + // {{{ + initial S_AXIL_BVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXIL_BVALID <= 1'b0; + else if (!S_AXIL_BVALID || S_AXIL_BREADY) + S_AXIL_BVALID <= axil_write_ready; + // }}} + + // S_AXIL_BRESP + // {{{ + assign S_AXIL_BRESP = AXI_OKAY; + // }}} + + // r_start + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_start <= 1'b0; + else begin + r_start <= !r_busy && axil_write_ready && wskd_strb[0] + && wskd_data[CTRL_START_BIT] + && (awskd_addr == CTRL_ADDR); + if (r_err && !wskd_data[CTRL_ERR_BIT]) + r_start <= 0; + if (r_abort && !wskd_data[CTRL_ABORT_BIT]) + r_start <= 0; + end + // }}} + + // r_err + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_err <= 1'b0; + else if (!r_busy) + begin + if (axil_write_ready) + r_err <= (r_err) && (!wskd_strb[0] + || !wskd_data[CTRL_ERR_BIT]); + end else begin + r_err <= r_err || fsm_err; + end + // }}} + + // o_int + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !r_int_enable || !r_busy) + o_int <= 0; + else if (w_int) + o_int <= 1'b1; + // }}} + + // r_int + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_int <= 0; + else if (!r_busy) + begin + if (axil_write_ready && awskd_addr == CTRL_ADDR + && wskd_strb[0]) + begin + if (wskd_data[CTRL_START_BIT]) + r_int <= 0; + else if (wskd_data[CTRL_INT_BIT]) + r_int <= 0; + end + end else if (w_int) + r_int <= 1'b1; + // }}} + + // r_abort + // {{{ + initial r_abort = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_abort <= 1'b0; + else if (!r_busy) + begin + if (axil_write_ready && awskd_addr == CTRL_ADDR && wskd_strb[0]) + begin + if(wskd_data[CTRL_START_BIT] + ||wskd_data[CTRL_ABORT_BIT] + ||wskd_data[CTRL_ERR_BIT]) + r_abort <= 0; + end + end else if (!r_abort) + r_abort <= (axil_write_ready && awskd_addr == CTRL_ADDR) + &&(wskd_strb[3] && wskd_data[31:24] == ABORT_KEY); + // }}} + + // wide_tbl, new_widetbl + // {{{ + always @(*) + begin + wide_tbl = 0; + wide_tbl[C_AXI_ADDR_WIDTH-1:0] = r_tbl_addr; + + new_widetbl = wide_tbl; + if (awskd_addr == TBLLO_ADDR) + new_widetbl[31:0] = apply_wstrb(wide_tbl[31:0], + wskd_data, wskd_strb); + if (awskd_addr == TBLHI_ADDR) + new_widetbl[63:32] = apply_wstrb(wide_tbl[63:32], + wskd_data, wskd_strb); + end + // }}} + + // r_prot, r_qos, r_int_enable, r_tbl_addr + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_prot <= 0; + r_qos <= 0; + r_int_enable <= 0; + end else if (!r_busy && axil_write_ready) + begin + case(awskd_addr) + CTRL_ADDR: begin + if (wskd_strb[2]) + begin + r_prot <= wskd_data[22:20]; + r_qos <= wskd_data[19:16]; + end + if (wskd_strb[0]) + begin + r_int_enable <= wskd_data[CTRL_INTEN_BIT]; + end end + TBLLO_ADDR: begin + r_tbl_addr <= new_widetbl[C_AXI_ADDR_WIDTH-1:0]; + end + TBLHI_ADDR: if (C_AXI_ADDR_WIDTH > C_AXIL_DATA_WIDTH) begin + r_tbl_addr <= new_widetbl[C_AXI_ADDR_WIDTH-1:0]; + end + default: begin end + endcase + end else if (r_busy) + begin + r_tbl_addr <= tbl_addr; + end + // }}} + + // apply_wstrb function + // {{{ + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] = wstrb[k] ? new_data[k*8 +: 8] + : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite control read interface + // {{{ + + // AXI-lite AR skid buffer + // {{{ + skidbuffer #( + // {{{ + .OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB) + // }}} + ) axilarskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr) + // }}} + ); + // }}} + + // axil_read_ready + // {{{ + always @(*) + begin + axil_read_ready = !S_AXIL_RVALID || S_AXIL_RREADY; + if (!arskd_valid) + axil_read_ready = 1'b0; + end + // }}} + + // S_AXIL_RVALID + // {{{ + initial S_AXIL_RVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXIL_RVALID <= 1'b0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + S_AXIL_RVALID <= axil_read_ready; + // }}} + + // S_AXIL_RDATA + // {{{ + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + S_AXIL_RDATA <= 0; + else if (!S_AXIL_RVALID || S_AXIL_RREADY) + begin + S_AXIL_RDATA <= 0; + case(arskd_addr) + CTRL_ADDR: begin + S_AXIL_RDATA[CTRL_ERR_BIT] <= r_err; + S_AXIL_RDATA[CTRL_ABORT_BIT] <= r_abort; + S_AXIL_RDATA[CTRL_INTEN_BIT] <= r_int_enable; + S_AXIL_RDATA[CTRL_INT_BIT] <= r_int; + S_AXIL_RDATA[CTRL_BUSY_BIT] <= r_busy; + end + // Unused: + TBLLO_ADDR: + S_AXIL_RDATA <= wide_tbl[C_AXIL_DATA_WIDTH-1:0]; + TBLHI_ADDR: + S_AXIL_RDATA <= wide_tbl[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + default: begin end + endcase + + if (OPT_LOWPOWER && (!axil_read_ready || !arskd_valid)) + S_AXIL_RDATA <= 0; + end + // }}} + + // S_AXIL_RRESP + // {{{ + assign S_AXIL_RRESP = AXI_OKAY; + // }}} + // }}} // AXI-lite read + // }}} // AXI-lite (all) + //////////////////////////////////////////////////////////////////////// + // + // DMA wrapper + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Prefix: dmac for the sub DMA control interface + // Prefix: sdma for the sub DMA master interface + axidma #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .OPT_UNALIGNED(OPT_UNALIGNED), + .OPT_WRAPMEM(OPT_WRAPMEM), + .LGMAXBURST(LGMAXBURST), + .LGFIFO(LGFIFO), + .LGLEN(LGLEN), + .AXI_READ_ID(DMA_READ_ID), + .AXI_WRITE_ID(DMA_WRITE_ID), + .ABORT_KEY(ABORT_KEY) + // }}} + ) subdma( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + // + // The AXI4-lite control interface + // {{{ + .S_AXIL_AWVALID(dmac_wvalid), // Merge AW & W channels:DMA ok w/ + .S_AXIL_AWREADY(dmac_awready_ignored), + .S_AXIL_AWADDR( dmac_waddr), + .S_AXIL_AWPROT( 3'h0), // Internally ignored + // + .S_AXIL_WVALID(dmac_wvalid), + .S_AXIL_WREADY(dmac_wready), + .S_AXIL_WDATA( dmac_wdata), + .S_AXIL_WSTRB( dmac_wstrb), + // + .S_AXIL_BVALID(dmac_bvalid), + .S_AXIL_BREADY(1'b1), + .S_AXIL_BRESP( dmac_bresp), + // + .S_AXIL_ARVALID(!S_AXI_ARESETN), + .S_AXIL_ARREADY(dmac_arready), + .S_AXIL_ARADDR( DMA_CONTROL), + .S_AXIL_ARPROT( 3'h0), // Internally ignored + // + .S_AXIL_RVALID(dmac_rvalid), + .S_AXIL_RREADY(1'b1), + .S_AXIL_RDATA( dmac_rdata), + .S_AXIL_RRESP( dmac_rresp), + // }}} + // + // The AXI Master (DMA) interface + // {{{ + .M_AXI_AWVALID(M_AXI_AWVALID), + .M_AXI_AWREADY(M_AXI_AWREADY), + .M_AXI_AWID( M_AXI_AWID), + .M_AXI_AWADDR( M_AXI_AWADDR), + .M_AXI_AWLEN( M_AXI_AWLEN), + .M_AXI_AWSIZE( M_AXI_AWSIZE), + .M_AXI_AWBURST(M_AXI_AWBURST), + .M_AXI_AWLOCK( unused_dma_lock), + .M_AXI_AWCACHE(M_AXI_AWCACHE), + .M_AXI_AWPROT( M_AXI_AWPROT), + .M_AXI_AWQOS( M_AXI_AWQOS), + // + // + .M_AXI_WVALID(M_AXI_WVALID), + .M_AXI_WREADY(M_AXI_WREADY), +`ifdef AXI3 + .M_AXI_WID(M_AXI_WID), +`endif + .M_AXI_WDATA(M_AXI_WDATA), + .M_AXI_WSTRB(M_AXI_WSTRB), + .M_AXI_WLAST(M_AXI_WLAST), + // + // + .M_AXI_BVALID(M_AXI_BVALID), + .M_AXI_BREADY(M_AXI_BREADY), + .M_AXI_BID( M_AXI_BID), + .M_AXI_BRESP( M_AXI_BRESP), + // }}} + // AXI master read interface + // {{{ + // The read channel needs to be arbitrated + .M_AXI_ARVALID(sdma_arvalid), + .M_AXI_ARREADY(sdma_arready), + .M_AXI_ARID(sdma_arid), + .M_AXI_ARADDR(sdma_araddr), + .M_AXI_ARLEN(sdma_arlen), + .M_AXI_ARSIZE(sdma_arsize), + .M_AXI_ARBURST(sdma_arburst), + .M_AXI_ARLOCK(sdma_arlock), + .M_AXI_ARCACHE(sdma_arcache), + .M_AXI_ARPROT(sdma_arprot), + .M_AXI_ARQOS(sdma_arqos), + // + .M_AXI_RVALID(M_AXI_RVALID && M_AXI_RID == DMA_READ_ID), + .M_AXI_RREADY(sdma_rready_ignored), // Known to be one + .M_AXI_RID( DMA_READ_ID), + .M_AXI_RDATA(M_AXI_RDATA), + .M_AXI_RLAST(M_AXI_RLAST), + .M_AXI_RRESP(M_AXI_RRESP), + // }}} + .o_int(dma_complete) + // }}} + ); + + assign M_AXI_AWLOCK = 0; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-Lite prefetch + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // The AXI_lite fetch submodule + // {{{ + axilfetch #( + // {{{ + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .FETCH_LIMIT(4) + // }}} + ) pf ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + // + // "CPU" interface + // {{{ + .i_cpu_reset(!S_AXI_ARESETN), + .i_new_pc(new_pc), + .i_clear_cache(pf_clear_cache), + .i_ready(pf_ready), + .i_pc(ipc), + .o_insn(pf_insn), + .o_valid(pf_valid), + .o_pc(pf_pc), + .o_illegal(pf_illegal), + // }}} + // AXI-lite interface + // {{{ + .M_AXI_ARVALID(pf_axi_arvalid), + .M_AXI_ARREADY(pf_axi_arready), + .M_AXI_ARADDR( pf_axi_araddr), + .M_AXI_ARPROT( pf_axi_arprot), + // + .M_AXI_RVALID( M_AXI_RVALID && M_AXI_RID == PF_READ_ID), + .M_AXI_RREADY( pf_axi_rready_ignored), // Always 1'b1 + .M_AXI_RDATA( M_AXI_RDATA), + .M_AXI_RRESP( M_AXI_RRESP) + // }}} + // }}} + ); + // }}} + + assign pf_axi_arid = PF_READ_ID; + assign pf_axi_arlen = 0; // Only read singletons + assign pf_axi_arsize = ADDRLSB[2:0]; + assign pf_axi_arburst = AXI_INCR; + assign pf_axi_arcache = 4'b0011; + // assign pf_axi_arprot = 3'b100; + assign pf_axi_arqos = 4'h0; + + assign M_AXI_RREADY = 1'b1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // PF vs DMA arbiter + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // pf_wins_arbitration + // {{{ + always @(posedge S_AXI_ACLK) + if (!m_axi_arvalid || m_axi_arready) + begin + if (pf_axi_arvalid && !sdma_arvalid) + pf_wins_arbitration <= 1'b1; + else + pf_wins_arbitration <= 1'b0; + end + // }}} + + // m_axi_* + // {{{ + always @(*) + begin + if (pf_wins_arbitration) + begin + m_axi_arvalid = pf_axi_arvalid; + m_axi_arid = pf_axi_arid; + m_axi_araddr = pf_axi_araddr; + m_axi_arlen = pf_axi_arlen; + m_axi_arsize = pf_axi_arsize; + m_axi_arburst = pf_axi_arburst; + m_axi_arcache = pf_axi_arcache; + m_axi_arprot = pf_axi_arprot; + m_axi_arqos = pf_axi_arqos; + end else begin + m_axi_arvalid = sdma_arvalid; + m_axi_arid = sdma_arid; + m_axi_araddr = sdma_araddr; + m_axi_arlen = sdma_arlen; + m_axi_arsize = sdma_arsize; + m_axi_arburst = sdma_arburst; + m_axi_arcache = sdma_arcache; + m_axi_arprot = sdma_arprot; + m_axi_arqos = sdma_arqos; + end + end + // }}} + + // *_arready + // {{{ + always @(*) + begin + sdma_arready = m_axi_arready && !pf_wins_arbitration; + pf_axi_arready = m_axi_arready && pf_wins_arbitration; + end + // }}} + + // Outgoing AR skid buffer + // {{{ + skidbuffer #( + // {{{ + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(1'b1), + .DW(C_AXI_ID_WIDTH + C_AXI_ADDR_WIDTH + LENWIDTH + + 3 + 2 + 4 +3 + 4) + // }}} + ) marskd( + // {{{ + S_AXI_ACLK, !S_AXI_ARESETN, m_axi_arvalid, m_axi_arready, + { m_axi_arid, m_axi_araddr, m_axi_arlen, m_axi_arsize, + m_axi_arburst, m_axi_arcache, + m_axi_arprot, m_axi_arqos }, + M_AXI_ARVALID, M_AXI_ARREADY, + { M_AXI_ARID, M_AXI_ARADDR, M_AXI_ARLEN, M_AXI_ARSIZE, + M_AXI_ARBURST, M_AXI_ARCACHE, + M_AXI_ARPROT, M_AXI_ARQOS } + // }}} + ); + +`ifdef AXI3 + assign M_AXI_ARLOCK = 2'b00; // We don't use lock anyway +`else + assign M_AXI_ARLOCK = 1'b0; +`endif + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // FSM Control states + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + axisgfsm #( + // {{{ + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .ABORT_KEY(ABORT_KEY) + // }}} + ) fsm ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + // Control interface + // {{{ + .i_start(r_start), + .i_abort(r_abort), + .i_tbl_addr(r_tbl_addr), + .i_qos(r_qos), + .i_prot(r_prot), + .o_done(r_done), + .o_busy(r_busy), + .o_int(w_int), + .o_err(fsm_err), + .o_tbl_addr(tbl_addr), + // }}} + // Prefetch interface + // {{{ + .o_new_pc(new_pc), + .o_pf_clear_cache(pf_clear_cache), + .o_pf_ready(pf_ready), + .o_pf_pc(ipc), + .i_pf_valid(pf_valid), + .i_pf_insn(pf_insn), + .i_pf_pc(pf_pc), + .i_pf_illegal(pf_illegal), + // }}} + // DMA AXI-lite control interface + // {{{ + .o_dmac_wvalid(dmac_wvalid), + .i_dmac_wready(dmac_wready), + .o_dmac_waddr(dmac_waddr), + .o_dmac_wdata(dmac_wdata), + .o_dmac_wstrb(dmac_wstrb), + .i_dmac_rdata(dmac_rdata), + .i_dma_complete(dma_complete) + // }}} + + // }}} + ); + + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, dmac_awready_ignored, dmac_bvalid, + dmac_bresp, dmac_rvalid, dmac_rresp, + S_AXIL_AWADDR[AXILLSB-1:0], S_AXIL_AWPROT, + S_AXIL_ARADDR[AXILLSB-1:0], S_AXIL_ARPROT, + M_AXI_RREADY, r_done, + new_widetbl[63:C_AXI_ADDR_WIDTH], + unused_dma_lock, + sdma_arlock, sdma_rready_ignored, + pf_axi_rready_ignored, dmac_arready }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties (neither written, nor tested) +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The full formal verification of this core has not been completed. + // + //////////////////////////////////////////////////////////////////////// + // + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // The control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxil_slave #( + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH) + // + // ... + // + ) + faxil( + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr(S_AXIL_AWADDR), + .i_axi_awprot(S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP) + // + // ... + // + ); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The main AXI data interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Formal contract checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions (i.e. constraints) + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // None (currently) + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axisgfsm.v b/rtl/wb2axip/axisgfsm.v new file mode 100644 index 0000000..ea01515 --- /dev/null +++ b/rtl/wb2axip/axisgfsm.v @@ -0,0 +1,1221 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisgfsm.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Scripts an AXI DMA via in-memory tables: reads from the tables, +// commands the DMA. +// +// Registers: +// +// 0. Control +// 8b KEY +// 3'b PROT +// 4'b QOS +// 1b Abort: Either aborting or aborted +// 1b Err: Ended on an error +// 1b Busy +// 1b Interrupt Enable +// 1b Interrupt Set +// 1b Start +// 1. Reserved +// 2-3. First table entry address +// (Current) table entry address on read, if in progress +// (Optional) +// 4-5. Current read address +// 6-7. Current write address +// 1. Remaining amount to be written (this entry) +// +// Table format: +// Table entries either consist of five 32-bit words, or three 32-bit +// words, depending upon the size of the address bus. +// +// Address bus > 30 bits: five 32-bit words +// 0-1: 64b: { 2'bflags, 62'b SOURCE ADDRESS (bytes) } +// 00: Continue after this to next +// 01: Last item in chain +// 10: Skip this address +// 11: Jump to new address +// 2-3: 64b: { int_en, 1'b0, DESTINATION ADDRESS (bytes) } +// 4: 32b Transfer length (in bytes) +// +// Address bus <= 30 bits: three 32-bit words +// 0: 32b: { 2'bflags, 30'b SOURCE ADDRESS (bytes) } +// 1: 32b: { int_en, 1'b0, 30'b DESTINATION ADDRESS (bytes) } +// 2: 32b LENGTH (in bytes) +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// `define AXI3 +// }}} +module axisgfsm #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 30, + // Verilator lint_off UNUSED + parameter C_AXI_DATA_WIDTH = 32, + // Verilator lint_on UNUSED + localparam DMAC_ADDR_WIDTH = 5, + localparam DMAC_DATA_WIDTH = 32, + // + // The "ABORT_KEY" is a byte that, if written to the control + // word while the core is running, will cause the data transfer + // to be aborted. + parameter [7:0] ABORT_KEY = 8'h6d + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The control interface + // {{{ + input wire i_start, i_abort, + input wire [C_AXI_ADDR_WIDTH-1:0] i_tbl_addr, + input wire [3:0] i_qos, + input wire [2:0] i_prot, + output reg o_busy, + output reg o_done, + output reg o_int, + output reg o_err, + output reg [C_AXI_ADDR_WIDTH-1:0] o_tbl_addr, + // }}} + // + // The prefetch interface + // {{{ + output reg o_new_pc, + output reg o_pf_clear_cache, + output reg o_pf_ready, + output reg [C_AXI_ADDR_WIDTH-1:0] o_pf_pc, + input wire i_pf_valid, + input wire [31:0] i_pf_insn, + input wire [C_AXI_ADDR_WIDTH-1:0] i_pf_pc, + input wire i_pf_illegal, + // }}} + // + // The DMA submodule's AXI4-lite control interface + // {{{ + output reg o_dmac_wvalid, + input wire i_dmac_wready, + output reg [DMAC_ADDR_WIDTH-1:0] o_dmac_waddr, + output reg [DMAC_DATA_WIDTH-1:0] o_dmac_wdata, + output reg [DMAC_DATA_WIDTH/8-1:0] o_dmac_wstrb, + input wire [DMAC_DATA_WIDTH-1:0] i_dmac_rdata, + input wire i_dma_complete + // }}} + // }}} + ); + + // Local parameter definitions + // {{{ + + // Scatter gather state machine states + // {{{ + // States: + // - 0. IDLE (nothing going on, waiting for a new program counter) + // Enter on completion, reset, or (abort and DMA complete) + // - 1. READ_LOSRC -> DMA + // - 2. READ_HISRC -> DMA (Optional) + // ((Skip || JUMP) ->READ_LOSRC) + // - 3. READ_LODST -> DMA + // - 4. READ_HIDST -> DMA (Optional) + // - 5. READ_LEN -> DMA + // - 6. READ_FLAGS -> SETS -> START DMA + // - 7. Wait on DMA + // (Set interrupt if INT complete) + // (If last, go to idle, else jump to #1) + // (Set LAST on abort) + // + localparam [2:0] SG_IDLE = 3'h0, + SG_SRCHALF = 3'h1, + SG_SRCADDR = 3'h2, + SG_DSTHALF = 3'h3, + SG_DSTADDR = 3'h4, + SG_LENGTH = 3'h5, + SG_CONTROL = 3'h6, + SG_WAIT = 3'h7; + // }}} + + // DMA device internal addresses + // {{{ + // Verilator lint_off UNUSED + localparam [4:0] DMA_CONTROL= 5'b00000, + DMA_UNUSED = 5'b00100, + DMA_SRCLO = 5'b01000, + DMA_SRCHI = 5'b01100, + DMA_DSTLO = 5'b10000, + DMA_DSTHI = 5'b10100, + DMA_LENLO = 5'b11000, + DMA_LENHI = 5'b11100; + // Verilator lint_on UNUSED + // }}} + + localparam [C_AXI_ADDR_WIDTH-1:0] TBL_SIZE + = (C_AXI_ADDR_WIDTH <= 30) ? (4*3) : (4*5); + + // }}} + + // Register/net declarations + // {{{ + reg tbl_last, tbl_int_enable; + reg [2:0] sgstate; + reg dma_err, dma_abort, dma_done, dma_busy, dma_starting, + dma_aborting; + reg [59:0] r_pf_pc; + reg dma_op_complete, dma_terminate; + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // FSM Control states + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_pf_ready + // {{{ + always @(*) + begin + case(sgstate) + SG_IDLE: o_pf_ready = 1'b0; + SG_SRCHALF: o_pf_ready = (!o_dmac_wvalid || i_dmac_wready); + SG_SRCADDR: o_pf_ready = (!o_dmac_wvalid || i_dmac_wready); + SG_DSTHALF: o_pf_ready = (!o_dmac_wvalid || i_dmac_wready); + SG_DSTADDR: o_pf_ready = (!o_dmac_wvalid || i_dmac_wready); + SG_LENGTH: o_pf_ready = (!o_dmac_wvalid || i_dmac_wready); + SG_CONTROL: o_pf_ready = 1'b0; + SG_WAIT: o_pf_ready = 1'b0; + endcase + + if (!o_busy || o_pf_clear_cache || o_new_pc) + o_pf_ready = 0; + end + // }}} + + // w_int + // {{{ + always @(*) + begin + o_int = 1'b0; + + if (sgstate == SG_WAIT && i_dma_complete + && (tbl_last || tbl_int_enable)) + o_int = 1'b1; + if (i_pf_valid && o_pf_ready && i_pf_illegal) + o_int = 1'b1; + end + // }}} + + // Returns from the DMA controller (one clock later) + // {{{ + always @(*) + begin + dma_err = i_dmac_rdata[4]; + dma_abort = i_dmac_rdata[3]; + dma_done = i_dmac_rdata[1]; + dma_busy = i_dmac_rdata[0]; + end + // }}} + + // dma_op_complete + // {{{ + always @(*) + if (dma_busy || dma_starting || (o_dmac_wvalid && !i_dmac_wready)) + dma_op_complete = 0; + else + dma_op_complete = (!o_dmac_wvalid || !o_dmac_wstrb[0]); + // }}} + + // dma_terminate + // {{{ + always @(*) + begin + dma_terminate = 0; + if (i_abort || tbl_last || dma_aborting) + dma_terminate = 1; + if (dma_err || i_pf_illegal) + dma_terminate = 1; + end + // }}} + + // The GIANT FSM itself + // new_pc, pf_clear_cache, dmac_wvalid, dmac_waddr, dmac_wdata, + // dmac_wstrb, ipc, + // {{{ + initial r_pf_pc = 0; + initial sgstate = SG_IDLE; + initial o_new_pc = 1'b0; + initial o_busy = 1'b0; + initial o_done = 1'b0; + initial o_err = 1'b0; + initial o_dmac_wvalid = 1'b0; + always @(posedge S_AXI_ACLK) + begin + // Auto-cleared values + // {{{ + o_new_pc <= 1'b0; + o_pf_clear_cache <= 1'b0; + if (i_dmac_wready) + o_dmac_wvalid <= 1'b0; + if (!o_dmac_wvalid || i_dmac_wready) + o_dmac_wstrb <= 4'hf; + o_done <= 1'b0; + o_err <= 1'b0; + // }}} + + case(sgstate) + SG_IDLE: // IDLE -- waiting for start + // {{{ + begin + r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_tbl_addr; + if (i_start) + begin + o_new_pc <= 1'b1; + if (C_AXI_ADDR_WIDTH > 30) + sgstate <= SG_SRCHALF; + else + sgstate <= SG_SRCADDR; + o_busy <= 1'b1; + end else begin + o_new_pc <= 1'b0; + o_pf_clear_cache <= 1'b1; + o_busy <= 1'b0; + end end + // }}} + SG_SRCHALF: // Get the first source address + // {{{ + if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready)) + begin + o_dmac_wvalid <= 1'b1; + o_dmac_waddr <= DMA_SRCLO; + o_dmac_wdata <= i_pf_insn; + sgstate <= SG_SRCADDR; + // r_pf_pc[31:0] <= i_pf_insn; + o_tbl_addr <= i_pf_pc; + end + // }}} + SG_SRCADDR: // Second half of the source address + // {{{ + if (i_pf_valid && o_pf_ready) + begin + o_dmac_wvalid <= !i_pf_insn[31]; + o_dmac_waddr <= (C_AXI_ADDR_WIDTH<=30) + ? DMA_SRCLO : DMA_SRCHI; + o_dmac_wdata <= i_pf_insn; + // r_pf_pc[31:0] <= i_pf_insn; + if (C_AXI_ADDR_WIDTH <= 30) + begin + sgstate <= SG_DSTADDR; + o_tbl_addr <= i_pf_pc; + // // r_pf_pc[30-1:0] <= i_pf_insn[30-1:0]; + end else begin + sgstate <= SG_DSTHALF; + // r_pf_pc[60-1:30] <= i_pf_insn[30-1:0]; + end + + tbl_last <= 1'b0; + case(i_pf_insn[31:30]) + 2'b00: // Other items still to follow + tbl_last <= 1'b0; + 2'b01: // Last item in the chain + tbl_last <= 1'b1; + 2'b10: begin // Skip + tbl_last <= 1'b0; + if (C_AXI_ADDR_WIDTH > 30) + sgstate <= SG_SRCHALF; + else + sgstate <= SG_SRCADDR; + o_new_pc <= 1'b1; + r_pf_pc[C_AXI_ADDR_WIDTH-1:0] + <= r_pf_pc[C_AXI_ADDR_WIDTH-1:0] + TBL_SIZE; + end + 2'b11: begin // Jump + o_new_pc <= 1'b1; + // ipc <= // already set from above + if (C_AXI_ADDR_WIDTH > 30) + sgstate <= SG_SRCHALF; + else + sgstate <= SG_SRCADDR; + r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_pf_insn[C_AXI_ADDR_WIDTH-1:0]; + end + endcase + end + // }}} + SG_DSTHALF: // Get the first half of the destination address + // {{{ + if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready)) + begin + o_dmac_wvalid<= 1'b1; + o_dmac_waddr <= DMA_DSTLO; + o_dmac_wdata <= i_pf_insn; + sgstate <= SG_DSTADDR; + end + // }}} + SG_DSTADDR: // Second half of the destination address + // {{{ + if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready)) + begin + o_dmac_wvalid <= 1'b1; + o_dmac_waddr <= (C_AXI_ADDR_WIDTH<=30) + ? DMA_DSTLO : DMA_DSTHI; + o_dmac_wdata <= { 2'b0, i_pf_insn[29:0] }; + sgstate <= SG_LENGTH; + tbl_int_enable <= i_pf_insn[31]; + end + // }}} + SG_LENGTH: // The length word + // {{{ + if (i_pf_valid && (!o_dmac_wvalid || i_dmac_wready)) + begin + o_dmac_wvalid <= 1'b1; + o_dmac_waddr <= DMA_LENLO; + o_dmac_wdata <= i_pf_insn; + sgstate <= SG_CONTROL; + if (i_pf_insn <= 0) + begin + o_dmac_wvalid <= 1'b0; + if (tbl_last) + begin + sgstate <= SG_IDLE; + o_new_pc <= 1'b0; + o_busy <= 1'b0; + o_done <= 1'b1; + o_pf_clear_cache <= 1'b1; + end else begin + sgstate <= SG_SRCADDR; + o_new_pc <= 1'b1; + end + end + end + // }}} + SG_CONTROL: // The control word to get us started + // {{{ + if (!o_dmac_wvalid || i_dmac_wready) + begin + o_dmac_wvalid <= 1'b1; + o_dmac_waddr <= DMA_CONTROL; + o_dmac_wdata <= { 9'h0, i_prot, i_qos, + 11'h0, + 1'h1, // Clear any errors + 1'b1, // Clr abort flag + 1'b1, // Set int enable, int + 1'b1, // Clr any prior interrupt + 1'b1 }; + sgstate <= SG_WAIT; + o_tbl_addr <= o_tbl_addr + TBL_SIZE; + end + // }}} + SG_WAIT: // Wait for the DMA transfer to complete + // {{{ + if (dma_op_complete) + begin + // o_int <= int_enable; + r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= o_tbl_addr; + if (dma_terminate) + begin + o_pf_clear_cache <= 1'b1; + sgstate <= SG_IDLE; + o_busy <= 1'b0; + o_done <= 1'b1; + o_err <= dma_err || dma_abort; + if (i_pf_illegal) + r_pf_pc[C_AXI_ADDR_WIDTH-1:0] <= i_pf_pc; + end else if (C_AXI_ADDR_WIDTH > 30) + sgstate <= SG_SRCHALF; + else + sgstate <= SG_SRCADDR; + end + // }}} + endcase + + if (i_pf_valid && i_pf_illegal + && sgstate != SG_CONTROL && sgstate != SG_WAIT + && !o_new_pc && !o_pf_clear_cache) + begin + // {{{ + sgstate <= SG_IDLE; + o_pf_clear_cache <= 1'b1; + o_done <= 1'b1; + o_busy <= 1'b0; + o_err <= 1'b1; + // }}} + end + + if (i_abort && (!o_dmac_wvalid || i_dmac_wready)) + begin + // {{{ + o_dmac_wstrb <= 4'h8; + o_dmac_wdata[31:24] <= ABORT_KEY; + o_dmac_wvalid <= o_dmac_wstrb[0] && dma_busy + && !dma_err && (sgstate == SG_WAIT); + // }}} + if (!dma_busy && (sgstate != SG_WAIT)) + begin + sgstate <= SG_IDLE; + o_done <= 1'b1; + o_new_pc<= 1'b0; + o_busy <= 1'b0; + o_dmac_wvalid <= 1'b0; + o_pf_clear_cache <= 1'b1; + end + end + + if (!S_AXI_ARESETN) + begin + // {{{ + sgstate <= SG_IDLE; + o_pf_clear_cache <= 1'b1; + o_new_pc <= 1'b0; + o_dmac_wvalid <= 1'b0; + r_pf_pc <= 0; + o_busy <= 1'b0; + o_done <= 1'b0; + o_err <= 1'b0; + // }}} + end + + r_pf_pc[60-1:C_AXI_ADDR_WIDTH] <= 0; + end + // }}} + + // dma_starting + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + dma_starting <= 0; + else if (sgstate != SG_WAIT) + dma_starting <= 0; + else if (!o_dmac_wvalid || o_dmac_waddr != DMA_CONTROL) + dma_starting <= 0; + else + dma_starting <= o_dmac_wdata[0] && o_dmac_wstrb[0]; + + always @(*) + o_pf_pc = { r_pf_pc[C_AXI_ADDR_WIDTH-1:2], 2'b00 }; + // }}} + + // dma_aborting + // {{{ + initial dma_aborting = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + dma_aborting <= 0; + else if (sgstate != SG_WAIT) + dma_aborting <= 0; + else if (!dma_aborting) + begin + if (i_abort) + dma_aborting <= 1; + end else if (!dma_busy && !dma_starting && (!o_dmac_wvalid + || (i_dmac_wready && !o_dmac_wstrb[0]))) + dma_aborting <= 0; +`ifdef FORMAL + always @(*) + if (S_AXI_ARESETN) + begin + if (o_dmac_wvalid && !o_dmac_wstrb[0]) + assert(dma_aborting); + if (sgstate != SG_WAIT && sgstate != SG_IDLE) + assert(!dma_aborting); + end +`endif + // }}} + // }}} + + // Make Verilator happy + // {{{ + // Verilatar lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, dma_done, i_dmac_rdata[31:5], + i_dmac_rdata[2], + r_pf_pc[59:C_AXI_ADDR_WIDTH] }; + // Verilatar lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + //////////////////////////////////////////////////////////////////////// + // + localparam F_LGDEPTH = 4; + // Verilator lint_off UNDRIVEN + (* anyseq *) reg [TBL_SIZE*8-1:0] f_tblentry; + (* anyconst *) reg f_never_abort; + (* anyseq *) reg f_dma_complete; + // Verilator lint_on UNDRIVEN + reg f_past_valid; + reg [C_AXI_ADDR_WIDTH-1 :0] f_tbladdr, f_pc; + reg f_tbl_last, f_tbl_skip, f_tbl_jump, + f_tbl_int_enable; + // reg [C_AXI_ADDR_WIDTH-1:0] f_tbl_src; + reg f_dma_arvalid, f_dma_arready; + reg f_dma_busy; + + + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + always @(*) + if (i_start) + begin + assume(!i_abort); + assume(i_tbl_addr[1:0] == 2'b00); + end + + //////////////////////////////////////////////////////////////////////// + // + // Prefetch interface property checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Prefetch properties + + assume property (@(posedge S_AXI_ACLK) + S_AXI_ARESETN && i_pf_valid && !o_pf_ready + && !o_pf_clear_cache && !o_new_pc + |=> i_pf_valid && $stable({ + i_pf_insn, i_pf_pc, i_pf_illegal })); + + always @(*) + if (o_new_pc) + assert(o_pf_pc[1:0] == 2'b00); + + always @(posedge S_AXI_ACLK) + if (o_new_pc) + f_pc <= o_pf_pc; + else if (i_pf_valid && o_pf_ready) + f_pc <= f_pc + 4; + + always @(*) + if (i_pf_valid) + assume(i_pf_pc == f_pc); + + always @(*) + if (S_AXI_ARESETN && !o_new_pc && !o_pf_clear_cache) + assert(f_pc[1:0] == 2'b00); + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN || o_new_pc + || o_pf_clear_cache)) + begin + assume(!i_pf_illegal); + end else if (!$rose(i_pf_valid)) + begin + assume(!$rose(i_pf_illegal)); + end else + assume($stable(i_pf_illegal)); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite interface property checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [F_LGDEPTH-1:0] fdma_rd_outstanding, fdma_wr_outstanding, + fdma_awr_outstanding; + reg f_dma_bvalid, f_dma_rvalid; + + faxil_master #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(DMAC_ADDR_WIDTH), + .F_OPT_BRESP(1'b1), + .F_OPT_RRESP(1'b0), + .F_OPT_NO_RESET(1'b1), + .F_LGDEPTH(F_LGDEPTH) + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(o_dmac_wvalid), + .i_axi_awready(i_dmac_wready), + .i_axi_awaddr(o_dmac_waddr), + .i_axi_awprot( 3'h0), + // + .i_axi_wvalid(o_dmac_wvalid), + .i_axi_wready(i_dmac_wready), + .i_axi_wdata( o_dmac_wdata), + .i_axi_wstrb( o_dmac_wstrb), + // + .i_axi_bvalid(f_dma_bvalid), + .i_axi_bready(1'b1), + .i_axi_bresp( 2'b00), + // + .i_axi_arvalid(f_dma_arvalid), + .i_axi_arready(f_dma_arready), + .i_axi_araddr(DMA_CONTROL), + .i_axi_arprot(3'h0), + // + .i_axi_rvalid(f_dma_rvalid), + .i_axi_rready(1'b1), + .i_axi_rdata(i_dmac_rdata), + .i_axi_rresp(2'b00), + // + .f_axi_rd_outstanding(fdma_rd_outstanding), + .f_axi_wr_outstanding(fdma_wr_outstanding), + .f_axi_awr_outstanding(fdma_awr_outstanding) + // }}} + ); + + initial f_dma_arvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dma_arvalid <= 1'b0; + else + f_dma_arvalid <= 1'b1; + + always @(*) + f_dma_arready = 1'b1; + + always @(*) + if (S_AXI_ARESETN) + begin + assert(fdma_awr_outstanding == fdma_wr_outstanding); + assert(fdma_wr_outstanding == (f_dma_bvalid ? 1:0)); + assert(fdma_rd_outstanding <= 1); + end + + assert property (@(posedge S_AXI_ACLK) + !S_AXI_ARESETN + |=> sgstate == SG_IDLE && !o_dmac_wvalid); + + + assert property (@(posedge S_AXI_ACLK) + S_AXI_ARESETN && o_dmac_wvalid && !i_dmac_wready + |=> o_dmac_wvalid && $stable({ + o_dmac_waddr, o_dmac_wdata, o_dmac_wstrb })); + + assert property (@(posedge S_AXI_ACLK) + fdma_wr_outstanding == fdma_awr_outstanding); + + assert property (@(posedge S_AXI_ACLK) + fdma_wr_outstanding == (f_dma_bvalid ? 1:0)); + + initial f_dma_bvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dma_bvalid <= 0; + else if (o_dmac_wvalid && i_dmac_wready) + f_dma_bvalid <= 1; + else + f_dma_bvalid <= 0; + + initial f_dma_rvalid = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dma_rvalid <= 0; + else if (f_dma_arvalid) + f_dma_rvalid <= 1; + else + f_dma_rvalid <= 0; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // DMA busy and read interface properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + initial f_dma_busy = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dma_busy <= 0; + else if (o_dmac_wvalid && i_dmac_wready && o_dmac_waddr == 0 + && o_dmac_wstrb[0] && o_dmac_wdata[0]) + f_dma_busy <= 1'b1; + else if (f_dma_busy) + f_dma_busy <= !f_dma_complete; + + always @(*) + if (S_AXI_ARESETN) + begin + if (sgstate != SG_WAIT) + assert(!f_dma_busy); + else if (o_dmac_wvalid && !dma_aborting) + assert(!f_dma_busy); + end + + always @(posedge S_AXI_ACLK) + if (f_past_valid && S_AXI_ARESETN && $past(f_dma_busy)) + assert(!dma_starting); + + always @(posedge S_AXI_ACLK) + if (f_past_valid) + begin + assume(dma_busy == $past(f_dma_busy)); + assume(i_dma_complete == $past(f_dma_complete)); + end + + always @(*) + if (!f_dma_busy) + assume(!f_dma_complete); + + assume property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + !dma_busy ##1 !dma_busy |-> !$rose(dma_err) + ); + + assume property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + $rose(dma_busy) |=> !dma_err); + + assume property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + o_dmac_wvalid && i_dmac_wready && o_dmac_wstrb[0] + && o_dmac_wdata[4] + |=> ##1 !dma_err); + + assume property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + !o_dmac_wvalid || !i_dmac_wready || !o_dmac_wstrb[0] + || !o_dmac_wdata[4] + |=> ##1 !$fell(dma_err)); + + +// assume property (@(posedge S_AXI_ACLK) +// dma_busy |=> dma_busy [*0:$] +// ##1 dma_busy && i_dma_complete +// ##1 !dma_busy); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // State machine checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + + always @(*) + if (o_new_pc) + begin + if (C_AXI_ADDR_WIDTH <= 30) + begin + assert(sgstate == SG_SRCADDR); + end else + assert(sgstate == SG_SRCHALF); + end + + generate if (C_AXI_ADDR_WIDTH > 30) + begin + always @(*) + begin + f_tbl_last = (f_tblentry[63:62] == 2'b01); + f_tbl_skip = (f_tblentry[63:62] == 2'b10); + f_tbl_jump = (f_tblentry[63:62] == 2'b11); + f_tbl_int_enable = f_tblentry[127]; + end + end else begin + always @(*) + begin + f_tbl_last = (f_tblentry[31:30] == 2'b01); + f_tbl_skip = (f_tblentry[31:30] == 2'b10); + f_tbl_jump = (f_tblentry[31:30] == 2'b11); + f_tbl_int_enable = f_tblentry[63]; + end + end endgenerate + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && (sgstate != SG_IDLE)) + begin + if ($stable(sgstate)) + begin + assume($stable(f_tblentry)); + end else if ((C_AXI_ADDR_WIDTH > 30)&&(sgstate != SG_SRCHALF)) + begin + assume($stable(f_tblentry)); + end else if ((C_AXI_ADDR_WIDTH <= 30)&&(sgstate != SG_SRCADDR)) + assume($stable(f_tblentry)); + end + + always @(posedge S_AXI_ACLK) + if (o_new_pc) + f_tbladdr <= o_pf_pc; + else if (sgstate == SG_WAIT && dma_op_complete && !dma_terminate) + f_tbladdr <= f_tbladdr + TBL_SIZE; + + assert property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + sgstate == SG_IDLE && !i_start + |=> sgstate == SG_IDLE && o_pf_clear_cache); + + generate if (C_AXI_ADDR_WIDTH <= 30) + begin : SMALL_ADDRESS_SPACE + // {{{ + reg [5:0] f_dma_seq; + + always @(*) + begin + assert(sgstate != SG_SRCHALF); + assert(sgstate != SG_DSTHALF); + end + + always @(*) + if (i_pf_valid) + case(sgstate) + // SG_IDLE: begin end + // SG_SRCHALF: begin end + // SG_DSTHALF: begin end + // SG_LENGTH: begin end + // SG_CONTROL: begin end + // SG_WAIT: begin end + SG_SRCADDR: begin + assume(i_pf_insn == f_tblentry[31:0] + &&(i_pf_pc == f_tbladdr)); + end + SG_DSTADDR: begin + assume(i_pf_insn == f_tblentry[63:32] + &&(i_pf_pc == f_tbladdr + 4)); + end + SG_LENGTH: begin + assume(i_pf_insn == f_tblentry[95:64] + &&(i_pf_pc == f_tbladdr + 8)); + end + default: begin end + endcase + + assert property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + sgstate == SG_IDLE && i_start + |=> o_new_pc && !o_pf_clear_cache + && sgstate == SG_SRCADDR + && r_pf_pc[C_AXI_ADDR_WIDTH-1:0] + == $past(i_tbl_addr)); + + initial f_dma_seq = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dma_seq <= 0; + else begin + + // SG_SRCADDR, or entering SG_SRCADDR + // {{{ + if ((f_dma_seq == 0)&&(i_start)) + f_dma_seq <= 1; + + if (f_dma_seq[0] || o_new_pc) + begin + assert(sgstate == SG_SRCADDR); + assert(!o_dmac_wvalid); + assert(!dma_busy); + f_dma_seq <= 1; + if (i_pf_valid && o_pf_ready) + begin + f_dma_seq <= 2; + + if (f_tbl_jump || f_tbl_skip) + f_dma_seq <= 1; + end + + if (!o_new_pc) + begin + assert(o_pf_pc == f_tbladdr); + assert(f_pc == o_pf_pc); + end + + if ($past(sgstate == SG_SRCADDR + && i_pf_valid && o_pf_ready)) + begin + // Jump or skip + + assert(o_new_pc); + // Check the jump address + // if ($past(i_pf_insn[31:30] == 2'b11)) + if ($past(f_tbl_jump)) + begin + assert(o_pf_pc == { $past(i_pf_insn[C_AXI_ADDR_WIDTH-1:2]), 2'b00 }); + end else // Skip + assert(o_pf_pc == $past(f_tbladdr + TBL_SIZE)); + end + end + // }}} + + // SG_DSTADDR + // {{{ + if (f_dma_seq[1]) + begin + assert(sgstate == SG_DSTADDR); + assert(tbl_last == f_tbl_last); + assert(o_tbl_addr == f_tbladdr); + if ($rose(f_dma_seq[1])) + assert(o_dmac_wvalid); + assert(o_dmac_waddr == DMA_SRCLO); + assert(o_dmac_wdata == f_tblentry[31:0]); + assert(&o_dmac_wstrb); + assert(!dma_busy); + assert(o_pf_pc == f_tbladdr); + // assert(f_pc == o_pf_pc + 4); + f_dma_seq <= 2; + if (i_pf_valid && o_pf_ready) + f_dma_seq <= 4; + end + // }}} + + // SG_LENGTH + // {{{ + if (f_dma_seq[2]) + begin + assert(sgstate == SG_LENGTH); + assert(tbl_last == f_tbl_last); + assert(tbl_int_enable == f_tbl_int_enable); + assert(o_tbl_addr == f_tbladdr); + if ($rose(f_dma_seq[2])) + assert(o_dmac_wvalid); + assert(o_dmac_waddr == DMA_DSTLO); + assert(o_dmac_wdata == { 2'b00, f_tblentry[61:32] }); + assert(&o_dmac_wstrb); + assert(!dma_busy); + assert(o_pf_pc == f_tbladdr); + // assert(f_pc == o_pf_pc + 8); + f_dma_seq <= 4; + if (i_pf_valid && o_pf_ready) + begin + if (i_pf_insn == 0) + begin + if (tbl_last) + f_dma_seq <= 0; + else + f_dma_seq <= 1; + end else + f_dma_seq <= 8; + end + end + // }}} + + // SG_CONTROL + // {{{ + if (f_dma_seq[3]) + begin + assert(sgstate == SG_CONTROL); + assert(tbl_last == f_tbl_last); + assert(o_tbl_addr == f_tbladdr); + assert(o_dmac_wvalid); + assert(o_dmac_waddr == DMA_LENLO); + assert(o_dmac_wdata == f_tblentry[95:64]); + assert(&o_dmac_wstrb); + assert(!dma_busy); + assert(o_pf_pc == f_tbladdr); + assert(f_pc == f_tbladdr + TBL_SIZE); + // assert(f_pc == o_pf_pc); + f_dma_seq <= 8; + if (i_dmac_wready) + f_dma_seq <= 16; + end + // }}} + + // SG_WAIT && o_dmac_wvalid + // {{{ + if (f_dma_seq[4]) + begin + assert(sgstate == SG_WAIT); + assert(tbl_last == f_tbl_last); + assert(o_tbl_addr == f_tbladdr + TBL_SIZE); + assert(o_dmac_wvalid); + assert(o_dmac_waddr == DMA_CONTROL); + assert(o_dmac_wdata[15:0] == 16'h1f); + assert(&o_dmac_wstrb); + assert(!dma_busy); + assert(o_pf_pc == f_tbladdr); + assert(f_pc == f_tbladdr + TBL_SIZE); + // assert(f_pc == o_pf_pc); + f_dma_seq <= 16; + if (o_dmac_wvalid && i_dmac_wready) + f_dma_seq <= 32; + end + // }}} + + // SG_WAIT && !o_dmac_wvalid + // {{{ + if (f_dma_seq[5]) + begin + assert(sgstate == SG_WAIT); + assert(tbl_last == f_tbl_last); + assert(o_tbl_addr == f_tbladdr + TBL_SIZE); + assert(o_dmac_waddr == DMA_CONTROL); + // assert(o_dmac_wdata == f_tblentry[95:64]); + if (f_never_abort) + begin + assert(&o_dmac_wstrb); + assert(!o_dmac_wvalid); + end else + assert(o_dmac_wstrb == 4'h8 + || o_dmac_wstrb == 4'hf); + if (o_dmac_wvalid) + assert(!o_dmac_wstrb[0]); + f_dma_seq <= 32; + assert(o_pf_pc == f_tbladdr); + assert(f_pc == f_tbladdr + TBL_SIZE); + // assert(f_pc == o_pf_pc); + // if (tbl_last || (dma_err && !o_busy) + // || i_pf_illegal) + if (dma_op_complete) + begin + if (dma_terminate) + // (dma_err && !o_busy)) + f_dma_seq <= 0; + else + f_dma_seq <= 1; + end + end + // }}} + + // pf_illegal + // {{{ + if ((|f_dma_seq[2:0]) && i_pf_illegal) + f_dma_seq <= 0; + // }}} + + // i_abort + if ((|f_dma_seq[3:0]) && i_abort + && (!o_dmac_wvalid || i_dmac_wready)) + f_dma_seq <= 0; + end + + always @(*) + if (f_dma_seq == 0) + begin + assert(sgstate == SG_IDLE); + assert(!o_new_pc); + assert(!o_dmac_wvalid); + end + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin end + else if (sgstate == 0) + begin + assert(o_pf_clear_cache); + assert(!dma_busy); + end + + cover property (@(posedge S_AXI_ACLK) S_AXI_ARESETN); // !!! + cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN); // !!! + cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN && !i_abort); // !!! + cover property (@(posedge S_AXI_ACLK) f_dma_seq == 0 && S_AXI_ARESETN && !i_abort && i_start); // !!! + cover property (@(posedge S_AXI_ACLK) f_dma_seq[1]); + cover property (@(posedge S_AXI_ACLK) f_dma_seq[2]); + cover property (@(posedge S_AXI_ACLK) f_dma_seq[3]); + cover property (@(posedge S_AXI_ACLK) f_dma_seq[4]); // !!! + cover property (@(posedge S_AXI_ACLK) f_dma_seq[5]); // !!! + +// cover property (@(posedge S_AXI_ACLK) +// disable iff (!S_AXI_ARESETN || i_abort || i_pf_illegal +// || dma_err) +// f_dma_seq[0] ##1 1[*0:$] ##1 f_dma_seq[5] +// ##1 f_dma_seq == 0); // !!! + + cover property (@(posedge S_AXI_ACLK) // !!! + f_dma_seq[5] && !tbl_last && f_dma_busy && dma_busy); + + cover property (@(posedge S_AXI_ACLK) // !!! + f_dma_seq[5] && tbl_last && f_dma_busy && dma_busy); + + cover property (@(posedge S_AXI_ACLK) + f_dma_seq[5] ##2 f_dma_seq[0]); // !!! + + cover property (@(posedge S_AXI_ACLK) + f_dma_seq[5] && tbl_last && i_dma_complete); // !!! + + cover property (@(posedge S_AXI_ACLK) + f_dma_seq[5] && i_dma_complete); // !!! + // }}} + end else begin : BIG_ADDR + end endgenerate + + always @(*) + assert(o_busy == (sgstate != SG_IDLE)); + + always @(*) + if (o_busy) + begin + assert(!o_done); + assert(!o_err); + end + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN) && $past(o_busy) && !o_busy) + assert(o_done); + +// assert property (@(posedge S_AXI_ACLK) +// !o_done |-> !o_int); + + assert property (@(posedge S_AXI_ACLK) + !o_done && !o_busy |-> !o_err); + + always @(*) + if (o_pf_ready) + assert(!o_dmac_wvalid || i_dmac_wready); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions (i.e. constraints) + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + if (f_never_abort) + assume(!i_abort); + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axispacker.v b/rtl/wb2axip/axispacker.v new file mode 100644 index 0000000..74b8f5b --- /dev/null +++ b/rtl/wb2axip/axispacker.v @@ -0,0 +1,890 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axispacker +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: AXI-Stream packer: This uses TKEEP to pack bytes in a stream. +// Bytes with TKEEP clear on input will be removed from the +// output stream. +// +// TID, TDEST, and TUSER fields are not (currently) implemented, although +// they shouldn't be too hard to add back in. +// +// This design *ASSUMES* that TLAST will only be set if TKEEP is not zero. +// This assumption is not required by the AXI-stream specification, and +// should be removed in the future. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2021-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axispacker #( + parameter C_AXIS_DATA_WIDTH = 32, + parameter [0:0] OPT_LOWPOWER = 1 + ) ( + // {{{ + input wire S_AXI_ACLK, S_AXI_ARESETN, + // Incoming slave interface + // {{{ + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire [C_AXIS_DATA_WIDTH/8-1:0] S_AXIS_TSTRB, + input wire [C_AXIS_DATA_WIDTH/8-1:0] S_AXIS_TKEEP, + input wire S_AXIS_TLAST, + // }}} + // Outgoing AXI-Stream master interface + // {{{ + output reg M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, + output reg [C_AXIS_DATA_WIDTH/8-1:0] M_AXIS_TSTRB, + output reg [C_AXIS_DATA_WIDTH/8-1:0] M_AXIS_TKEEP, + output reg M_AXIS_TLAST + // }}} + // }}} + ); + + // Local declarations + // {{{ + localparam DW = C_AXIS_DATA_WIDTH; + + wire pre_tvalid, pre_tready; + wire [DW-1:0] pre_tdata; + wire [DW/8-1:0] pre_tstrb, pre_tkeep; + wire pre_tlast; + + localparam MAX_COUNT = DW/8; + localparam COUNT_BITS = $clog2(MAX_COUNT+1)+1; + + reg [DW-1:0] pck_tdata; + reg [DW/8-1:0] pck_tstrb, pck_tkeep; + reg pck_tlast; + reg [COUNT_BITS-1:0] pck_count; + + reg axis_ready; + + wire skd_valid; + wire [DW-1:0] skd_data; + wire [DW/8-1:0] skd_strb, skd_keep; + wire [COUNT_BITS-1:0] skd_count; + wire skd_last; + + reg [COUNT_BITS-1:0] mid_fill; + reg [DW-1:0] mid_data; + reg [DW/8-1:0] mid_strb, mid_keep; + reg mid_last; + + reg [2*DW-1:0] w_packed_data; + reg [2*DW/8-1:0] w_packed_strb, w_packed_keep; + + localparam TRIM_MAX = MAX_COUNT[COUNT_BITS-1:0]; + reg next_valid, next_last; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming skid buffer + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // This makes it so that we can control VALID && DATA (and so + // backpressure) with a combinatorial value, something that would + // otherwise be against protocol. + // + +`ifdef FORMAL + // {{{ + assign pre_tvalid = S_AXIS_TVALID; + assign S_AXIS_TREADY = pre_tready; + assign pre_tdata = S_AXIS_TDATA; + assign pre_tstrb = S_AXIS_TSTRB; + assign pre_tkeep = S_AXIS_TKEEP; + assign pre_tlast = S_AXIS_TLAST; + // }}} +`else + skidbuffer #( + // {{{ + .DW(DW + DW/8 + DW/8 + 1), + .OPT_OUTREG(1'b1) + // }}} + ) slave_skd ( + // {{{ + .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXIS_TVALID), .o_ready(S_AXIS_TREADY), + .i_data({ S_AXIS_TDATA, S_AXIS_TSTRB, + S_AXIS_TKEEP, S_AXIS_TLAST }), + .o_valid(pre_tvalid), .i_ready(pre_tready), + .o_data({ pre_tdata, pre_tstrb, pre_tkeep, pre_tlast }) + // }}} + ); +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Beat packing stage + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + begin + { pck_tdata, pck_tstrb, pck_tkeep } + = pack( pre_tdata, pre_tstrb, + (pre_tvalid) ? pre_tkeep : {(DW/8){1'b0}} ); + pck_tlast = pre_tlast; + // Verilator lint_off WIDTH + pck_count = $countones(pre_tkeep); + // Verilator lint_on WIDTH + end + +`ifdef FORMAL + // {{{ + assign skd_valid = pre_tvalid; + assign pre_tready = axis_ready; + assign skd_data = pck_tdata; + assign skd_strb = pck_tstrb; + assign skd_keep = pck_tkeep; + assign skd_last = pck_tlast; + assign skd_count= pck_count; + // }}} +`else + skidbuffer #( + // {{{ + .DW(DW + DW/8 + DW/8 + 1 + COUNT_BITS), + .OPT_OUTREG(1'b1) + // }}} + ) beat_skd ( + // {{{ + .i_clk(S_AXI_ACLK), + .i_reset(!S_AXI_ARESETN), + .i_valid(pre_tvalid), .o_ready(pre_tready), + .i_data({ pck_count, pck_tdata, pck_tstrb, + pck_tkeep, pck_tlast }), + .o_valid(skd_valid), .i_ready(axis_ready), + .o_data({ skd_count, skd_data, skd_strb, skd_keep, + skd_last }) + // }}} + ); +`endif + + function [DW+DW/8+DW/8-1:0] pack; // (i_data, i_strb, i_keep) + // {{{ + input [DW-1:0] i_data; + input [DW/8-1:0] i_strb; + input [DW/8-1:0] i_keep; + + reg [DW-1:0] p_data; + reg [DW/8-1:0] p_strb; + reg [DW/8-1:0] p_keep; + + integer rounds, ik; + begin + p_data = i_data; + p_strb = i_strb; + p_keep = i_keep; + for(ik=0; ik < DW/8; ik=ik+1) + begin + if (!p_keep[ik]) + begin + p_data[ik*8 +: 8]= 8'h00; + p_strb[ik] = 1'b0; + end + end + for(rounds = 0; rounds < DW/8; rounds = rounds+1) + begin + for(ik=0; ik < DW/8-1; ik=ik+1) + begin + if (!p_keep[ik]) + begin + p_data[ik*8 +: 8]=p_data[(ik+1)*8 +: 8]; + p_keep[ik] = p_keep[ik+1]; + p_strb[ik] = p_strb[ik+1]; + + p_keep[ik+1] = 0; + p_data[(ik+1)*8 +: 8] = 0; + p_strb[ik+1] = 0; + end + end + end + + pack = { p_data, p_strb, p_keep }; +/* + p_data = 0; + p_strb = 0; + p_keep = 0; + + for(fill=0; fill<2*DW; fill=fill+1) + begin + p_data[(fill *8) +: 8] = i_data[fill*8 +: 8]; + p_strb[ fill ] = i_strb[fill]; + p_keep[ fill ] = i_keep[fill]; + if (fill != 0) + p_keep[ fill ] = i_keep[fill] + && $countones(i_keep[((fill > 0)?(fill-1):0):0]) == fill; + for(ik=fill; ik<2*DW; ik=ik+1) + if (i_keep[ik] && $countones(i_keep[ik-1:0]) == fill) + begin + p_data[fill*8 +: 8] = i_data[ik*8 +: 8]; + p_strb[fill ] = i_strb[ik]; + p_keep[fill ] = i_keep[ik]; + end + + if (!p_keep[fill]) + begin + p_strb[fill] = 1'b0; + if (OPT_LOWPOWER) + p_data[(fill *8) +: 8] = 8'h00; + end + end + + pack = { p_data, p_strb, p_keep }; +*/ + end endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Word Packing stage + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // axis_ready + // {{{ + always @(*) + begin + axis_ready = 1; + if ((mid_last || (skd_count + mid_fill >= TRIM_MAX)) + &&(M_AXIS_TVALID && !M_AXIS_TREADY)) + axis_ready = 0; + if (!skd_valid) + axis_ready = 0; + end + // }}} + + // mid_fill + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + mid_fill <= 0; + else if (mid_last && (!M_AXIS_TVALID || M_AXIS_TREADY)) + mid_fill <= (axis_ready) ? skd_count : 0; + else if (skd_valid && skd_last && (!M_AXIS_TVALID || M_AXIS_TREADY)) + mid_fill <= (skd_count + mid_fill <= TRIM_MAX) ? 0 : skd_count + mid_fill - TRIM_MAX; + else + mid_fill <= mid_fill + (axis_ready ? skd_count : 0) + - ((next_valid && (!M_AXIS_TVALID || M_AXIS_TREADY)) + ? TRIM_MAX : 0); + // }}} + + // mid_last + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + mid_last <= 0; + else if (axis_ready) + begin + mid_last <= !next_last || (M_AXIS_TVALID && !M_AXIS_TREADY); + if (mid_last) + mid_last <= 1'b1; + if (!skd_last) + mid_last <= 1'b0; + end else if (!M_AXIS_TVALID || M_AXIS_TREADY) + mid_last <= 0; + // }}} + + // mid_data, mid_strb, mid_keep + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + mid_data <= 0; + mid_strb <= 0; + mid_keep <= 0; + end else if (mid_last && (!M_AXIS_TVALID || M_AXIS_TREADY)) + begin + mid_data <= (axis_ready) ? skd_data : 0; + mid_strb <= (axis_ready) ? skd_strb : 0; + mid_keep <= (axis_ready) ? skd_keep : 0; + end else if (skd_valid && skd_last && (!M_AXIS_TVALID || M_AXIS_TREADY)) + begin + if (skd_count + mid_fill <= TRIM_MAX) + begin + mid_data <= 0; + mid_strb <= 0; + mid_keep <= 0; + end else begin + mid_data <= w_packed_data[2*DW-1:DW]; + mid_strb <= w_packed_strb[2*DW/8-1:DW/8]; + mid_keep <= w_packed_keep[2*DW/8-1:DW/8]; + end + end else if (axis_ready) + begin + if (mid_fill + skd_count >= TRIM_MAX) + begin + mid_data <= w_packed_data[2*DW-1:DW]; + mid_strb <= w_packed_strb[2*DW/8-1:DW/8]; + mid_keep <= w_packed_keep[2*DW/8-1:DW/8]; + end else begin + mid_data <= w_packed_data[DW-1:0]; + mid_strb <= w_packed_strb[DW/8-1:0]; + mid_keep <= w_packed_keep[DW/8-1:0]; + end + end + // }}} + + // w_packed_data, w_packed_strb, w_packed_keep + // {{{ + always @(*) + begin + w_packed_data = 0; + w_packed_strb = 0; + + w_packed_data = { {(DW){1'b0}}, mid_data } + | ( { {(DW){1'b0}}, skd_data } << (mid_fill * 8)); + w_packed_strb = { {(DW/8){1'b0}}, mid_strb } + | ( { {(DW/8){1'b0}}, skd_strb } << (mid_fill)); + w_packed_keep = { {(DW/8){1'b0}}, mid_keep } + | ( { {(DW/8){1'b0}}, skd_keep } << (mid_fill)); + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outputs + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // next_valid + // {{{ + always @(*) + begin + next_valid = skd_valid && (skd_count + mid_fill >= TRIM_MAX); + if (mid_last || (skd_last && skd_valid)) + next_valid = 1; + if (skd_count + mid_fill == 0) + next_valid = 0; + end + // }}} + + // next_last + // {{{ + always @(*) + begin + next_last = mid_last; + if (skd_valid && skd_last && (skd_count + mid_fill <= TRIM_MAX)) + next_last = 1; + if (!next_valid) + next_last = 0; + end + // }}} + + // M_AXIS_TVALID + // {{{ + initial M_AXIS_TVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXIS_TVALID <= 0; + else if (!M_AXIS_TVALID || M_AXIS_TREADY) + M_AXIS_TVALID <= next_valid; + // }}} + + // M_AXIS_TDATA, M_AXIS_TSTRB, M_AXIS_TKEEP, M_AXIS_TLAST + // {{{ + initial M_AXIS_TDATA = 0; + initial M_AXIS_TSTRB = 0; + initial M_AXIS_TKEEP = 0; + initial M_AXIS_TLAST = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + M_AXIS_TDATA <= 0; + M_AXIS_TSTRB <= 0; + M_AXIS_TKEEP <= 0; + M_AXIS_TLAST <= 0; + end else if (!M_AXIS_TVALID || M_AXIS_TREADY) + begin + if (mid_last) + begin + M_AXIS_TDATA <= mid_data; + M_AXIS_TSTRB <= mid_strb; + M_AXIS_TKEEP <= mid_keep; + M_AXIS_TLAST <= 1'b1; + end else begin + M_AXIS_TDATA <= w_packed_data[DW-1:0]; + M_AXIS_TSTRB <= w_packed_strb[DW/8-1:0]; + M_AXIS_TKEEP <= w_packed_keep[DW/8-1:0]; + M_AXIS_TLAST <= next_last; + end + + + if (OPT_LOWPOWER && !next_valid) + begin + M_AXIS_TDATA <= 0; + M_AXIS_TSTRB <= 0; + M_AXIS_TKEEP <= 0; + M_AXIS_TLAST <= 0; + end + end + // }}} + + // }}} + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + localparam F_COUNT = COUNT_BITS + 4 + 16; + reg [F_COUNT-1:0] f_icount, f_ocount, f_ibeat, f_obeat, + f_mcount, f_mbeat; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + //////////////////////////////////////////////////////////////////////// + // + // Incoming stream assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assume(!S_AXIS_TVALID); + end else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY)) + begin + assume(S_AXIS_TVALID); + assume($stable(S_AXIS_TDATA)); + assume($stable(S_AXIS_TSTRB)); + assume($stable(S_AXIS_TKEEP)); + assume($stable(S_AXIS_TLAST)); + end + + // If TKEEP is low, TSTRB must be low as well + always @(*) + if (S_AXI_ARESETN) + assume(((~S_AXIS_TKEEP) & S_AXIS_TSTRB) == 0); + + // TLAST requires at least one beat + always @(*) + if (S_AXI_ARESETN && S_AXIS_TVALID && S_AXIS_TLAST) + assume(S_AXIS_TKEEP != 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outgoing stream assertions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assert(!M_AXIS_TVALID); + end else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY)) + begin + assert(M_AXIS_TVALID); + assert($stable(M_AXIS_TDATA)); + assert($stable(M_AXIS_TSTRB)); + assert($stable(M_AXIS_TKEEP)); + assert($stable(M_AXIS_TLAST)); + end + + // If TKEEP is low, TSTRB must be low as well + always @(*) + if (S_AXI_ARESETN && M_AXIS_TVALID) + assert(((~M_AXIS_TKEEP) & M_AXIS_TSTRB) == 0); + + // Our output requirement is that if TLAST is ever low, TKEEP must + // be all ones + always @(*) + if (S_AXI_ARESETN && M_AXIS_TVALID && !M_AXIS_TLAST) + assert(&M_AXIS_TKEEP); + + always @(*) + if (S_AXI_ARESETN && OPT_LOWPOWER && !M_AXIS_TVALID) + begin + assert(M_AXIS_TDATA == 0); + assert(M_AXIS_TSTRB == 0); + assert(M_AXIS_TKEEP == 0); + assert(M_AXIS_TLAST == 0); + end + + // TLAST requires at least one beat + always @(*) + if (S_AXI_ARESETN && M_AXIS_TVALID && M_AXIS_TLAST) + assert(M_AXIS_TKEEP != 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Induction assertions for the middle + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + genvar gk; + + always @(*) + if (S_AXI_ARESETN) + assert(mid_fill == { 1'b0, $countones(mid_keep) }); + + always @(*) + if (S_AXI_ARESETN && mid_last) + assert(mid_fill > 0); + + // If TKEEP is low, TSTRB must be low as well + always @(*) + if (S_AXI_ARESETN) + begin + if (mid_fill > 0) + begin + assert(((~mid_keep) & mid_strb) == 0); + end else begin + // assert(mid_data == 0); + assert(mid_strb == 0); + end + end + + // Our output requirement is that if TLAST is ever low, TKEEP must + // be all ones + generate for(gk=1; gk<DW/8; gk=gk+1) + begin + always @(*) + if (S_AXI_ARESETN && mid_fill > 0 && mid_keep[gk]) + assert(&mid_keep[gk-1:0]); + end endgenerate + + always @(*) + if (S_AXI_ARESETN && OPT_LOWPOWER && !M_AXIS_TVALID) + begin + assert(M_AXIS_TDATA == 0); + assert(M_AXIS_TSTRB == 0); + assert(M_AXIS_TKEEP == 0); + assert(M_AXIS_TLAST == 0); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Pick a byte of the given packet. Force that byte to have a known + // value. Prove that the same byte on the output has the same known + // value. + + (* anyconst *) reg [F_COUNT-1:0] fc_count; + (* anyconst *) reg [8-1:0] fc_data; + (* anyconst *) reg fc_strb, fc_last; + wire [F_COUNT:0] f_chk_count; + + // Assume the special input + // {{{ + generate for(gk=0; gk<DW/8; gk=gk+1) + begin + if (gk == 0) + begin + always @(*) + if (S_AXIS_TVALID && S_AXIS_TKEEP[0] + && f_icount == fc_count) + begin + assume(fc_data == S_AXIS_TDATA[7:0]); + assume(fc_strb == S_AXIS_TSTRB[0]); + if (fc_last) + begin + assume(S_AXIS_TKEEP[DW/8-1:1] == 0); + assume(S_AXIS_TLAST); + end else if (S_AXIS_TLAST) + assume(S_AXIS_TKEEP[DW/8-1:1] != 0); + end + end else begin + + always @(*) + if (S_AXIS_TKEEP[gk] && (f_icount + // Verilator lint_off WIDTH + + $countones(S_AXIS_TKEEP[gk-1:0]) == fc_count)) + // Verilator lint_on WIDTH + begin + assume(S_AXIS_TDATA[gk*8 +: 8] == fc_data); + assume(S_AXIS_TSTRB[gk] == fc_strb); + if (fc_last) + begin + if (gk < DW/8-1) + assume(S_AXIS_TKEEP[DW/8-1:gk+1] == 0); + assume(S_AXIS_TLAST); + end else if (gk < DW/8-1) + begin + if (S_AXIS_TLAST) + assume(S_AXIS_TKEEP[DW/8-1:gk+1] != 0); + end else + assume(!S_AXIS_TLAST); + end + end + end endgenerate + // }}} + + // Assert the special output + // {{{ + generate for(gk=0; gk<DW/8; gk=gk+1) + begin + if (gk == 0) + begin + always @(*) + if (S_AXI_ARESETN && M_AXIS_TVALID && M_AXIS_TKEEP[0] + && f_ocount == fc_count) + begin + assert(M_AXIS_TDATA[7:0] == fc_data); + assert(M_AXIS_TSTRB[0] == fc_strb); + if (fc_last) + begin + assert(M_AXIS_TKEEP[DW/8-1:1] == 0); + assert(M_AXIS_TLAST); + end else if (M_AXIS_TLAST) + assert(M_AXIS_TKEEP[DW/8-1:1] != 0); + end + end else begin + + always @(*) + if (S_AXI_ARESETN && M_AXIS_TKEEP[gk] && + // Verilator lint_off WIDTH + (f_ocount + $countones(M_AXIS_TKEEP[gk-1:0]) + == fc_count)) + // Verilator lint_on WIDTH + begin + assert(M_AXIS_TDATA[gk*8 +: 8] == fc_data); + assert(M_AXIS_TSTRB[gk] == fc_strb); + if (fc_last) + begin + if (gk < DW/8-1) + assert(M_AXIS_TKEEP[DW/8-1:gk+1] == 0); + assert(M_AXIS_TLAST); + end else if (gk < DW/8-1) + begin + if (M_AXIS_TLAST) + assert(M_AXIS_TKEEP[DW/8-1:gk+1] != 0); + end else + assert(!M_AXIS_TLAST); + end + end + end endgenerate + // }}} + + // f_icount + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_icount <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + // Verilator lint_off WIDTH + f_icount <= f_icount + $countones(S_AXIS_TKEEP); + // Verilator lint_on WIDTH + // }}} + + // f_ocount + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_ocount <= 0; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + // Verilator lint_off WIDTH + f_ocount <= f_ocount + $countones(M_AXIS_TKEEP); + // Verilator lint_on WIDTH + // }}} + + // Induction assertion(s) on mid_* + // {{{ + always @(*) + // Verilator lint_off WIDTH + f_mcount = f_icount - mid_fill; + // Verilator lint_on WIDTH + + generate for(gk=0; gk<DW/8; gk=gk+1) + begin + if (gk == 0) + begin + always @(*) + if (S_AXI_ARESETN && mid_fill > 0 + && f_mcount == fc_count) + begin + assert(mid_data[7:0] == fc_data); + assert(mid_strb[0] == fc_strb); + assert(mid_keep[0]); + if (fc_last) + begin + assert(mid_fill == 1); + assert(mid_last); + end else if (mid_last) + assert(mid_fill > 1); + end else if (S_AXI_ARESETN && mid_fill == 0) + begin + assert(mid_data[7:0] == 8'h00); + assert(!mid_strb[0]); + assert(!mid_keep[0]); + end + end else begin + + always @(*) + if (S_AXI_ARESETN && mid_fill > gk + && (f_mcount + gk == fc_count)) + begin + assert(mid_data[gk*8 +: 8] == fc_data); + assert(mid_strb[gk] == fc_strb); + assert(mid_keep[gk]); + if (fc_last) + begin + assert(mid_fill == gk + 1); + assert(mid_last); + end else if (gk < DW/8-1) + begin + if (mid_last) + assert(mid_fill > gk + 1); + end else + assert(!mid_last); + end else if (S_AXI_ARESETN && mid_fill <= gk) + begin + assert(mid_data[gk*8 +: 8] == 8'h00); + assert(!mid_strb[gk]); + assert(!mid_keep[gk]); + end + end + end endgenerate + // }}} + + // Relate icount to ocount + // {{{ + // Verilator lint_off WIDTH + assign f_chk_count = f_ocount + mid_fill + $countones(M_AXIS_TKEEP); + // Verilator lint_on WIDTH + + always @(*) + if (S_AXI_ARESETN) + begin + assume(f_chk_count[F_COUNT] == 1'b0); + assert(f_icount == f_chk_count[F_COUNT-1:0]); + end + // }}} + + // f_ibeat + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_ibeat <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + begin + // Verilator lint_off WIDTH + f_ibeat <= f_ibeat + $countones(S_AXIS_TKEEP); + // Verilator lint_on WIDTH + if (M_AXIS_TLAST) + f_ibeat <= 0; + end + // }}} + + // f_obeat + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_obeat <= 0; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + begin + // Verilator lint_off WIDTH + f_obeat <= f_obeat + $countones(M_AXIS_TKEEP); + // Verilator lint_on WIDTH + if (M_AXIS_TLAST) + f_obeat <= 0; + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + cover(M_AXIS_TVALID && M_AXIS_TREADY && M_AXIS_TLAST + && $past(M_AXIS_TVALID&& M_AXIS_TREADY&& M_AXIS_TLAST)); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + cover(M_AXIS_TVALID && !M_AXIS_TLAST + && $past(M_AXIS_TVALID&& M_AXIS_TREADY&& M_AXIS_TLAST)); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + cover(M_AXIS_TVALID && M_AXIS_TLAST + && $past(M_AXIS_TVALID&& M_AXIS_TREADY&&!M_AXIS_TLAST)); + + // }}} + + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_formal; + assign unused_formal = &{ 1'b0, f_ibeat, f_obeat, f_icount, f_ocount }; + // Verilator lint_on UNUSED + // }}} +`endif // FORMAL +// }}} +endmodule diff --git a/rtl/wb2axip/axisrandom.v b/rtl/wb2axip/axisrandom.v new file mode 100644 index 0000000..43d91f9 --- /dev/null +++ b/rtl/wb2axip/axisrandom.v @@ -0,0 +1,136 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisrandom +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: An AXI-stream based pseudorandom noise generator +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// +`default_nettype none +// +module axisrandom #( + // {{{ + localparam C_AXIS_DATA_WIDTH = 32 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + output reg M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA + // }}} + ); + + localparam INITIAL_FILL = { 1'b1, {(C_AXIS_DATA_WIDTH-1){1'b0}} }; + localparam LGPOLY = 31; + localparam [LGPOLY-1:0] CORE_POLY = { 31'h00_00_20_01 }; + localparam [C_AXIS_DATA_WIDTH-1:0] POLY + = { CORE_POLY, {(C_AXIS_DATA_WIDTH-31){1'b0}} }; + + // M_AXIS_TVALID + // {{{ + initial M_AXIS_TVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXIS_TVALID <= 1'b0; + else + M_AXIS_TVALID <= 1'b1; + // }}} + + // M_AXIS_TDATA + // {{{ + // + // Note--this setup is *FAR* from cryptographically random. + // + initial M_AXIS_TDATA = INITIAL_FILL; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXIS_TDATA <= INITIAL_FILL; + else if (M_AXIS_TREADY) + begin + M_AXIS_TDATA <= M_AXIS_TDATA >> 1; + M_AXIS_TDATA[C_AXIS_DATA_WIDTH-1] <= ^(M_AXIS_TDATA & POLY); + end + // }}} + + // Verilator lint_off UNUSED + // {{{ + wire unused; + assign unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used in verfiying this core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + // Make certain this polynomial will never degenerate, and so that + // this random number stream will go on for ever--eventually repeating + // after 2^LGPOLY-1 (hopefully) elements. + always @(*) + assert(M_AXIS_TDATA[C_AXIS_DATA_WIDTH-1 + :C_AXIS_DATA_WIDTH-LGPOLY] != 0); + + // AXI stream has only one significant property + // {{{ + // Here we'll modify it slightly for our purposes + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + assert(!M_AXIS_TVALID); + else begin + assert(M_AXIS_TVALID); + if ($past(M_AXIS_TVALID && !M_AXIS_TREADY)) + // Normally I'd assesrt M_AXIS_TVALID here, not above, + // but this core *ALWAYS* produces data + assert($stable(M_AXIS_TDATA)); + else if ($past(M_AXIS_TVALID)) + // Insist that the data always changes otherwise + assert($changed(M_AXIS_TDATA)); + end + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axissafety.v b/rtl/wb2axip/axissafety.v new file mode 100644 index 0000000..14a194d --- /dev/null +++ b/rtl/wb2axip/axissafety.v @@ -0,0 +1,677 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axissafety.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A firewall for the AXI-stream protocol. Only handles TVALID, +// TREADY, TDATA, TLAST and TUSER signals. +// +// This module is designed to be a "bump in the stream" of the stream +// protocol--a bump that can be used to detect AXI stream errors. +// Specifically, this firewall can detect one of three errors: +// +// 1. CHANGE The stream is stalled and something changes. This +// could easily be caused by a stream overflow. +// 2. PACKET LENGTH If OPT_PACKET_LENGTH != 0, then all stream +// packets are assumed to be OPT_PACKET_LENGTH in length. +// Packets that are not OPT_PACKET_LENGTH in length will +// generate a fault. +// +// This core cannot handle dynamic packet lengths, but +// should do just fine with detecting errors in fixed +// length packets--such as an FFT might use. +// 3. TOO MANY STALLS +// If OPT_MAX_STALL != 0, and the master stalls an input more than +// OPT_MAX_STALL cycles, then a fault will be declared. +// +// 4. (Sorry--I haven't implemented a video firewall. Video images +// having TUSER set to start of frame will not be guaranteed +// video stream compliant, since TUSER might still be set to the +// wrong number.) +// +// If a fault is detected, o_fault will be set true. This is designed so +// that you can trigger an ILA off of a protocol error, and then see what +// caused the fault. +// +// If OPT_SELF_RESET is set, then o_fault will be self clearing. Once +// a fault is detected, the current packet will be flushed, and the +// firewall will become idle (holding TREADY high) until S_AXIS_TVALID +// and S_AXIS_TLAST--marking the end of a broken packet. This will +// resynchronize the core, following which packets may pass through again. +// +// If you aren't using TLAST, set it to one. +// +// If you aren't using TUSER, set the width to 1 and the input to a +// constant zero. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// +// }}} +module axissafety #( + // {{{ +`ifdef FORMAL + parameter [0:0] F_OPT_FAULTLESS = 0, +`endif + parameter C_AXIS_DATA_WIDTH = 8, + parameter C_AXIS_USER_WIDTH = 1, + parameter OPT_MAX_STALL = 0, + parameter OPT_PACKET_LENGTH = 0, + parameter [0:0] OPT_SELF_RESET = 0 + // }}} + ) ( + // {{{ + output reg o_fault, + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI-stream slave (incoming) port + // {{{ + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire S_AXIS_TLAST, // = 1, + input wire [C_AXIS_USER_WIDTH-1:0] S_AXIS_TUSER, // = 0, + // }}} + // AXI-stream master (outgoing) port + // {{{ + output reg M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, + output reg M_AXIS_TLAST, + output reg [C_AXIS_USER_WIDTH-1:0] M_AXIS_TUSER + // }}} + // }}} + ); + + // Parameter/register declarations + // {{{ + localparam LGPKTLEN = $clog2(OPT_PACKET_LENGTH+1); + localparam LGSTALLCOUNT = $clog2(OPT_MAX_STALL+1); + reg skdr_valid, skd_valid, skd_ready; + + reg [C_AXIS_DATA_WIDTH+1+C_AXIS_USER_WIDTH-1:0] idata,skdr_data; + reg [C_AXIS_DATA_WIDTH-1:0] skd_data; + reg [C_AXIS_USER_WIDTH-1:0] skd_user; + reg skd_last; + + reg r_stalled; + reg packet_fault, change_fault, stall_fault; + reg [C_AXIS_DATA_WIDTH+1+C_AXIS_USER_WIDTH-1:0] past_data; + + reg [LGSTALLCOUNT-1:0] stall_count; + reg [LGPKTLEN-1:0] s_packet_counter; + reg [LGPKTLEN-1:0] m_packet_count; + + reg m_end_of_packet, m_idle; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Implement a skid buffer with no assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + idata = { S_AXIS_TUSER, S_AXIS_TLAST, S_AXIS_TDATA }; + + initial skdr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + skdr_valid <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY && (skd_valid && !skd_ready)) + skdr_valid <= 1; + else if (skd_ready) + skdr_valid <= 0; + + initial skdr_data = 0; // Allow data to be optimized away if always clr + always @(posedge S_AXI_ACLK) + if (S_AXIS_TVALID && S_AXIS_TREADY) + skdr_data <= idata; + + always @(*) + if (S_AXIS_TREADY) + { skd_user, skd_last, skd_data } = idata; + else + { skd_user, skd_last, skd_data } = skdr_data; + + always @(*) + skd_valid = S_AXIS_TVALID || skdr_valid; + + assign S_AXIS_TREADY = !skdr_valid; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Fault detection + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // r_stalled + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_stalled <= 0; + else + r_stalled <= (S_AXIS_TVALID && !S_AXIS_TREADY); + // }}} + + // past_data + // {{{ + always @(posedge S_AXI_ACLK) + past_data <= idata; + // }}} + + always @(*) + change_fault = (r_stalled && (past_data != idata)); + + // packet_fault + // {{{ + generate if (OPT_PACKET_LENGTH != 0) + begin : S_PACKET_COUNT + // {{{ + + // s_packet_counter + // {{{ + initial s_packet_counter = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_packet_counter <= 0; + else if (o_fault) + s_packet_counter <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + begin + s_packet_counter <= s_packet_counter + 1; + if (S_AXIS_TLAST) + s_packet_counter <= 0; + end + // }}} + + // packet_fault + // {{{ + always @(*) + begin + packet_fault = (S_AXIS_TLAST + != (s_packet_counter == OPT_PACKET_LENGTH-1)); + if (!S_AXIS_TVALID) + packet_fault = 0; + end + // }}} + // }}} + end else begin + // {{{ + always @(*) + packet_fault = 1'b0; + always @(*) + s_packet_counter = 0; + + // Verilator lint_off UNUSED + wire unused_pkt_counter; + assign unused_pkt_counter = &{ 1'b0, s_packet_counter }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + // }}} + + // stall_fault + // {{{ + generate if (OPT_MAX_STALL != 0) + begin : S_STALL_COUNT + // {{{ + + initial stall_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + stall_count <= 0; + else if (!S_AXIS_TVALID || S_AXIS_TREADY) + stall_count <= 0; + else + stall_count <= stall_count + 1; + + always @(*) + stall_fault = (stall_count > OPT_MAX_STALL); + // }}} + end else begin + // {{{ + always @(*) + stall_fault = 0; + + always @(*) + stall_count = 0; + // }}} + end endgenerate + // }}} + + // o_fault + // {{{ + initial o_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + o_fault <= 0; + else if (!o_fault) + o_fault <= change_fault || stall_fault || packet_fault; + else if (OPT_SELF_RESET) + begin + if (skd_last && skd_ready && m_idle) + o_fault <= 1'b0; + end + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The outgoing AXI-stream port + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // skd_ready + // {{{ + always @(*) + if (!o_fault && !change_fault && !stall_fault && !packet_fault) + skd_ready = !M_AXIS_TVALID || M_AXIS_TREADY; + else if (!o_fault) + skd_ready = 1'b0; + else + skd_ready = 1'b1; + // }}} + + // m_end_of_packet, m_packet_count + // {{{ + generate if (OPT_PACKET_LENGTH != 0) + begin + // {{{ + initial m_packet_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_packet_count <= 0; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + begin + m_packet_count <= m_packet_count + 1; + if (M_AXIS_TLAST) + m_packet_count <= 0; + end + + // initial m_end_of_packet = 0; + // always @(posedge S_AXI_ACLK) + // if (!S_AXI_ARESETN) + // m_end_of_packet <= 0; + // else if (M_AXIS_TVALID && M_AXIS_TREADY) + // m_end_of_packet <= (m_packet_count >= (OPT_PACKET_LENGTH-3)); + always @(*) + begin + m_end_of_packet = (m_packet_count == (OPT_PACKET_LENGTH-1)); + if (M_AXIS_TVALID) + m_end_of_packet = (m_packet_count == (OPT_PACKET_LENGTH-2)); + end + // }}} + end else begin + // {{{ + always @(*) + m_end_of_packet = 1'b1; + always @(*) + m_packet_count = 0; + // Verilator lint_off UNUSED + wire unused_mpkt_counter; + assign unused_mpkt_counter = &{ 1'b0, m_packet_count }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + + initial m_idle = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + m_idle <= 1; + else if (!M_AXIS_TVALID || M_AXIS_TREADY) + begin + if (M_AXIS_TVALID && M_AXIS_TLAST) + m_idle <= o_fault || (!skd_valid || !skd_ready); + else if (m_idle && !o_fault) + m_idle <= (!skd_valid || !skd_ready); + end + // }}} + + // M_AXIS_TVALID + // {{{ + initial M_AXIS_TVALID = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXIS_TVALID <= 1'b0; + else if (!M_AXIS_TVALID || M_AXIS_TREADY) + begin + M_AXIS_TVALID <= 1'b0; + if (!o_fault && skd_ready) + M_AXIS_TVALID <= skd_valid; + else if (o_fault) + begin + if (m_idle) + M_AXIS_TVALID <= 1'b0; + else if (!M_AXIS_TVALID || M_AXIS_TLAST) + M_AXIS_TVALID <= 1'b0; + else + M_AXIS_TVALID <= 1'b1; + end + end + // }}} + + // M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA + // {{{ + initial { M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA } <= 0; + always @(posedge S_AXI_ACLK) + begin + if (!M_AXIS_TVALID || M_AXIS_TREADY) + begin + if (o_fault || change_fault || packet_fault) + begin + { M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA } <= 0; + // if (!M_AXIS_TLAST) + // M_AXIS_TLAST <= m_end_of_packet; + end else + { M_AXIS_TUSER, M_AXIS_TLAST, M_AXIS_TDATA } + <= { skd_user, skd_last, skd_data }; + + if (OPT_PACKET_LENGTH != 0) + M_AXIS_TLAST <= m_end_of_packet; + else if (o_fault && !m_idle) + M_AXIS_TLAST <= 1'b1; + end + + if (!S_AXI_ARESETN) + M_AXIS_TLAST <= 0; + end + // }}} + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused = &{ 1'b0 }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + reg [LGPKTLEN-1:0] fm_packet_counter; + reg [LGSTALLCOUNT-1:0] fm_stall_count; + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + // Stability + // {{{ + always @(posedge S_AXI_ACLK) + if (!f_past_valid || !$past(S_AXI_ARESETN)) + assert(!M_AXIS_TVALID); + else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY)) + begin + assert(M_AXIS_TVALID); + assert($stable(M_AXIS_TDATA)); + assert($stable(M_AXIS_TLAST)); + assert($stable(M_AXIS_TUSER)); + end + // }}} + + // Packet counter, assertions on the output + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + fm_packet_counter <= 0; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + begin + fm_packet_counter <= fm_packet_counter + 1; + if (M_AXIS_TLAST) + fm_packet_counter <= 0; + end + + always @(*) + if (S_AXI_ARESETN && OPT_PACKET_LENGTH != 0) + begin + assert(fm_packet_counter < OPT_PACKET_LENGTH); + assert(M_AXIS_TLAST == (fm_packet_counter == OPT_PACKET_LENGTH-1)); + + assert(m_packet_count == fm_packet_counter); + if (!o_fault) + begin + if (skdr_valid && skd_last) + begin + assert(s_packet_counter == 0); + assert(m_packet_count == OPT_PACKET_LENGTH-2); + end else if (M_AXIS_TVALID && M_AXIS_TLAST) + begin + assert(s_packet_counter == (skdr_valid ? 1:0)); + assert(m_packet_count == OPT_PACKET_LENGTH-1); + end else + assert(s_packet_counter == m_packet_count + + (skdr_valid ? 1:0) + + (M_AXIS_TVALID ? 1:0)); + end + end + + always @(*) + if (S_AXI_ARESETN && !o_fault && OPT_PACKET_LENGTH > 0) + assert(s_packet_counter < OPT_PACKET_LENGTH); + + always @(*) + if (S_AXI_ARESETN && OPT_PACKET_LENGTH > 0) + assert(m_idle == (m_packet_count == 0 && !M_AXIS_TVALID)); + // }}} + + // Input stall counting + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + fm_stall_count <= 0; + else if (!S_AXIS_TVALID || S_AXIS_TREADY) + fm_stall_count <= 0; + + always @(*) + if (S_AXI_ARESETN && OPT_MAX_STALL > 0) + assert(fm_stall_count < OPT_MAX_STALL); + // }}} + + generate if (F_OPT_FAULTLESS) + begin + // {{{ + // Stall and stability properties + // {{{ + always @(posedge S_AXI_ACLK) + if (!f_past_valid || !$past(S_AXI_ARESETN)) + assume(!S_AXIS_TVALID); + else if ($past(S_AXIS_TVALID && !S_AXIS_TREADY)) + begin + assume(S_AXIS_TVALID); + assume($stable(S_AXIS_TDATA)); + assume($stable(S_AXIS_TLAST)); + assume($stable(S_AXIS_TUSER)); + end + // }}} + + // f_packet_counter + // {{{ + if (OPT_PACKET_LENGTH != 0) + begin + // {{{ + reg [LGPKTLEN-1:0] fs_packet_counter; + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + fs_packet_counter <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + begin + fs_packet_counter <= fs_packet_counter + 1; + if (S_AXIS_TLAST) + fs_packet_counter <= 0; + end + + always @(*) + if (S_AXI_ARESETN) + begin + assert(s_packet_counter == fs_packet_counter); + if (skdr_valid && skd_last) + assert(s_packet_counter == 0); + else if (M_AXIS_TVALID && M_AXIS_TLAST) + assert(s_packet_counter == (skdr_valid ? 1:0)); + else // if (!M_AXIS_TVALID && !M_AXIS_TLAST) + assert(s_packet_counter + == fm_packet_counter + + (skdr_valid ? 1:0) + + (M_AXIS_TVALID ? 1:0)); + end + + always @(*) + if (S_AXI_ARESETN && S_AXIS_TVALID) + assume(S_AXIS_TLAST == (fs_packet_counter == OPT_PACKET_LENGTH-1)); + // }}} + end + // }}} + + // f_stall_count + // {{{ + if (OPT_MAX_STALL != 0) + begin + // {{{ + reg [LGSTALLCOUNT-1:0] f_stall_count; + + initial stall_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_stall_count <= 0; + else if (!S_AXIS_TVALID || S_AXIS_TREADY) + f_stall_count <= 0; + else + f_stall_count <= f_stall_count + 1; + + always @(*) + if (S_AXI_ARESETN) + assert(stall_count == f_stall_count); + + always @(*) + assume(f_stall_count <= OPT_MAX_STALL); + // }}} + end + // }}} + + always @(*) + assert(!o_fault); + // }}} + end endgenerate + + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + generate if (F_OPT_FAULTLESS) + begin + end else begin + + reg cvr_stall_fault, cvr_packet_fault, cvr_change_fault, + cvr_last; + + initial cvr_last = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_last <= 1'b0; + else if (o_fault && $stable(o_fault) + && $rose(M_AXIS_TVALID && M_AXIS_TLAST)) + cvr_last <= 1'b1; + + initial cvr_change_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_change_fault <= 1'b0; + else if (!o_fault && change_fault && !packet_fault && !stall_fault) + cvr_change_fault <= 1'b1; + else if (!o_fault) + cvr_change_fault <= 1'b0; + + if (OPT_PACKET_LENGTH > 0) + begin + initial cvr_packet_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_packet_fault <= 1'b0; + else if (!o_fault && !change_fault && packet_fault && !stall_fault) + cvr_packet_fault <= 1'b1; + else if (!o_fault) + cvr_packet_fault <= 1'b0; + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + begin + cover($fell(o_fault) && cvr_packet_fault); + cover($fell(o_fault) && cvr_packet_fault && cvr_last); + end + end + + if (OPT_MAX_STALL > 0) + begin + initial cvr_stall_fault = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + cvr_stall_fault <= 1'b0; + else if (!o_fault && !change_fault && !packet_fault && stall_fault) + cvr_stall_fault <= 1'b1; + else if (!o_fault) + cvr_stall_fault <= 1'b0; + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + begin + cover($fell(o_fault) && cvr_stall_fault); + cover($fell(o_fault) && cvr_stall_fault && cvr_last); + end + end + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && $past(S_AXI_ARESETN)) + begin + cover($fell(o_fault)); + cover($fell(o_fault) && cvr_change_fault); + cover($fell(o_fault) && cvr_change_fault && cvr_last); + end + + end endgenerate + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axisswitch.v b/rtl/wb2axip/axisswitch.v new file mode 100644 index 0000000..bcae4a3 --- /dev/null +++ b/rtl/wb2axip/axisswitch.v @@ -0,0 +1,631 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axisswitch.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Switch from among several AXI streams based upon an AXI-lite +// controlled index. All streams must have the same width. +// The switch will use TLAST to guarantee that it will not change +// mid-packet. If TLAST is unused for a particular input, simply set it +// to 1'b1. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axisswitch #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have a single configuration words. + parameter C_AXI_ADDR_WIDTH = 2, + localparam C_AXI_DATA_WIDTH = 32, + // + parameter NUM_STREAMS = 4, + parameter C_AXIS_DATA_WIDTH = 32, + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // AXI-Lite control + // {{{ + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP, + // }}} + // AXI stream inputs to be switched + // {{{ + input wire [NUM_STREAMS-1:0] S_AXIS_TVALID, + output wire [NUM_STREAMS-1:0] S_AXIS_TREADY, + input wire [NUM_STREAMS*C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire [NUM_STREAMS-1:0] S_AXIS_TLAST, + // }}} + // AXI stream output result + // {{{ + output reg M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, + output reg M_AXIS_TLAST + // }}} + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + localparam LGNS = $clog2(NUM_STREAMS); + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + + wire i_reset = !S_AXI_ARESETN; + + wire axil_write_ready; + wire [0:0] awskd_addr; // UNUSED + // + wire [C_AXI_DATA_WIDTH-1:0] wskd_data; + wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire axil_read_ready; + wire [0:0] arskd_addr; // UNUSED + reg [C_AXI_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + + reg [LGNS-1:0] r_index; + wire [31:0] wskd_index; + + genvar gk; + reg [NUM_STREAMS-1:0] skd_switch_ready; + reg [LGNS-1:0] switch_index; + wire [C_AXIS_DATA_WIDTH-1:0] skd_data [0:NUM_STREAMS-1]; + wire [NUM_STREAMS-1:0] skd_valid, skd_last; + reg mid_packet, r_mid_packet; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write signaling + // + // {{{ + + wire awskd_valid, wskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), .DW(1)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data(1'b0), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXI_BVALID || S_AXI_BREADY); + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXI_BREADY) + axil_bvalid <= 0; + + assign S_AXI_BVALID = axil_bvalid; + assign S_AXI_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + + wire arskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(1)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data(1'b0), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXI_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXI_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXI_RVALID = axil_read_valid; + assign S_AXI_RDATA = axil_read_data; + assign S_AXI_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // apply_wstrb(old_data, new_data, write_strobes) + // r_index, (wskd_index) + // {{{ + assign wskd_index = apply_wstrb( + { {(C_AXI_DATA_WIDTH-LGNS){1'b0}}, r_index }, + wskd_data, wskd_strb); + + // r_index + initial r_index = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + r_index <= 0; + else if (axil_write_ready) + r_index <= wskd_index[LGNS-1:0]; + // }}} + + // axil_read_data + // {{{ + initial axil_read_data = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_read_data <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + axil_read_data <= 0; + axil_read_data[LGNS-1:0] <= r_index; + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + // }}} + + // function apply_wstrb + // {{{ + function [C_AXI_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXI_DATA_WIDTH-1:0] prior_data; + input [C_AXI_DATA_WIDTH-1:0] new_data; + input [C_AXI_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The actual AXI switch + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Place a skidbuffer on every incoming stream input + // {{{ + generate for(gk=0; gk<NUM_STREAMS; gk=gk+1) + begin + skidbuffer #( + // {{{ + .OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXIS_DATA_WIDTH+1) + // }}} + ) skdswitch(// + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIS_TVALID[gk]),.o_ready(S_AXIS_TREADY[gk]), + .i_data({ S_AXIS_TDATA[gk*C_AXIS_DATA_WIDTH + +: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[gk] }), + .o_valid(skd_valid[gk]), .i_ready(skd_switch_ready[gk]), + .o_data({ skd_data[gk], skd_last[gk] }) + // }}} + ); + + + end endgenerate + // }}} + + // skd_switch_ready + // {{{ + always @(*) + begin + skd_switch_ready = (1<<switch_index); + if (M_AXIS_TVALID && !M_AXIS_TREADY) + skd_switch_ready = 0; + if (!mid_packet && r_index != switch_index) + skd_switch_ready = 0; + end + // }}} + + // mid_packet -- are we currently within a packet or not + // {{{ + initial r_mid_packet = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_mid_packet <= 0; + else if (M_AXIS_TVALID) + r_mid_packet <= !M_AXIS_TLAST; + + always @(*) + if (M_AXIS_TVALID) + mid_packet = !M_AXIS_TLAST; + else + mid_packet = r_mid_packet; + // }}} + + // switch_index -- the current index of the skidbuffer switch + // {{{ + initial switch_index = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + switch_index <= 0; + else if (!mid_packet) + switch_index <= r_index; + // }}} + + // M_AXIS_TVALID + // {{{ + initial M_AXIS_TVALID = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + M_AXIS_TVALID <= 1'b0; + else if (!M_AXIS_TVALID || M_AXIS_TREADY) + M_AXIS_TVALID <= |(skd_valid & skd_switch_ready); + // }}} + + // M_AXIS_TDATA, M_AXIS_TLAST + // {{{ + initial M_AXIS_TDATA = 0; + initial M_AXIS_TLAST = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + begin + M_AXIS_TDATA <= 0; + M_AXIS_TLAST <= 0; + end else if (!M_AXIS_TVALID || M_AXIS_TREADY) + begin + M_AXIS_TDATA <= skd_data[switch_index]; + M_AXIS_TLAST <= skd_last[switch_index]; + + if (OPT_LOWPOWER && (skd_valid | skd_switch_ready) == 0) + begin + M_AXIS_TDATA <= 0; + M_AXIS_TLAST <= 0; + end + end + // }}} + + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_ARADDR[ADDRLSB-1:0], awskd_addr, arskd_addr, + S_AXI_AWADDR[ADDRLSB-1:0], wskd_index }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used in verfiying this core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3), + .F_OPT_COVER_BURST(4) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0) + +(S_AXI_AWREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0) + +(S_AXI_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0) + +(S_AXI_ARREADY ? 0:1)); + end + + always @(*) + assert(S_AXI_RDATA[C_AXI_DATA_WIDTH-1:LGNS] == 0); + + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN + && axil_read_ready)) + begin + assert(S_AXI_RVALID); + assert(S_AXI_RDATA[LGNS-1:0] == $past(r_index)); + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + if (OPT_LOWPOWER && !S_AXI_RVALID) + assert(S_AXI_RDATA == 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI Stream properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate for(gk=0; gk<NUM_STREAMS; gk=gk+1) + begin : S_STREAM_ASSUMPTIONS + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + assume(S_AXIS_TVALID[gk] == 0); + else if ($past(S_AXIS_TVALID[gk] && !S_AXIS_TREADY[gk])) + begin + assume(S_AXIS_TVALID[gk]); + assume($stable(S_AXIS_TDATA[gk*C_AXIS_DATA_WIDTH +: C_AXIS_DATA_WIDTH])); + assume($stable(S_AXIS_TLAST[gk])); + end + end endgenerate + + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + assert(!M_AXIS_TVALID); + else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY)) + begin + assert(M_AXIS_TVALID); + assert($stable(M_AXIS_TDATA)); + assert($stable(M_AXIS_TLAST)); + end + + always @(*) + if (OPT_LOWPOWER && !M_AXIS_TVALID) + begin + assert(M_AXIS_TDATA == 0); + assert(M_AXIS_TLAST == 0); + end + + (* anyconst *) reg [LGNS-1:0] f_const_index; + (* anyconst *) reg [C_AXIS_DATA_WIDTH-1:0] f_never_data; + reg [LGNS-1:0] f_this_index; + reg [3:0] f_count, f_recount; + reg f_accepts, f_delivers; + + always @(*) + assume(f_const_index < NUM_STREAMS); + + // f_this_index + // {{{ + initial f_this_index = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_this_index <= 1'b0; + else if (!M_AXIS_TVALID || M_AXIS_TREADY) + f_this_index <= switch_index; + + always @(*) + assert(f_this_index < NUM_STREAMS); + + always @(*) + assert(switch_index < NUM_STREAMS); + // }}} + + // f_count + // {{{ + always @(*) + begin + f_accepts = S_AXIS_TVALID[f_const_index] + && S_AXIS_TREADY[f_const_index]; + f_delivers = M_AXIS_TVALID && M_AXIS_TREADY + && f_this_index == f_const_index; + end + + initial f_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_count <= 0; + else case( { f_accepts, f_delivers }) + 2'b01: f_count <= f_count - 1; + 2'b10: f_count <= f_count + 1; + default: begin end + endcase + // }}} + + // f_count induction + // {{{ + always @(*) + begin + f_recount = 0; + if (!S_AXIS_TREADY[f_const_index]) + f_recount = 1; + if (M_AXIS_TVALID && f_this_index == f_const_index) + f_recount = f_recount + 1; + + assert(f_recount == f_count); + end + // }}} + + // f_this_index induction + always @(*) + if (M_AXIS_TVALID && !M_AXIS_TLAST) + assert(f_this_index == switch_index); + + // assume != f_never_data + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXIS_TVALID[f_const_index]) + assume({ S_AXIS_TDATA[f_const_index * C_AXIS_DATA_WIDTH +: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[f_const_index] } != f_never_data); + // }}} + + // Never data induction + // {{{ + always @(*) + begin + if (skd_valid[f_const_index]) + assert({ skd_data[f_const_index], skd_last[f_const_index] } != f_never_data); + if (M_AXIS_TVALID && f_this_index == f_const_index) + assert({ M_AXIS_TDATA, M_AXIS_TLAST } != f_never_data); + end + // }}} + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // While there are already cover properties in the formal property + // set above, you'll probably still want to cover something + // application specific here + + // }}} +`endif + // }}} +endmodule diff --git a/rtl/wb2axip/axivcamera.v b/rtl/wb2axip/axivcamera.v new file mode 100644 index 0000000..d5de9ad --- /dev/null +++ b/rtl/wb2axip/axivcamera.v @@ -0,0 +1,1219 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axivcamera +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Reads a video frame from a source and writes the result to +// memory. +// +// Registers: +// {{{ +// 0: FBUF_CONTROL and status +// bit 0: START(1)/STOP(0) +// Command the core to start by writing a '1' to this bit. It +// will then remain busy/active until either you tell it to halt, +// or an error occurrs. +// bit 1: BUSY +// bit 2: ERR +// If the core receives a bus error, it assumes it has been +// inappropriately set up, sets this error bit and then halts. +// It will not start again until this bit is cleared. Only a +// write to the control register with the ERR bit set will clear +// it. (Don't do this unless you know what caused it to halt ...) +// bit 3: DIRTY +// If you update core parameters while it is running, the busy +// bit will be set. This bit is an indication that the current +// configuration doesn't necessarily match the one you are reading +// out. To clear DIRTY, deactivate the core, wait for it to be +// no longer busy, and then start it again. This will also start +// it under the new configuration so the two match. +// bits 15-8: FRAMES +// Indicates the number of frames you want to copy. Set this to +// 0 to continuously copy, or a finite number to grab only that +// many frames. +// +// As a feature, this isn't perhaps the most useful, since every +// frame will be written to the same location. A more useful +// approach would be to increase the desired number of lines to +// (NLINES * NFRAMES), and then to either leave this number at zero +// or set it to one. +// +// 2: FBUF_LINESTEP +// Controls the distance from one line to the next. This is the +// value added to the address of the beginning of the line to get +// to the beginning of the next line. This should nominally be +// equal to the number of bytes per line, although it doesn't +// need to be. +// +// Any attempt to set this value to zero will simply copy the +// number of data bytes per line (rounded down to the neareset +// word) into this value. This is a recommended approach to +// setting this value. +// +// 4: FBUF_LINEBYTES +// This is the number of data bytes necessary to capture all of +// the video data in a line. This value must be more than zero +// in order to activate the core. +// +// At present, this core can only handle a number of bytes aligned +// with the word size of the bus. To convert from bytes to words, +// round any fractional part upwards (i.e. ceil()). The core +// will naturally round down internally. +// +// 6: FBUF_NUMLINES +// The number of lines of active video data in a frame. This +// number must be greater than zero in order to activate and +// operate the core. +// +// 8: FBUF_ADDRESS +// The is the first address of video data in memory. Each frame +// will start reading from this address. +// +// 12: (reserved for the upper FBUF_ADDRESS) +// +// KNOWN ISSUES: +// +// Does not support interlacing (yet). (Interlacing is not on my "todo" +// list, so it might take a while to get said support if you need it.) +// +// Does not support unaligned addressing. The frame buffer address, and +// the line step, must all be word aligned. While nothing is stopping +// me from supporting unaligned addressing, it was such a pain to do the +// last time that I might just hold off until I have a paying customer +// who wants it. +// +// Line bytes that include a fraction of a word are rounded down and not +// up. +// }}} +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axivcamera #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ID_WIDTH = 1, + // + // We support five 32-bit AXI-lite registers, requiring 5-bits + // of AXI-lite addressing + localparam C_AXIL_ADDR_WIDTH = 4, + localparam C_AXIL_DATA_WIDTH = 32, + // + // The bottom ADDRLSB bits of any AXI address are subword bits + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3, + localparam AXILLSB = $clog2(C_AXIL_DATA_WIDTH)-3, + // + // OPT_LGMAXBURST + parameter OPT_LGMAXBURST = 8, + // + parameter [0:0] DEF_ACTIVE_ON_RESET = 0, + parameter [15:0] DEF_LINES_PER_FRAME = 1024, + parameter [16-ADDRLSB-1:0] DEF_WORDS_PER_LINE = (1280 * 32)/C_AXI_DATA_WIDTH, + // + // DEF_FRAMEADDR: the default AXI address of the frame buffer + // containing video memory. Unless OPT_UNALIGNED is set, this + // should be aligned so that DEF_FRAMEADDR[ADDRLSB-1:0] == 0. + parameter [C_AXI_ADDR_WIDTH-1:0] DEF_FRAMEADDR = 0, + // + // The (log-based two of the) size of the FIFO in words. + // I like to set this to the size of two AXI bursts, so that + // while one is being read out the second can be read in. Can + // also be set larger if desired. + parameter OPT_LGFIFO = OPT_LGMAXBURST+1, + localparam LGFIFO = (OPT_LGFIFO < OPT_LGMAXBURST+1) + ? OPT_LGMAXBURST+1 : OPT_LGFIFO, + // + // AXI_ID is the ID we will use for all of our AXI transactions + parameter AXI_ID = 0, + // + // OPT_IGNORE_HLAST + parameter [0:0] OPT_IGNORE_HLAST = 0, + // + // OPT_TUSER_IS_SOF. Xilinx and I chose different encodings. + // I encode TLAST == VLAST, and TUSER == HLAST (optional). + // Xilinx likes TLAST == HLAST and TUSER == SOF (start of frame) + // Set OPT_TUSER_IS_SOF to use Xilinx's encoding + parameter [0:0] OPT_TUSER_IS_SOF = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The incoming video stream data/pixel interface + // {{{ + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXIS_TDATA, + input wire /* VLAST */ S_AXIS_TLAST, + input wire /* HLAST */ S_AXIS_TUSER, + // }}} + // + // The control interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // + + // + // The AXI (full) write-only interface + // {{{ + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [7:0] M_AXI_AWLEN, + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, + output wire M_AXI_AWLOCK, + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP + // }}} + // }}} + ); + + // Core logic implementation + // {{{ + // Local parameter declarations + // {{{ + localparam [1:0] FBUF_CONTROL = 2'b00, + FBUF_FRAMEINFO = 2'b01, + FBUF_ADDRLO = 2'b10, + FBUF_ADDRHI = 2'b11; + + localparam CBIT_ACTIVE = 0, + CBIT_BUSY = 1, + CBIT_ERR = 2, + // CBIT_DIRTY = 3, + CBIT_LOST_SYNC = 4; + + localparam TMPLGMAXBURST=(LGFIFO-1 > OPT_LGMAXBURST) + ? OPT_LGMAXBURST : LGFIFO-1; + + localparam LGMAXBURST = (TMPLGMAXBURST+ADDRLSB > 12) + ? (12-ADDRLSB) : TMPLGMAXBURST; + // }}} + + wire i_clk = S_AXI_ACLK; + wire i_reset = !S_AXI_ARESETN; + + // Signal declarations + // {{{ + reg soft_reset, r_err, r_stopped, lost_sync; + reg cfg_active, cfg_zero_length, + cfg_continuous; + reg [C_AXI_ADDR_WIDTH-1:0] cfg_frame_addr; + reg [15:0] cfg_frame_lines, cfg_line_step; + reg [16-ADDRLSB-1:0] cfg_line_words; + + // FIFO signals + wire reset_fifo, write_to_fifo, + read_from_fifo; + wire [C_AXI_DATA_WIDTH-1:0] write_data, fifo_data; + wire [LGFIFO:0] fifo_fill; + wire fifo_full, fifo_empty, + fifo_vlast, fifo_hlast; + + wire awskd_valid, axil_write_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + // + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire arskd_valid, axil_read_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + reg [C_AXIL_DATA_WIDTH-1:0] w_status_word; + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_address, new_wideaddr; + wire [C_AXIL_DATA_WIDTH-1:0] new_cmdaddrlo, new_cmdaddrhi; + reg [C_AXIL_DATA_WIDTH-1:0] wide_config; + wire [C_AXIL_DATA_WIDTH-1:0] new_config; + + reg axi_awvalid, axi_wvalid, axi_wlast, + phantom_start, start_burst, + aw_none_outstanding; + reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; + reg [7:0] axi_awlen, next_awlen; + reg [8:0] wr_pending; + reg [15:0] aw_bursts_outstanding; + // + // reg vlast; + // reg [15:0] r_frame_lines, r_line_step; + reg [16-ADDRLSB-1:0] next_line_words; + + reg req_hlast, req_vlast, req_newline; + reg [15:0] req_nlines; + reg [7:0] req_nframes; + reg [16-ADDRLSB-1:0] req_line_words; + reg [C_AXI_ADDR_WIDTH:0] req_addr, req_line_addr, next_line_addr; + // + reg wr_hlast, wr_vlast; + reg [15:0] wr_lines; + reg [16-ADDRLSB-1:0] wr_line_beats; + // wire no_fifo__available; + // + reg [LGMAXBURST-1:0] till_boundary; + reg [LGFIFO:0] fifo_data_available; + reg fifo_bursts_available; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // + //////////////////////////////////////////////////////////////////////// + // + // This is mostly the skidbuffer logic, and handling of the VALID + // and READY signals for the AXI-lite control logic in the next + // section. + // {{{ + + // + // Write signaling + // + // {{{ + + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXIL_BVALID || S_AXIL_BREADY); + + initial axil_bvalid = 0; + always @(posedge i_clk) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXIL_BREADY) + axil_bvalid <= 0; + + assign S_AXIL_BVALID = axil_bvalid; + assign S_AXIL_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXIL_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge i_clk) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXIL_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXIL_RVALID = axil_read_valid; + assign S_AXIL_RDATA = axil_read_data; + assign S_AXIL_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite controlled logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // soft_reset, r_err + // {{{ + initial soft_reset = 1; + always @(posedge i_clk) + if (i_reset) + begin + soft_reset <= 1; + r_err <= 0; + end else if (r_stopped) + begin + + if (axil_write_ready && awskd_addr == FBUF_CONTROL + && wskd_strb[0] && wskd_data[CBIT_ERR]) + begin + r_err <= cfg_zero_length; + soft_reset <= 0; + end + + if (!r_err) + soft_reset <= 0; + + end else // if (!soft_reset) + begin + // Halt on any bus error. We'll require user intervention + // to start back up again + if ((M_AXI_BVALID && M_AXI_BREADY && M_AXI_BRESP[1]) + ||(req_addr[C_AXI_ADDR_WIDTH])) + begin + soft_reset <= 1; + r_err <= 1; + end + + // Halt on any user request + if (!cfg_active) + soft_reset <= 1; + end + // }}} + + // wide_* and new_* write setup registers + // {{{ + always @(*) + begin + wide_address = 0; + wide_address[C_AXI_ADDR_WIDTH-1:0] = cfg_frame_addr; + wide_address[ADDRLSB-1:0] = 0; + + wide_config = { cfg_frame_lines, cfg_line_words, + {(ADDRLSB){1'b0}} }; + end + + assign new_cmdaddrlo = apply_wstrb( + wide_address[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + + generate if (C_AXI_ADDR_WIDTH > 32) + begin : GEN_LARGE_AW + + assign new_cmdaddrhi = apply_wstrb( + wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + + end else begin : SINGLE_WORD_AW + + assign new_cmdaddrhi = 0; + + end endgenerate + + + wire [C_AXIL_DATA_WIDTH-1:0] new_control; + assign new_control = apply_wstrb(w_status_word, wskd_data, wskd_strb); + assign new_config = apply_wstrb(wide_config, wskd_data, wskd_strb); + + always @(*) + begin + new_wideaddr = wide_address; + + if (awskd_addr == FBUF_ADDRLO) + new_wideaddr[C_AXIL_DATA_WIDTH-1:0] = new_cmdaddrlo; + if (awskd_addr == FBUF_ADDRHI) + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] = new_cmdaddrhi; + new_wideaddr[ADDRLSB-1:0] = 0; + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + end + // }}} + + // Configuration registers (Write) + // {{{ + initial cfg_active = 0; + initial cfg_continuous = 0; + initial cfg_frame_addr = DEF_FRAMEADDR; + initial cfg_frame_addr[ADDRLSB-1:0] = 0; + initial cfg_line_words = DEF_WORDS_PER_LINE; + initial cfg_frame_lines = DEF_LINES_PER_FRAME; + initial cfg_zero_length = (DEF_WORDS_PER_LINE == 0) + ||(DEF_LINES_PER_FRAME == 0); + initial cfg_line_step = { DEF_WORDS_PER_LINE, {(ADDRLSB){1'b0}} }; + always @(posedge i_clk) + if (i_reset) + begin + cfg_active <= DEF_ACTIVE_ON_RESET; + cfg_frame_addr <= DEF_FRAMEADDR; + cfg_line_words <= DEF_WORDS_PER_LINE; + cfg_line_step <= { DEF_WORDS_PER_LINE, {(ADDRLSB){1'b0}} }; + cfg_frame_lines <= DEF_LINES_PER_FRAME; + cfg_zero_length <= (DEF_WORDS_PER_LINE==0) + ||(DEF_LINES_PER_FRAME == 0); + req_nframes <= 0; + cfg_continuous <= 0; + end else begin + if (phantom_start && req_vlast && req_hlast && req_nframes > 0) + req_nframes <= req_nframes - 1; + + if (axil_write_ready) + case(awskd_addr) + FBUF_CONTROL: begin + if (wskd_strb[0]) + cfg_active <= (cfg_active || r_stopped) + && wskd_data[CBIT_ACTIVE] + && (!r_err || wskd_data[CBIT_ERR]) + && (!cfg_zero_length); + + if (!cfg_active && r_stopped && wskd_strb[1]) + begin + req_nframes <= wskd_data[15:8]; + cfg_continuous <= (wskd_data[15:8] == 0); + end + + if (!cfg_active && r_stopped) + begin + if (new_control[31:16] == 0) + begin + cfg_line_step <= 0; + cfg_line_step[16-1:ADDRLSB] <= cfg_line_words; + end else + cfg_line_step <= new_control[31:16]; + end end + FBUF_FRAMEINFO: + if (!cfg_active && r_stopped) + begin + { cfg_frame_lines, cfg_line_words } + <= new_config[C_AXIL_DATA_WIDTH-1:ADDRLSB]; + cfg_zero_length <= (new_config[31:16] == 0) + ||(new_config[15:ADDRLSB] == 0); + end + FBUF_ADDRLO, FBUF_ADDRHI: if (!cfg_active && r_stopped) + cfg_frame_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + default: begin end + endcase + + if (M_AXI_BREADY && M_AXI_BVALID && M_AXI_BRESP[1]) + cfg_active <= 0; + if (req_addr[C_AXI_ADDR_WIDTH]) + cfg_active <= 0; + if (phantom_start && req_vlast && req_hlast && req_nframes <= 1 + && !cfg_continuous) + cfg_active <= 0; + + cfg_line_step[ADDRLSB-1:0] <= 0; + cfg_frame_addr[ADDRLSB-1:0] <= 0; + end + // }}} + + // AXI-Lite read register data + // {{{ + always @(*) + begin + w_status_word = 0; + w_status_word[31:16] = cfg_line_step; + w_status_word[15:8] = req_nframes; + w_status_word[CBIT_LOST_SYNC] = lost_sync; + // w_status_word[CBIT_DIRTY] = cfg_dirty; + w_status_word[CBIT_ERR] = r_err; + w_status_word[CBIT_BUSY] = !soft_reset; + w_status_word[CBIT_ACTIVE] = cfg_active || (!soft_reset || !r_stopped); + end + + always @(posedge i_clk) + if (!axil_read_valid || S_AXIL_RREADY) + begin + axil_read_data <= 0; + case(arskd_addr) + FBUF_CONTROL: axil_read_data <= w_status_word; + FBUF_FRAMEINFO: axil_read_data <= { cfg_frame_lines, + cfg_line_words, {(ADDRLSB){1'b0}} }; + FBUF_ADDRLO: axil_read_data <= wide_address[C_AXIL_DATA_WIDTH-1:0]; + FBUF_ADDRHI: axil_read_data <= wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + default axil_read_data <= 0; + endcase + end + // }}} + + // apply_wstrb function for applying wstrbs to register words + // {{{ + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The data FIFO section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + wire S_AXIS_SOF, S_AXIS_HLAST, S_AXIS_VLAST; + generate if (OPT_TUSER_IS_SOF) + begin : XILINX_SOF_LOCK + reg [15:0] axis_line; + reg axis_last_line; + + assign S_AXIS_HLAST = S_AXIS_TLAST; + assign S_AXIS_SOF = S_AXIS_TUSER; + + // Generate S_AXIS_VLAST from S_AXIS_SOF + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axis_line <= 0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + begin + if (S_AXIS_SOF) + axis_line <= 0; + else if (S_AXIS_HLAST) + axis_line <= axis_line + 1; + end + + always @(posedge S_AXI_ACLK) + axis_last_line <= (axis_line+1 >= cfg_frame_lines); + + assign S_AXIS_VLAST = axis_last_line && S_AXIS_HLAST; + + end else begin : VLAST_LOCK + + assign S_AXIS_VLAST = S_AXIS_TLAST; + + assign S_AXIS_HLAST = S_AXIS_TUSER; + + /* + initial S_AXIS_SOF = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + S_AXIS_SOF <= 1'b0; + else if (S_AXIS_TVALID && S_AXIS_TREADY) + S_AXIS_SOF <= S_AXIS_VLAST; + */ + assign S_AXIS_SOF = 1'b0; + + // Verilator lint_off UNUSED + wire unused_sof; + assign unused_sof = &{ 1'b0, S_AXIS_SOF }; + // Verilator lint_on UNUSED + end endgenerate + + // Read/write signaling, lost_sync detection + // {{{ + assign reset_fifo = (!cfg_active || r_stopped || lost_sync); + + assign write_to_fifo = S_AXIS_TVALID && !lost_sync && !fifo_full; + assign write_data = S_AXIS_TDATA; + assign read_from_fifo = (M_AXI_WVALID && M_AXI_WREADY); + assign S_AXIS_TREADY = 1'b1; + + initial lost_sync = 1; + always @(posedge S_AXI_ACLK) + begin + if (!S_AXI_ARESETN || !cfg_active || r_stopped) + lost_sync <= 0; + else if (M_AXI_WVALID && M_AXI_WREADY + &&((!OPT_IGNORE_HLAST&&(wr_hlast != fifo_hlast)) + || (!wr_hlast && fifo_vlast) + || (wr_vlast && wr_hlast && !fifo_vlast))) + // Here is where we might possibly notice we've lost sync + lost_sync <= 1; + + // lost_sync <= 1'b0; + end + // }}} + + generate if (LGFIFO > 0) + begin : GEN_SPACE_AVAILBLE + // Here's where we'll put the actual outgoing FIFO + // {{{ + reg [LGFIFO:0] next_data_available; + always @(*) + begin + next_data_available = fifo_data_available; + // Verilator lint_off WIDTH + if (phantom_start) + next_data_available = fifo_data_available - (M_AXI_AWLEN + 1); + // Verilator lint_on WIDTH + if (write_to_fifo) + next_data_available = next_data_available + 1; + end + + initial fifo_data_available = 0; + initial fifo_bursts_available = 0; + always @(posedge i_clk) + if (reset_fifo) + begin + fifo_data_available <= 0; + fifo_bursts_available <= 0; + end else if (phantom_start || write_to_fifo) + begin + fifo_data_available <= next_data_available; + fifo_bursts_available <= |next_data_available[LGFIFO:LGMAXBURST]; + end + + sfifo #(.BW(C_AXI_DATA_WIDTH+2), .LGFLEN(LGFIFO)) + sfifo(i_clk, reset_fifo, + write_to_fifo, { S_AXIS_VLAST, + (!OPT_IGNORE_HLAST && S_AXIS_HLAST),write_data}, + fifo_full, fifo_fill, + read_from_fifo, { fifo_vlast, fifo_hlast, + fifo_data }, fifo_empty); + // }}} + end else begin : NO_FIFO + // {{{ + // + // This isn't verified or tested. I'm not sure I'd expect this + // to work at all. + assign fifo_full = M_AXI_WVALID && !M_AXI_WREADY; + assign fifo_fill = 0; + assign fifo_empty = !S_AXIS_TVALID; + assign fifo_vlast = S_AXIS_VLAST; + assign fifo_hlast = (!OPT_IGNORE_HLAST && S_AXIS_HLAST); + + assign fifo_data = write_data; + // }}} + end endgenerate + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outoing frame address counting + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg w_frame_needs_alignment, frame_needs_alignment, + line_needs_alignment, + line_multiple_bursts, req_needs_alignment, req_multiple_bursts; + + // frame_needs_alignment + // {{{ + always @(*) + begin + w_frame_needs_alignment = 0; + if (|cfg_line_words[15-ADDRLSB:LGMAXBURST]) + w_frame_needs_alignment = 1; + if (~cfg_frame_addr[ADDRLSB +: LGMAXBURST] + < cfg_line_words[LGMAXBURST-1:0]) + w_frame_needs_alignment = 1; + if (cfg_frame_addr[ADDRLSB +: LGMAXBURST] == 0) + w_frame_needs_alignment = 0; + end + + always @(posedge i_clk) + if (!cfg_active && r_stopped) + frame_needs_alignment <= w_frame_needs_alignment; + // }}} + + // line_needs_alignment + // {{{ + always @(posedge i_clk) + if (r_stopped) + line_multiple_bursts <= (cfg_line_words >= (1<<LGMAXBURST)); + + always @(*) + if (!cfg_active || req_vlast) + next_line_addr = { 1'b0, cfg_frame_addr }; + else + next_line_addr = req_line_addr+ { {(C_AXI_ADDR_WIDTH-16){1'b0}}, cfg_line_step }; + + always @(posedge i_clk) + if (req_newline) + begin + line_needs_alignment <= 0; + if (|next_line_addr[ADDRLSB +: LGMAXBURST]) + begin + if (|cfg_line_words[15-ADDRLSB:LGMAXBURST]) + line_needs_alignment <= 1; + if (~next_line_addr[ADDRLSB +: LGMAXBURST] + < cfg_line_words[LGMAXBURST-1:0]) + line_needs_alignment <= 1; + end + end + // }}} + + // req_addr, req_line_addr, req_line_words + // {{{ + always @(*) + // Verilator lint_off WIDTH + next_line_words = req_line_words - (M_AXI_AWLEN+1); + // Verilator lint_on WIDTH + + initial req_addr = 0; + initial req_line_addr = 0; + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + req_addr <= { 1'b0, cfg_frame_addr }; + req_line_addr <= { 1'b0, cfg_frame_addr }; + req_line_words <= cfg_line_words; + req_newline <= 1; + req_multiple_bursts <= (cfg_line_words >= (1<<LGMAXBURST)); + req_needs_alignment <= w_frame_needs_alignment; + end else if (phantom_start) + begin + req_newline <= req_hlast; + req_needs_alignment <= 0; + if (req_hlast && req_vlast) + begin + req_addr <= { 1'b0, cfg_frame_addr }; + req_line_addr <= { 1'b0, cfg_frame_addr }; + req_line_words <= cfg_line_words; + req_multiple_bursts <= line_multiple_bursts; + + req_needs_alignment <= frame_needs_alignment; + end else if (req_hlast) + begin + // verilator lint_off WIDTH + req_addr <= req_line_addr + cfg_line_step; + req_line_addr <= req_line_addr + cfg_line_step; + // verilator lint_on WIDTH + req_line_words <= cfg_line_words; + req_needs_alignment <= line_needs_alignment; + req_multiple_bursts <= line_multiple_bursts; + end else begin + req_addr <= req_addr + (1<<(LGMAXBURST+ADDRLSB)); + req_line_words <= next_line_words; + req_multiple_bursts <= |next_line_words[15-ADDRLSB:LGMAXBURST]; + + req_addr[LGMAXBURST+ADDRLSB-1:0] <= 0; + end + end else + req_newline <= 0; + // }}} + + // req_nlines, req_vlast + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + req_nlines <= cfg_frame_lines-1; + req_vlast <= (cfg_frame_lines <= 1); + end else if (phantom_start && req_hlast) + begin + if (req_vlast) + begin + req_nlines <= cfg_frame_lines-1; + req_vlast <= (cfg_frame_lines <= 1); + end else begin + req_nlines <= req_nlines - 1; + req_vlast <= (req_nlines <= 1); + end + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outgoing sync counting + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wr_line_beats, wr_hlast + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + wr_line_beats <= cfg_line_words-1; + wr_hlast <= (cfg_line_words == 1); + end else if (M_AXI_WVALID && M_AXI_WREADY) + begin + if (wr_hlast) + begin + wr_line_beats <= cfg_line_words - 1; + wr_hlast <= (cfg_line_words <= 1); + end else begin + wr_line_beats <= wr_line_beats - 1; + wr_hlast <= (wr_line_beats <= 1); + end + end + // }}} + + // wr_lines, wr_vlast + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + wr_lines <= cfg_frame_lines-1; + wr_vlast <= (cfg_frame_lines == 1); + end else if (M_AXI_WVALID && M_AXI_WREADY && wr_hlast) + begin + if (wr_vlast) + begin + wr_lines <= cfg_frame_lines - 1; + wr_vlast <= (cfg_frame_lines <= 1); + end else begin + wr_lines <= wr_lines - 1; + wr_vlast <= (wr_lines <= 1); + end + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The outgoing AXI (full) protocol section + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Some counters to keep track of our state + // {{{ + + // aw_bursts_outstanding + // {{{ + // Count the number of bursts outstanding--these are the number of + // AWVALIDs that have been accepted, but for which the BVALID + // has not (yet) been returned. + initial aw_none_outstanding = 1; + initial aw_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset) + begin + aw_bursts_outstanding <= 0; + aw_none_outstanding <= 1; + end else case ({ phantom_start, M_AXI_BVALID && M_AXI_BREADY }) + 2'b01: begin + aw_bursts_outstanding <= aw_bursts_outstanding - 1; + aw_none_outstanding <= (aw_bursts_outstanding == 1); + end + 2'b10: begin + aw_bursts_outstanding <= aw_bursts_outstanding + 1; + aw_none_outstanding <= 0; + end + default: begin end + endcase + // }}} + + // r_stopped + // {{{ + // Following an error or drop of cfg_active, soft_reset will go high. + // We then come to a stop once everything becomes inactive + initial r_stopped = 1; + always @(posedge i_clk) + if (i_reset) + r_stopped <= 1; + else if (r_stopped) + begin + // Synchronize on the last pixel of an incoming frame + if (cfg_active && S_AXIS_TVALID && S_AXIS_VLAST) + r_stopped <= soft_reset; + end else if ((soft_reset || lost_sync) && aw_none_outstanding + && !M_AXI_AWVALID && !M_AXI_WVALID) + r_stopped <= 1; + // }}} + + // }}} + + // + // start_burst + // {{{ + always @(*) + begin + start_burst = 1; + if (LGFIFO > 0 && !fifo_bursts_available) + begin + if (!req_multiple_bursts && fifo_data_available[7:0] + < req_line_words[7:0]) + start_burst = 0; + if (req_multiple_bursts && !fifo_bursts_available) + start_burst = 0; + end + + if (phantom_start || req_newline || lost_sync) + start_burst = 0; + + // Can't start a new burst if the outgoing channel is still + // stalled. + if (M_AXI_AWVALID && !M_AXI_AWREADY) + start_burst = 0; + if (M_AXI_WVALID && (!M_AXI_WREADY || !M_AXI_WLAST)) + start_burst = 0; + + // Don't let our aw_bursts_outstanding counter overflow + if (aw_bursts_outstanding[15]) + start_burst = 0; + + // Don't wrap around memory + if (req_addr[C_AXI_ADDR_WIDTH]) + start_burst = 0; + + // If the user wants us to stop, then stop + if (soft_reset || !cfg_active || r_stopped) + start_burst = 0; + end + // }}} + + // AWLEN + // {{{ + always @(*) + till_boundary = ~req_addr[ADDRLSB +: LGMAXBURST]; + + always @(*) + begin + if (req_needs_alignment) + next_awlen = till_boundary; + else if (req_multiple_bursts) + next_awlen = (1<<LGMAXBURST)-1; + else + next_awlen = req_line_words[7:0]-1; + end + + always @(posedge i_clk) + if (!M_AXI_AWVALID || M_AXI_AWREADY) + axi_awlen <= next_awlen; + // }}} + + // wr_pending + // {{{ + initial wr_pending = 0; + always @(posedge i_clk) + begin + if (M_AXI_WVALID && M_AXI_WREADY) + wr_pending <= wr_pending - 1; + if (start_burst) + wr_pending <= next_awlen + 1; + + if (!S_AXI_ARESETN) + wr_pending <= 0; + end + // }}} + + // req_hlast + // {{{ + always @(posedge i_clk) + // Verilator lint_off WIDTH + if (r_stopped) + req_hlast <= (cfg_frame_addr[ADDRLSB +: LGMAXBURST] + + cfg_line_words <= (1<<LGMAXBURST)); + else if (phantom_start) + begin + if (req_hlast && req_vlast) + req_hlast <= (cfg_frame_addr[ADDRLSB +: LGMAXBURST] + + cfg_line_words <= (1<<LGMAXBURST)); + else if (req_hlast) + req_hlast <= (next_line_addr[ADDRLSB +: LGMAXBURST] + + cfg_line_words <= (1<<LGMAXBURST)); + else + req_hlast <= (req_line_words - (M_AXI_AWLEN+1) + <= (1<<LGMAXBURST)); + // Verilator lint_on WIDTH + end + // }}} + + // phantom_start + // {{{ + initial phantom_start = 0; + always @(posedge i_clk) + if (i_reset) + phantom_start <= 0; + else + phantom_start <= start_burst; + // }}} + + // AWVALID + // {{{ + initial axi_awvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_awvalid <= 0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + axi_awvalid <= start_burst; + // }}} + + // ARADDR + // {{{ + initial axi_awaddr = 0; + always @(posedge i_clk) + begin + if (start_burst) + axi_awaddr <= req_addr[C_AXI_ADDR_WIDTH-1:0]; + + axi_awaddr[ADDRLSB-1:0] <= 0; + end + // }}} + + // WVALID + // {{{ + initial axi_wvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_wvalid <= 0; + else if (!M_AXI_WVALID || M_AXI_WREADY) + axi_wvalid <= start_burst || (M_AXI_WVALID && !M_AXI_WLAST); + // }}} + + // WLAST + // {{{ + always @(posedge i_clk) + begin + if (M_AXI_WVALID && M_AXI_WREADY) + axi_wlast <= (wr_pending <= 2); + + if (start_burst) + axi_wlast <= (next_awlen == 0); + end + // }}} + + + // Set the constant M_AXI_* signals + // {{{ + assign M_AXI_AWVALID= axi_awvalid; + assign M_AXI_AWID = AXI_ID; + assign M_AXI_AWADDR = axi_awaddr; + assign M_AXI_AWLEN = axi_awlen; + // Verilator lint_off WIDTH + assign M_AXI_AWSIZE = $clog2(C_AXI_DATA_WIDTH)-3; + // Verilator lint_on WIDTH + assign M_AXI_AWBURST= 2'b01; // INCR + assign M_AXI_AWLOCK = 0; + assign M_AXI_AWCACHE= 0; + assign M_AXI_AWPROT = 0; + assign M_AXI_AWQOS = 0; + // + assign M_AXI_WVALID = axi_wvalid; + assign M_AXI_WLAST = axi_wlast; + assign M_AXI_WDATA = fifo_data; + assign M_AXI_WSTRB = -1; + // + assign M_AXI_BREADY = 1; + // }}} + + // End AXI protocol section + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_BID, + M_AXI_BRESP[0], fifo_empty, + S_AXIL_AWADDR[AXILLSB-1:0], S_AXIL_ARADDR[AXILLSB-1:0], + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], + new_control, new_config, fifo_fill, next_line_addr, + fifo_vlast, fifo_hlast + }; + // Verilator lint_on UNUSED + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // {{{ + + // The formal properties for this core are maintained elsewhere + + //////////////////////////////////////////////////////////////////////// + // + // Contract checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // The formal proof for this core doesn't (yet) include a contract + // check. + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Simplifying (careless) assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // If the FIFO gets reset, then we really don't care about what + // gets written to memory. However, it is possible that a value + // on the output might get changed and so violate our protocol checker. + // (We don't care.) So let's just assume it never happens, and check + // everything else instead. + always @(*) + if (M_AXI_WVALID) + assume(!lost_sync && cfg_active); + // }}} + // }}} +`endif +endmodule diff --git a/rtl/wb2axip/axivdisplay.v b/rtl/wb2axip/axivdisplay.v new file mode 100644 index 0000000..aac6c62 --- /dev/null +++ b/rtl/wb2axip/axivdisplay.v @@ -0,0 +1,1438 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axivdisplay +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Reads from memory for the purposes of serving a video frame +// {{{ +// buffer. The result of this core is an AXI stream having the +// word size of the memory interface in the first place. TLAST is set +// based upon the end of the frame, and TUSER based upon the end of the +// line. An option exists to use Xilinx's encoding instead where TLAST +// is the end of the line and TUSER is the first pixel word of the frame. +// +// This core is in many ways similar to the AXI MM2S core, but with some +// key differences: The AXI MM2S core generates a single transfer. This +// core generates a repeating set of transfers--one per line, repeated +// per frame. +// +// To insure high bandwidth, data is first copied into a FIFO of twice +// the width of the longest burst. +// +// }}} +// Registers: +// {{{ +// 0: FBUF_CONTROL and status +// bit 0: START(1)/STOP(0) +// Command the core to start by writing a '1' to this bit. It +// will then remain busy/active until either you tell it to halt, +// or an error occurrs. +// bit 1: BUSY +// bit 2: ERR +// If the core receives a bus error, it assumes it has been +// inappropriately set up, sets this error bit and then halts. +// It will not start again until this bit is cleared. Only a +// write to the control register with the ERR bit set will clear +// it. (Don't do this unless you know what caused it to halt ...) +// bit 3: DIRTY +// If you update core parameters while it is running, the busy +// bit will be set. This bit is an indication that the current +// configuration doesn't necessarily match the one you are reading +// out. To clear DIRTY, deactivate the core, wait for it to be +// no longer busy, and then start it again. This will also start +// it under the new configuration so the two match. +// +// 2: FBUF_LINESTEP +// Controls the distance from one line to the next. This is the +// value added to the address of the beginning of the line to get +// to the beginning of the next line. This should nominally be +// equal to the number of bytes per line, although it doesn't +// need to be. +// +// Any attempt to set this value to zero will simply copy the +// number of data bytes per line into this value. +// +// 4: FBUF_LINEBYTES +// This is the number of data bytes necessary to capture all of +// the video data in a line. This value must be more than zero +// in order to activate the core. +// +// At present, this core can only handle a number of bytes aligned +// with the word size of the bus. +// +// 6: FBUF_NUMLINES +// The number of lines of active video data in a frame. This +// number must be greater than zero in order to activate and +// operate the core. +// +// 8: FBUF_ADDRESS +// The is the first address of video data in memory. Each frame +// will start reading from this address. +// +// 12: (reserved for the upper FBUF_ADDRESS) +// +// BUGS: +// 1. Memory reads are currently allowed to wrap around the end of memory. +// I'm not (yet) sure if this is a good thing or a bad thing. I mean, its +// a great thing for small dedicated memories designated for this purpose +// alone, but perhaps a bad thing if the memory space is shared with +// peripherals as well as a CPU. +// +// 2. I'd still like to add an option to handle memory addresses that +// aren't aligned. Until that is done, the means of interacting with the +// core will change from one implementation to another. +// +// 3. If the configuration changes mid-write, but without de-activating +// the core and waiting for it to come to idle, the synchronization flags +// might out-of-sync with the pixels. To resolve, deactivate the core and +// let it come to whenever changing configuration parameters. +// }}} +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axivdisplay #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + parameter C_AXI_ID_WIDTH = 1, + // + // We support five 32-bit AXI-lite registers, requiring 5-bits + // of AXI-lite addressing + localparam C_AXIL_ADDR_WIDTH = 4, + localparam C_AXIL_DATA_WIDTH = 32, + // + // The bottom ADDRLSB bits of any AXI address are subword bits + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3, + localparam AXILLSB = $clog2(C_AXIL_DATA_WIDTH)-3, + // + // OPT_UNALIGNED: Allow unaligned accesses, address requests + // and sizes which may or may not match the underlying data + // width. If set, the core will quietly align these requests. + // parameter [0:0] OPT_UNALIGNED = 1'b0, + // + // OPT_LGMAXBURST + parameter OPT_LGMAXBURST = 8, + // + parameter [0:0] DEF_ACTIVE_ON_RESET = 0, + parameter [15:0] DEF_LINES_PER_FRAME = 1024, + parameter [16-ADDRLSB-1:0] DEF_WORDS_PER_LINE = (1280 * 32)/C_AXI_DATA_WIDTH, + // + // DEF_FRAMEADDR: the default AXI address of the frame buffer + // containing video memory. Unless OPT_UNALIGNED is set, this + // should be aligned so that DEF_FRAMEADDR[ADDRLSB-1:0] == 0. + parameter [C_AXI_ADDR_WIDTH-1:0] DEF_FRAMEADDR = 0, + // + // The (log-based two of the) size of the FIFO in words. + // I like to set this to the size of two AXI bursts, so that + // while one is being read out the second can be read in. Can + // also be set larger if desired. + parameter LGFIFO = OPT_LGMAXBURST+1, + // + // AXI_ID is the ID we will use for all of our AXI transactions + parameter AXI_ID = 0, + // + // OPT_TUSER_IS_SOF. Xilinx and I chose different stream + // encodings. I encode TLAST== VLAST and + // TUSER == (optional) HLAST. Xilinx chose TLAST == HLAST + // and TUSER == SOF(start of frame). Set OPT_TUSER_IS_SOF to + // use Xilinx's encoding. + parameter [0:0] OPT_TUSER_IS_SOF = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The video stream interface + // {{{ + output wire M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output wire [C_AXI_DATA_WIDTH-1:0] M_AXIS_TDATA, + output wire /* VLAST or HLAST */ M_AXIS_TLAST, + output wire /* HLAST or SOF */ M_AXIS_TUSER, + // }}} + // + // The control interface + // {{{ + input wire S_AXIL_AWVALID, + output wire S_AXIL_AWREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_AWADDR, + input wire [2:0] S_AXIL_AWPROT, + // + input wire S_AXIL_WVALID, + output wire S_AXIL_WREADY, + input wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_WDATA, + input wire [C_AXIL_DATA_WIDTH/8-1:0] S_AXIL_WSTRB, + // + output wire S_AXIL_BVALID, + input wire S_AXIL_BREADY, + output wire [1:0] S_AXIL_BRESP, + // + input wire S_AXIL_ARVALID, + output wire S_AXIL_ARREADY, + input wire [C_AXIL_ADDR_WIDTH-1:0] S_AXIL_ARADDR, + input wire [2:0] S_AXIL_ARPROT, + // + output wire S_AXIL_RVALID, + input wire S_AXIL_RREADY, + output wire [C_AXIL_DATA_WIDTH-1:0] S_AXIL_RDATA, + output wire [1:0] S_AXIL_RRESP, + // }}} + // + + // + // The AXI (full) read interface + // {{{ + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [7:0] M_AXI_ARLEN, + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [1:0] M_AXI_RRESP + // }}} + // }}} + ); + + // Core logic implementation + // {{{ + // Local parameter declarations + // {{{ + localparam [1:0] FBUF_CONTROL = 2'b00, + FBUF_FRAMEINFO = 2'b01, + FBUF_ADDRLO = 2'b10, + FBUF_ADDRHI = 2'b11; + + localparam CBIT_ACTIVE = 0, + CBIT_BUSY = 1, + CBIT_ERR = 2, + CBIT_DIRTY = 3; + + localparam TMPLGMAXBURST=(LGFIFO-1 > OPT_LGMAXBURST) + ? OPT_LGMAXBURST : LGFIFO-1; + + localparam LGMAXBURST = (TMPLGMAXBURST+ADDRLSB > 12) + ? (12-ADDRLSB) : TMPLGMAXBURST; + // }}} + + wire i_clk = S_AXI_ACLK; + wire i_reset = !S_AXI_ARESETN; + + // Signal declarations + // {{{ + reg soft_reset, r_err, r_stopped; + reg cfg_active, cfg_zero_length, cfg_dirty; + reg [C_AXI_ADDR_WIDTH-1:0] cfg_frame_addr; + reg [15:0] cfg_frame_lines, cfg_line_step; + reg [16-ADDRLSB-1:0] cfg_line_words; + + // FIFO signals + wire reset_fifo, write_to_fifo, + read_from_fifo; + wire [C_AXI_DATA_WIDTH-1:0] write_data; + wire [LGFIFO:0] fifo_fill; + wire fifo_full, fifo_empty; + + wire awskd_valid, axil_write_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] awskd_addr; + // + wire wskd_valid; + wire [C_AXIL_DATA_WIDTH-1:0] wskd_data; + wire [C_AXIL_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire arskd_valid, axil_read_ready; + wire [C_AXIL_ADDR_WIDTH-AXILLSB-1:0] arskd_addr; + reg [C_AXIL_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + reg [C_AXIL_DATA_WIDTH-1:0] w_status_word; + reg [2*C_AXIL_DATA_WIDTH-1:0] wide_address, new_wideaddr; + wire [C_AXIL_DATA_WIDTH-1:0] new_cmdaddrlo, new_cmdaddrhi; + reg [C_AXIL_DATA_WIDTH-1:0] wide_config; + wire [C_AXIL_DATA_WIDTH-1:0] new_config; + + // reg partial_burst_requested; + // reg [LGMAXBURST-1:0] addralign; + // reg [LGFIFO:0] rd_uncommitted; + // reg [LGMAXBURST:0] initial_burstlen; + // reg [LGLENWA-1:0] rd_reads_remaining; + // reg rd_none_remaining, + // rd_last_remaining; + + // wire realign_last_valid; + + reg axi_arvalid, lag_start, phantom_start, start_burst, + ar_none_outstanding; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + reg [LGMAXBURST:0] max_burst; + reg [7:0] axi_arlen; + reg [15:0] ar_bursts_outstanding; + // + wire vlast, hlast; + reg [15:0] r_frame_lines, r_line_step; + reg [16-ADDRLSB-1:0] r_line_words; + + reg req_hlast, req_vlast; + reg [15:0] req_nlines; + reg [16-ADDRLSB-1:0] req_line_words; + reg [C_AXI_ADDR_WIDTH:0] req_addr, req_line_addr; + // + reg rd_hlast, rd_vlast; + reg [15:0] rd_lines; + reg [16-ADDRLSB-1:0] rd_line_beats; + wire no_fifo_space_available; + // + reg [LGMAXBURST-1:0] till_boundary; + reg [LGFIFO:0] fifo_space_available; + + +`ifdef FORMAL + reg [C_AXI_ADDR_WIDTH:0] f_rd_line_addr, f_rd_addr; +`endif + + wire M_AXIS_VLAST, M_AXIS_HLAST; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // + //////////////////////////////////////////////////////////////////////// + // + // This is mostly the skidbuffer logic, and handling of the VALID + // and READY signals for the AXI-lite control logic in the next + // section. + // {{{ + + // + // Write signaling + // + // {{{ + + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_AWVALID), .o_ready(S_AXIL_AWREADY), + .i_data(S_AXIL_AWADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_DATA_WIDTH+C_AXIL_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_WVALID), .o_ready(S_AXIL_WREADY), + .i_data({ S_AXIL_WDATA, S_AXIL_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXIL_BVALID || S_AXIL_BREADY); + + initial axil_bvalid = 0; + always @(posedge i_clk) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXIL_BREADY) + axil_bvalid <= 0; + + assign S_AXIL_BVALID = axil_bvalid; + assign S_AXIL_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + skidbuffer #(.OPT_OUTREG(0), .DW(C_AXIL_ADDR_WIDTH-AXILLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXIL_ARVALID), .o_ready(S_AXIL_ARREADY), + .i_data(S_AXIL_ARADDR[C_AXIL_ADDR_WIDTH-1:AXILLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXIL_RREADY); + + initial axil_read_valid = 1'b0; + always @(posedge i_clk) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXIL_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXIL_RVALID = axil_read_valid; + assign S_AXIL_RDATA = axil_read_data; + assign S_AXIL_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite controlled logic + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + // soft_reset, r_err + // {{{ + initial soft_reset = 1; + always @(posedge i_clk) + if (i_reset) + begin + soft_reset <= 1; + r_err <= 0; + end else if (soft_reset) + begin + + if ((axil_write_ready && awskd_addr == FBUF_CONTROL) + && wskd_strb[0] && wskd_data[CBIT_ERR]) + r_err <= cfg_zero_length; + + if (cfg_active && !r_err && r_stopped) + soft_reset <= 0; + + end else // if (!soft_reset) + begin + // Halt on any bus error. We'll require user intervention + // to start back up again + if (M_AXI_RREADY && M_AXI_RVALID && M_AXI_RRESP[1]) + begin + soft_reset <= 1; + r_err <= 1; + end + + // Halt on any user request + if (!cfg_active) + soft_reset <= 1; + end + // }}} + + // wide_* and new_* write setup registers + // {{{ + always @(*) + begin + wide_address = 0; + wide_address[C_AXI_ADDR_WIDTH-1:0] = cfg_frame_addr; + wide_address[ADDRLSB-1:0] = 0; + + wide_config = { cfg_frame_lines, cfg_line_words, + {(ADDRLSB){1'b0}} }; + end + + assign new_cmdaddrlo = apply_wstrb( + wide_address[C_AXIL_DATA_WIDTH-1:0], + wskd_data, wskd_strb); + + generate if (C_AXI_ADDR_WIDTH > 32) + begin : GEN_LARGE_AW + + assign new_cmdaddrhi = apply_wstrb( + wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH], + wskd_data, wskd_strb); + + end else begin : GEN_SINGLE_WORD_AW + + assign new_cmdaddrhi = 0; + + end endgenerate + + + wire [C_AXIL_DATA_WIDTH-1:0] new_control; + assign new_control = apply_wstrb(w_status_word, wskd_data, wskd_strb); + assign new_config = apply_wstrb(wide_config, wskd_data, wskd_strb); + + always @(*) + begin + new_wideaddr = wide_address; + + if (awskd_addr == FBUF_ADDRLO) + new_wideaddr[C_AXIL_DATA_WIDTH-1:0] = new_cmdaddrlo; + if (awskd_addr == FBUF_ADDRHI) + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH] = new_cmdaddrhi; + new_wideaddr[ADDRLSB-1:0] = 0; + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH] = 0; + end + // }}} + + // Configuration registers (Write) + // {{{ + initial cfg_active = 0; + initial cfg_frame_addr = DEF_FRAMEADDR; + initial cfg_frame_addr[ADDRLSB-1:0] = 0; + initial cfg_line_words = DEF_WORDS_PER_LINE; + initial cfg_frame_lines = DEF_LINES_PER_FRAME; + initial cfg_zero_length = (DEF_WORDS_PER_LINE == 0) + ||(DEF_LINES_PER_FRAME == 0); + always @(posedge i_clk) + if (i_reset) + begin + cfg_active <= DEF_ACTIVE_ON_RESET; + cfg_frame_addr <= DEF_FRAMEADDR; + cfg_line_words <= DEF_WORDS_PER_LINE; + cfg_line_step <= { DEF_WORDS_PER_LINE, {(ADDRLSB){1'b0}} }; + cfg_frame_lines <= DEF_LINES_PER_FRAME; + cfg_zero_length <= (DEF_WORDS_PER_LINE==0) + ||(DEF_LINES_PER_FRAME == 0); + cfg_dirty <= 0; + end else begin + if (r_stopped) + cfg_dirty <= 0; + if (cfg_active && req_hlast && req_vlast && phantom_start) + cfg_dirty <= 0; + if (M_AXI_RREADY && M_AXI_RVALID && M_AXI_RRESP[1]) + cfg_active <= 0; + + if (axil_write_ready) + case(awskd_addr) + FBUF_CONTROL: begin + if (wskd_strb[0]) + cfg_active <= wskd_data[CBIT_ACTIVE] + && (!r_err || wskd_data[CBIT_ERR]) + && (!cfg_zero_length); + + if (new_control[31:16] == 0) + begin + cfg_line_step <= 0; + cfg_line_step[16-1:ADDRLSB] <= cfg_line_words; + // Verilator lint_off WIDTH + cfg_dirty <= (cfg_line_step + != (cfg_line_words << ADDRLSB)); + // Verilator lint_on WIDTH + end else begin + cfg_line_step <= new_control[31:16]; + cfg_dirty <= (cfg_line_step != new_control[31:16]); + end end + FBUF_FRAMEINFO: begin + { cfg_frame_lines, cfg_line_words } + <= new_config[C_AXI_DATA_WIDTH-1:ADDRLSB]; + cfg_zero_length <= (new_config[31:16] == 0) + ||(new_config[15:ADDRLSB] == 0); + if ((new_config[31:16] == 0) + ||(new_config[15:ADDRLSB] == 0)) + cfg_active <= 0; + cfg_dirty <= 1; + end + FBUF_ADDRLO, FBUF_ADDRHI: begin + cfg_frame_addr <= new_wideaddr[C_AXI_ADDR_WIDTH-1:0]; + cfg_dirty <= 1; + end + default: begin end + endcase + end + // }}} + + // AXI-Lite read register data + // {{{ + always @(*) + begin + w_status_word = 0; + w_status_word[31:16] = cfg_line_step; + w_status_word[CBIT_DIRTY] = cfg_dirty; + w_status_word[CBIT_ERR] = r_err; + w_status_word[CBIT_BUSY] = !soft_reset; + w_status_word[CBIT_ACTIVE] = cfg_active || (!soft_reset || !r_stopped); + end + + always @(posedge i_clk) + if (!axil_read_valid || S_AXIL_RREADY) + begin + axil_read_data <= 0; + case(arskd_addr) + FBUF_CONTROL: axil_read_data <= w_status_word; + FBUF_FRAMEINFO: axil_read_data <= { cfg_frame_lines, + cfg_line_words, {(ADDRLSB){1'b0}} }; + FBUF_ADDRLO: axil_read_data <= wide_address[C_AXIL_DATA_WIDTH-1:0]; + FBUF_ADDRHI: axil_read_data <= wide_address[2*C_AXIL_DATA_WIDTH-1:C_AXIL_DATA_WIDTH]; + default axil_read_data <= 0; + endcase + end + // }}} + + // apply_wstrb function for applying wstrbs to register words + // {{{ + function [C_AXIL_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXIL_DATA_WIDTH-1:0] prior_data; + input [C_AXIL_DATA_WIDTH-1:0] new_data; + input [C_AXIL_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXIL_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The data FIFO section + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + // {{{ + assign reset_fifo = r_stopped; + + assign write_to_fifo = M_AXI_RVALID; + assign write_data = M_AXI_RDATA; + // assign realign_last_valid = 0; + assign vlast = rd_vlast; + assign hlast = rd_hlast; + assign M_AXI_RREADY = !fifo_full; + // }}} + + generate if (LGFIFO > 0) + begin : GEN_SPACE_AVAILBLE + // Here's where we'll put the actual outgoing FIFO + // {{{ + initial fifo_space_available = (1<<LGFIFO); + always @(posedge i_clk) + if (reset_fifo) + fifo_space_available <= (1<<LGFIFO); + else case({phantom_start, read_from_fifo && !fifo_empty }) + 2'b00: begin end + // Verilator lint_off WIDTH + 2'b10: fifo_space_available <= fifo_space_available - (M_AXI_ARLEN+1); + 2'b11: fifo_space_available <= fifo_space_available - (M_AXI_ARLEN); + // Verilator lint_on WIDTH + 2'b01: fifo_space_available <= fifo_space_available + 1; + endcase + + assign M_AXIS_TVALID = !fifo_empty; + assign read_from_fifo = M_AXIS_TVALID && M_AXIS_TREADY; + + sfifo #(.BW(C_AXI_DATA_WIDTH+2), .LGFLEN(LGFIFO)) + sfifo(i_clk, reset_fifo, + write_to_fifo, { vlast && hlast, hlast, write_data }, + fifo_full, fifo_fill, + read_from_fifo, { M_AXIS_VLAST, M_AXIS_HLAST, + M_AXIS_TDATA }, fifo_empty); + + assign no_fifo_space_available = (fifo_space_available < (1<<LGMAXBURST)); + // }}} + end else begin : NO_FIFO + // {{{ + assign fifo_full = !M_AXIS_TREADY; + assign fifo_fill = 0; + assign fifo_empty = !M_AXIS_TVALID; + assign M_AXIS_TVALID = write_to_fifo; + + assign M_AXIS_VLAST = vlast && hlast; + assign M_AXIS_HLAST = hlast; + assign M_AXIS_TDATA = M_AXI_RDATA; + + assign no_fifo_space_available = (ar_bursts_outstanding >= 3); + // }}} + end endgenerate + // }}} + + // Switch between TLAST/TUSER encodings if necessary + // {{{ + generate if (OPT_TUSER_IS_SOF) + begin : XILINX_ENCODING + + reg SOF; + + initial SOF = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || r_stopped) + SOF <= 1'b1; + else if (M_AXIS_TVALID && M_AXIS_TREADY) + SOF <= M_AXIS_VLAST; + + assign M_AXIS_TLAST = M_AXIS_HLAST; + assign M_AXIS_TUSER = SOF; + + end else begin : VLAST_EQUALS_TLAST + + assign M_AXIS_TLAST = M_AXIS_VLAST; + assign M_AXIS_TUSER = M_AXIS_HLAST; + end endgenerate + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outoing frame address counting + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped || (phantom_start && req_hlast && req_vlast)) + begin + r_frame_lines <= cfg_frame_lines; + r_line_words <= cfg_line_words; + r_line_step <= { {(ADDRLSB){1'b0}}, cfg_line_step[15:ADDRLSB] }; + end + + initial req_addr = 0; + initial req_line_addr = 0; + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + req_addr <= { 1'b0, cfg_frame_addr }; + req_line_addr <= { 1'b0, cfg_frame_addr }; + req_line_words <= cfg_line_words; + end else if (phantom_start) + begin + if (req_hlast && req_vlast) + begin + req_addr <= { 1'b0, cfg_frame_addr }; + req_line_addr <= { 1'b0, cfg_frame_addr }; + req_line_words <= cfg_line_words; + end else if (req_hlast) + begin + // verilator lint_off WIDTH + req_addr <= req_line_addr + + (r_line_step << M_AXI_ARSIZE); + req_line_addr <= req_line_addr + + (r_line_step << M_AXI_ARSIZE); + // verilator lint_on WIDTH + req_line_words <= r_line_words; + end else begin + // verilator lint_off WIDTH + req_addr <= req_addr + (1<<(LGMAXBURST+ADDRLSB)); + req_line_words <= req_line_words - (M_AXI_ARLEN+1); + // verilator lint_on WIDTH + + req_addr[LGMAXBURST+ADDRLSB-1:0] <= 0; + end + end + + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + req_nlines <= cfg_frame_lines-1; + req_vlast <= (cfg_frame_lines <= 1); + end else if (phantom_start && req_hlast) + begin + if (req_vlast) + begin + req_nlines <= cfg_frame_lines-1; + req_vlast <= (cfg_frame_lines <= 1); + end else begin + req_nlines <= req_nlines - 1; + req_vlast <= (req_nlines <= 1); + end + end +`ifdef FORMAL + always @(*) + if (!r_stopped) + begin + assert(req_vlast == (req_nlines == 0)); + assert(req_addr >= { 1'b0, cfg_frame_addr }); + assert(req_line_addr >= { 1'b0, cfg_frame_addr }); + assert(req_line_addr <= req_addr); + end + + always @(*) + if (cfg_active) + begin + assert(cfg_frame_lines != 0); + assert(cfg_line_words != 0); + end + + always @(*) + if (!r_stopped) + begin + assert(r_frame_lines != 0); + assert(r_line_words != 0); + end +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming frame address counting + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + rd_line_beats <= cfg_line_words-1; + rd_hlast <= (cfg_line_words == 1); + end else if (M_AXI_RVALID && M_AXI_RREADY) + begin + if (rd_hlast) + begin + rd_line_beats <= r_line_words - 1; + rd_hlast <= (r_line_words <= 1); + end else begin + rd_line_beats <= rd_line_beats - 1; + rd_hlast <= (rd_line_beats <= 1); + end + end + + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + rd_lines <= cfg_frame_lines-1; + rd_vlast <= (cfg_frame_lines == 1); + end else if (M_AXI_RVALID && M_AXI_RREADY && rd_hlast) + begin + if (vlast) + begin + rd_lines <= r_frame_lines - 1; + rd_vlast <= (r_frame_lines <= 1); + end else begin + rd_lines <= rd_lines - 1; + rd_vlast <= (rd_lines <= 1); + end + end + +`ifdef FORMAL + always @(posedge i_clk) + if (i_reset || r_stopped) + begin + f_rd_addr <= { 1'b0, cfg_frame_addr }; + f_rd_line_addr <= { 1'b0, cfg_frame_addr }; + end else if (M_AXI_RVALID && M_AXI_RREADY) + begin + if (rd_vlast && rd_hlast) + begin + f_rd_addr <= cfg_frame_addr; + f_rd_line_addr <= cfg_frame_addr; + end else if (rd_hlast) + begin + f_rd_addr <= f_rd_line_addr + (r_line_step << M_AXI_ARSIZE); + f_rd_line_addr <= f_rd_line_addr + (r_line_step << M_AXI_ARSIZE); + end else begin + f_rd_addr <= f_rd_addr + (1<<ADDRLSB); + end + end +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The incoming AXI (full) protocol section + // + //////////////////////////////////////////////////////////////////////// + // + // + // {{{ + + // Some counters to keep track of our state + // {{{ + + // ar_bursts_outstanding + // {{{ + // Count the number of bursts outstanding--these are the number of + // ARVALIDs that have been accepted, but for which the RVALID && RLAST + // has not (yet) been returned. + initial ar_none_outstanding = 1; + initial ar_bursts_outstanding = 0; + always @(posedge i_clk) + if (i_reset) + begin + ar_bursts_outstanding <= 0; + ar_none_outstanding <= 1; + end else case ({ phantom_start, + M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST }) + 2'b01: begin + ar_bursts_outstanding <= ar_bursts_outstanding - 1; + ar_none_outstanding <= (ar_bursts_outstanding == 1); + end + 2'b10: begin + ar_bursts_outstanding <= ar_bursts_outstanding + 1; + ar_none_outstanding <= 0; + end + default: begin end + endcase + // }}} + + // r_stopped + // {{{ + // Following an error or drop of cfg_active, soft_reset will go high. + // We then come to a stop once everything becomes inactive + initial r_stopped = 1; + always @(posedge i_clk) + if (i_reset) + r_stopped <= 1; + else if (r_stopped) + r_stopped <= soft_reset || !cfg_active; + else if (soft_reset && ar_none_outstanding && !M_AXI_ARVALID) + r_stopped <= 1; + // }}} + + // }}} + + // + // start_burst + // {{{ + always @(*) + begin + start_burst = 1; + if (no_fifo_space_available) + start_burst = 0; + if (phantom_start || lag_start) + // Insist on a minimum of two clocks between burst + // starts, so we can get our lengths right + start_burst = 0; + + // Can't start a new burst if the outgoing channel is still + // stalled. + if (M_AXI_ARVALID && !M_AXI_ARREADY) + start_burst = 0; + + // If the user wants us to stop, then stop + if (!cfg_active || soft_reset) + start_burst = 0; + end + // }}} + + // ARLEN + // {{{ + // Coming in, req_addr and req_line_words can be trusted + // lag_start will be true to reflect this + always @(posedge i_clk) + if (lag_start) + begin + if (req_line_words >= (1<<LGMAXBURST)) + max_burst <= (1<<LGMAXBURST); + else + // Verilator lint_off WIDTH + max_burst <= req_line_words; + // Verilator lint_on WIDTH + end + + always @(*) + till_boundary = ~req_addr[ADDRLSB +: LGMAXBURST]; + + always @(posedge i_clk) + if (!M_AXI_ARVALID || M_AXI_ARREADY) + begin + // Verilator lint_off WIDTH + if (till_boundary > 0 && max_burst <= till_boundary) + axi_arlen <= max_burst-1; + // Verilator lint_on WIDTH + else + axi_arlen <= till_boundary; + end + // }}} + + // req_hlast + // {{{ + always @(posedge i_clk) + if (lag_start || start_burst) + begin + req_hlast <= 1; + // Verilator lint_off WIDTH + if (req_line_words > till_boundary+1) + req_hlast <= 0; + if (req_line_words > max_burst) + req_hlast <= 0; + // Verilator lint_on WIDTH + end + +`ifdef FORMAL + always @(*) + if (phantom_start) + begin + if (req_hlast) + begin + assert(axi_arlen+1 == req_line_words); + end else + assert(axi_arlen+1 < req_line_words); + end else if (!soft_reset && !r_stopped && !lag_start) + begin + if (max_burst != req_line_words) + begin + assert(!req_hlast); + end else if (!req_hlast && !M_AXI_ARVALID) + assert(axi_arlen < max_burst); + + assert(max_burst > 0); + if (req_line_words > (1<<LGMAXBURST)) + begin + assert(max_burst == (1<<LGMAXBURST)); + end else + assert(max_burst == req_line_words); + end +`endif + // }}} + + // phantom_start + // {{{ + initial phantom_start = 0; + always @(posedge i_clk) + if (i_reset) + phantom_start <= 0; + else + phantom_start <= start_burst; + + initial lag_start = 1; + always @(posedge i_clk) + if (i_reset || r_stopped) + lag_start <= 1; + else + lag_start <= phantom_start; + // }}} + + // ARVALID + // {{{ + initial axi_arvalid = 0; + always @(posedge i_clk) + if (i_reset) + axi_arvalid <= 0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arvalid <= start_burst; + // }}} + + // ARADDR + // {{{ + initial axi_araddr = 0; + always @(posedge i_clk) + begin + if (start_burst) + axi_araddr <= req_addr[C_AXI_ADDR_WIDTH-1:0]; + + axi_araddr[ADDRLSB-1:0] <= 0; + end + // }}} + + // Set the constant M_AXI_* signals + // {{{ + assign M_AXI_ARVALID= axi_arvalid; + assign M_AXI_ARID = AXI_ID; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARLEN = axi_arlen; + // Verilator lint_off WIDTH + assign M_AXI_ARSIZE = $clog2(C_AXI_DATA_WIDTH)-3; + // Verilator lint_on WIDTH + assign M_AXI_ARBURST= 2'b01; // INCR + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE= 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_ARQOS = 0; + // }}} + + // End AXI protocol section + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXIL_AWPROT, S_AXIL_ARPROT, M_AXI_RID, + M_AXI_RRESP[0], fifo_full, wskd_strb[2:0], + S_AXIL_AWADDR[AXILLSB-1:0], S_AXIL_ARADDR[AXILLSB-1:0], + new_wideaddr[2*C_AXIL_DATA_WIDTH-1:C_AXI_ADDR_WIDTH], + new_control, new_config, fifo_fill }; + // Verilator lint_on UNUSED + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The following are only a subset of the formal properties currently + // being used to verify this module. They are not expected to be + // syntactically accurate. + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + // + // ... + // + + //////////////////////////////////////////////////////////////////////// + // + // Properties of the AXI-stream data interface + // {{{ + // + //////////////////////////////////////////////////////////////////////// + // + // + + // (These are captured by the FIFO within) + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, faxil_awr_outstanding; + + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXIL_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXIL_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(2), + .F_AXI_MAXDELAY(2), + .F_AXI_MAXRSTALL(3) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXIL_AWVALID), + .i_axi_awready(S_AXIL_AWREADY), + .i_axi_awaddr( S_AXIL_AWADDR), + .i_axi_awprot( S_AXIL_AWPROT), + // + .i_axi_wvalid(S_AXIL_WVALID), + .i_axi_wready(S_AXIL_WREADY), + .i_axi_wdata( S_AXIL_WDATA), + .i_axi_wstrb( S_AXIL_WSTRB), + // + .i_axi_bvalid(S_AXIL_BVALID), + .i_axi_bready(S_AXIL_BREADY), + .i_axi_bresp( S_AXIL_BRESP), + // + .i_axi_arvalid(S_AXIL_ARVALID), + .i_axi_arready(S_AXIL_ARREADY), + .i_axi_araddr( S_AXIL_ARADDR), + .i_axi_arprot( S_AXIL_ARPROT), + // + .i_axi_rvalid(S_AXIL_RVALID), + .i_axi_rready(S_AXIL_RREADY), + .i_axi_rdata( S_AXIL_RDATA), + .i_axi_rresp( S_AXIL_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + begin + assert(faxil_rd_outstanding == (S_AXIL_RVALID ? 1:0) + +(S_AXIL_ARREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXIL_BVALID ? 1:0) + +(S_AXIL_WREADY ? 0:1)); + assert(faxil_awr_outstanding== (S_AXIL_BVALID ? 1:0) + +(S_AXIL_AWREADY ? 0:1)); + end + + always @(*) + assert(cfg_zero_length == + ((cfg_line_words == 0)||(cfg_frame_lines == 0))); + + always @(*) + if (cfg_zero_length) + assert(!cfg_active); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The AXI master memory interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + localparam F_AXI_LGDEPTH = 11; // LGLENW-LGMAXBURST+2 ?? + + // + // ... + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + // + // ... + // + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // The (unused) write interface + // {{{ + .i_axi_awvalid(1'b0), + .i_axi_awready(1'b0), + .i_axi_awid( AXI_ID), + .i_axi_awaddr( 0), + .i_axi_awlen( 0), + .i_axi_awsize( 0), + .i_axi_awburst(0), + .i_axi_awlock( 0), + .i_axi_awcache(0), + .i_axi_awprot( 0), + .i_axi_awqos( 0), + // + .i_axi_wvalid(0), + .i_axi_wready(0), + .i_axi_wdata( 0), + .i_axi_wstrb( 0), + .i_axi_wlast( 0), + // + .i_axi_bvalid(1'b0), + .i_axi_bready(1'b0), + .i_axi_bid( AXI_ID), + .i_axi_bresp( 2'b00), + // }}} + // The read interface + // {{{ + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rlast( M_AXI_RLAST), + .i_axi_rresp( M_AXI_RRESP), + // ... + // }}} + ); + + + // + // ... + // + always @(*) + begin + // ... + + assert(M_AXI_ARBURST == 2'b01); + end + + always @(*) + if (faxi_rd_ckvalid) + begin + assert(!r_stopped); + // + // ... + // + end + + // + // ... + // + + reg [LGMAXBURST-1:0] f_rd_subaddr; + always @(*) + f_rd_subaddr = f_rd_addr[ADDRLSB +: LGMAXBURST]; + + always @(*) + begin + assert(cfg_frame_addr[ADDRLSB-1:0] == 0); + if (!r_stopped) + begin + assert(req_addr[ADDRLSB-1:0] == 0); + assert(req_line_addr[ADDRLSB-1:0] == 0); + end + + if (M_AXI_RVALID) + assert(M_AXI_RLAST == (rd_hlast || (&f_rd_subaddr))); + end + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read request to return address correlations + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + if (M_AXI_RVALID) + begin + if (rd_hlast || (&f_rd_subaddr)) + begin + // ... + assert(M_AXI_RLAST); + end else + // ... + assume(!M_AXI_RLAST); + end + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other formal properties + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + reg cvr_full_frame, cvr_full_size; + (* anyconst *) reg cvr_hlast_rlast; + + always @(posedge i_clk) + if (r_stopped) + cvr_full_size <= (cfg_line_step >= cfg_line_words) + &&(cfg_line_words > 5) + &&(cfg_frame_lines > 4); + else begin + if ($changed(cfg_line_step)) + cvr_full_size <= 0; + if ($changed(cfg_line_words)) + cvr_full_size <= 0; + if ($changed(cfg_frame_lines)) + cvr_full_size <= 0; + if ($changed(cfg_frame_addr)) + cvr_full_size <= 0; + end + + always @(posedge i_clk) + if (r_stopped || !cvr_full_size) + cvr_full_frame <= 0; + else if (!cvr_full_frame) + cvr_full_frame <= rd_vlast && rd_hlast && M_AXI_RVALID && M_AXI_RREADY; + + always @(*) + cover(!soft_reset); + + always @(*) + cover(start_burst); + + always @(*) + cover(M_AXI_ARVALID && M_AXI_ARREADY); + + always @(*) + cover(M_AXI_RVALID); + + always @(*) + cover(M_AXI_RVALID & M_AXI_RLAST); + + always @(*) + cover(!r_stopped && cvr_full_frame); + + always @(*) + cover(cvr_full_frame && phantom_start && !r_stopped); + + always @(*) + if (cvr_hlast_rlast) + begin + // assume(M_AXI_RLAST == (rd_hlast && M_AXI_RVALID)); + assume(M_AXI_ARREADY && M_AXI_RREADY); + assume(M_AXIS_TREADY); + assume(cfg_frame_addr[12:0] == 0); + assume(cfg_line_step[3:0] == 0); + end + + always @(*) + cover(cvr_hlast_rlast && cvr_full_frame && phantom_start && !r_stopped); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Simplifying (careless) assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge i_clk) + if (!r_stopped || cfg_active) + begin + assume($stable(cfg_frame_addr)); + assume($stable(cfg_frame_lines)); + assume($stable(cfg_line_words)); + assume($stable(cfg_line_step)); + end + + + always @(*) + assume(!f_sequential); + + always @(*) + assume(!f_biglines); + + always @(*) + assume(!req_addr[C_AXI_ADDR_WIDTH]); + + always @(*) + assume(!req_line_addr[C_AXI_ADDR_WIDTH]); + // }}} + // }}} +`endif +endmodule diff --git a/rtl/wb2axip/axivfifo.v b/rtl/wb2axip/axivfifo.v new file mode 100644 index 0000000..2dc5369 --- /dev/null +++ b/rtl/wb2axip/axivfifo.v @@ -0,0 +1,1405 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axivfifo.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A virtual FIFO, using an AXI based memory on the back end. Data +// written via the AXI stream interface is written to an external +// memory once enough is available to fill a burst. It's then copied from +// this external memory to a FIFO from which it can drive an outgoing +// stream. +// +// Registers: +// This core is simple--providing no control interface nor registers +// whereby it may be controlled. To place it in a particular region of +// SDRAM, limit the address width and fill the rest of the address with +// the region you want. Note: THIS CORE DEPENDS UPON ALIGNED MEMORY +// ACCESSES. Hence, it must be aligned to the memory to keep these +// accesses aligned. +// +// Performance goals: +// 100% throughput +// Stay off the bus until you can drive it hard +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// +// `define AXI3 +// }}} +module axivfifo #( + // {{{ + parameter C_AXI_ID_WIDTH = 1, + parameter C_AXI_ADDR_WIDTH = 32, + parameter C_AXI_DATA_WIDTH = 32, + // + // This core requires that the stream data width be identical + // to the bus data width. Use an upstream core if you need to + // pack a smaller width into your bus's width, or a downstream + // core if you need to unpack it. + localparam C_AXIS_DATA_WIDTH = C_AXI_DATA_WIDTH, + // + // LGMAXBURST determines the size of the maximum AXI burst. + // In AXI4, the maximum burst size is 256 beats the log_2() + // of which is 8. In AXI3, it's a 16 beat burst of which the + // log_2() is 4. Smaller numbers are also permissible here, + // although not verified. I expect problems if LGMAXBURST is + // ever set to zero (no bursting). An upgrade should fix that. + // Lower LGMAXBURST values will decrease the latency in this + // core while possibly causing throughput to be decreased + // (in the rest of the system--this core can handle 100% + // throughput either way.) + // + // Beware of the AXI requirement that bursts cannot cross + // 4kB boundaries. If your bus is larger than 128 bits wide, + // you'll need to lower this maximum burst size to meet that + // requirement. +`ifdef AXI3 + parameter LGMAXBURST=4, // 16 beats max +`else + parameter LGMAXBURST=8, // 256 beats +`endif + // + // LGFIFO: This is the (log-based-2) size of the internal FIFO. + // Hence if LGFIFO=8, the internal FIFO will have 256 elements + // (words) in it. High throughput transfers are accomplished + // by first storing data into a FIFO, then once a full burst + // size is available bursting that data over the bus. In + // order to be able to keep receiving data while bursting it + // out, the FIFO size must be at least twice the size of the + // maximum burst size--that is LGFIFO must be at least one more + // than LGMAXBURST. Larger sizes are possible as well. + parameter LGFIFO = LGMAXBURST+1, // 512 element FIFO + // + // AXI uses ID's to transfer information. This core rather + // ignores them. Instead, it uses a constant ID for all + // transfers. The following two parameters control that ID. + parameter [C_AXI_ID_WIDTH-1:0] AXI_READ_ID = 0, + parameter [C_AXI_ID_WIDTH-1:0] AXI_WRITE_ID = 0, + // + localparam ADDRLSB= $clog2(C_AXI_DATA_WIDTH)-3 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + // The AXI4-stream interface + // {{{ + // This core does not support TLAST, TKEEP, or TSTRB. If you + // want to support these extra values, expand the width of + // TDATA, and unpack them on the output of the FIFO. + // + // The incoming stream + input wire S_AXIS_TVALID, + output wire S_AXIS_TREADY, + input wire [C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA, + // + // The outgoing stream + output wire M_AXIS_TVALID, + input wire M_AXIS_TREADY, + output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA, + // }}} + // + // The AXI Master (DMA) interface + // {{{ + // First to write data to the (external) AXI buffer + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, +`ifdef AXI3 + output wire [3:0] M_AXI_AWLEN, +`else + output wire [7:0] M_AXI_AWLEN, +`endif + output wire [2:0] M_AXI_AWSIZE, + output wire [1:0] M_AXI_AWBURST, +`ifdef AXI3 + output wire [1:0] M_AXI_AWLOCK, +`else + output wire M_AXI_AWLOCK, +`endif + output wire [3:0] M_AXI_AWCACHE, + output wire [2:0] M_AXI_AWPROT, + output wire [3:0] M_AXI_AWQOS, + // + // + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, +`ifdef AXI3 + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_WID, +`endif + output wire [C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + // + // + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [1:0] M_AXI_BRESP, + // + // Then the read interface to read the data back + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + output wire [C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, +`ifdef AXI3 + output wire [3:0] M_AXI_ARLEN, +`else + output wire [7:0] M_AXI_ARLEN, +`endif + output wire [2:0] M_AXI_ARSIZE, + output wire [1:0] M_AXI_ARBURST, +`ifdef AXI3 + output wire [1:0] M_AXI_ARLOCK, +`else + output wire M_AXI_ARLOCK, +`endif + output wire [3:0] M_AXI_ARCACHE, + output wire [2:0] M_AXI_ARPROT, + output wire [3:0] M_AXI_ARQOS, + // + input wire M_AXI_RVALID, + output wire M_AXI_RREADY, + input wire [C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire M_AXI_RLAST, + input wire [1:0] M_AXI_RRESP, + // }}} + // + // {{{ + // Request a soft-reset: reset the FIFO without resetting th bus + input wire i_reset, + // o_err is a hard error. If ever true, the core will come + // to a hard stop. + output reg o_err, + // o_overflow is an indication of data changing before it is + // accepted. + output reg o_overflow, + // o_mm2s_full is a reference to the last FIFO in our + // processing pipeline. It's true if a burst of (1<<LGMAXBURST) + // words of (1<<ADDRLSB) bytes may be read from the downstream + // FIFO without waiting on the external memory. + output reg o_mm2s_full, + // o_empty is true if nothing is in the core. + output reg o_empty, + // o_fill counts the number of items in the core. Just because + // the number of items is non-zero, however, doesn't mean you + // can read them out. In general, you will need to write at + // least a full (1<<LGMAXBURST) words to the core, those will + // need to be written to memory, read from memory, and then + // used to fill the downstream FIFO before you can read. This + // number is just available for your informational use. + output reg [C_AXI_ADDR_WIDTH-ADDRLSB:0] o_fill + // }}} + // }}} + ); + + // Register and signal definitions + // {{{ + // The number of beats in this maximum burst size is automatically + // determined from LGMAXBURST, and so its forced to be a power of + // two this way. + localparam MAXBURST=(1<<LGMAXBURST); + localparam BURSTAW = C_AXI_ADDR_WIDTH-LGMAXBURST-ADDRLSB; + + reg soft_reset, vfifo_empty, vfifo_full; + wire reset_fifo; + reg [C_AXI_ADDR_WIDTH-ADDRLSB:0] vfifo_fill; + reg [BURSTAW:0] mem_data_available_w, + writes_outstanding; + reg [BURSTAW:0] mem_space_available_w, + reads_outstanding; + reg s_last_stalled; + reg [C_AXI_DATA_WIDTH-1:0] s_last_tdata; + wire read_from_fifo, ififo_full, ififo_empty; + wire [C_AXI_DATA_WIDTH-1:0] ififo_data; + wire [LGFIFO:0] ififo_fill; + + reg start_write, phantom_write, + axi_awvalid, axi_wvalid, axi_wlast, + writes_idle; + reg [C_AXI_ADDR_WIDTH-1:0] axi_awaddr; + reg [LGMAXBURST:0] writes_pending; + + reg start_read, phantom_read, reads_idle, + axi_arvalid; + reg [C_AXI_ADDR_WIDTH-1:0] axi_araddr; + + reg skd_valid; + reg [C_AXI_DATA_WIDTH-1:0] skd_data; + + reg [LGFIFO:0] ofifo_space_available; + wire write_to_fifo, ofifo_empty, ofifo_full; + wire [LGFIFO:0] ofifo_fill; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Global FIFO signal handling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // A soft reset + // {{{ + // This is how we reset the FIFO without resetting the rest of the AXI + // bus. On a reset request, we raise the soft_reset flag and reset all + // of our internal FIFOs. We also stop issuing bus commands. Once all + // outstanding bus commands come to a halt, then we release from reset + // and start operating as a FIFO. + initial soft_reset = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + soft_reset <= 0; + else if (i_reset) + soft_reset <= 1; + else if (writes_idle && reads_idle) + soft_reset <= 0; + + assign reset_fifo = soft_reset || !S_AXI_ARESETN; + // }}} + + // + // Calculating the fill of the virtual FIFO, and the associated + // full/empty flags that go with it + // {{{ + initial vfifo_fill = 0; + initial vfifo_empty = 1; + initial vfifo_full = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset) + begin + vfifo_fill <= 0; + vfifo_empty <= 1; + vfifo_full <= 0; + end else case({ S_AXIS_TVALID && S_AXIS_TREADY, + M_AXIS_TVALID && M_AXIS_TREADY }) + 2'b10: begin + vfifo_fill <= vfifo_fill + 1; + vfifo_empty <= 0; + vfifo_full <= (&vfifo_fill[C_AXI_ADDR_WIDTH-ADDRLSB-1:0]); + end + 2'b01: begin + vfifo_fill <= vfifo_fill - 1; + vfifo_full <= 0; + vfifo_empty<= (vfifo_fill <= 1); + end + default: begin end + endcase + + always @(*) + o_fill = vfifo_fill; + + always @(*) + o_empty = vfifo_empty; + // }}} + + // Determining when the write half is idle + // {{{ + // This is required to know when to come out of soft reset. + // + // The first step is to count the number of bursts that remain + // outstanding + initial writes_outstanding = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + writes_outstanding <= 0; + else case({ phantom_write,M_AXI_BVALID && M_AXI_BREADY}) + 2'b01: writes_outstanding <= writes_outstanding - 1; + 2'b10: writes_outstanding <= writes_outstanding + 1; + default: begin end + endcase + + // The second step is to use this counter to determine if we are idle. + // If WVALID is ever high, or start_write goes high, then we are + // obviously not idle. Otherwise, we become idle when the number of + // writes outstanding transitions to (or equals) zero. + initial writes_idle = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + writes_idle <= 1; + else if (start_write || M_AXI_WVALID) + writes_idle <= 0; + else + writes_idle <= (writes_outstanding + == ((M_AXI_BVALID && M_AXI_BREADY) ? 1:0)); + // }}} + + // Count how much space is used in the memory device + // {{{ + // Well, obviously, we can't fill our memory device or we have problems. + // To make sure we don't overflow, we count memory usage here. We'll + // count memory usage in units of bursts of (1<<LGMAXBURST) words of + // (1<<ADDRLSB) bytes each. So ... here we count the amount of device + // memory that hasn't (yet) been committed. This is different from the + // memory used (which we don't calculate), or the memory which may yet + // be read--which we'll calculate in a moment. + initial mem_space_available_w = (1<<BURSTAW); + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset) + mem_space_available_w <= (1<<BURSTAW); + else case({ phantom_write,M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST }) + 2'b01: mem_space_available_w <= mem_space_available_w + 1; + 2'b10: mem_space_available_w <= mem_space_available_w - 1; + default: begin end + endcase + // }}} + + // Determining when the read half is idle + // {{{ + // Count the number of read bursts that we've committed to. This + // includes bursts that have ARVALID but haven't been accepted, as well + // as any the downstream device will yet return an RLAST for. + initial reads_outstanding = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + reads_outstanding <= 0; + else case({ phantom_read,M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST}) + 2'b01: reads_outstanding <= reads_outstanding - 1; + 2'b10: reads_outstanding <= reads_outstanding + 1; + default: begin end + endcase + + // Now, using the reads_outstanding counter, we can check whether or not + // we are idle (and can exit a reset) of if instead there are more + // bursts outstanding to wait for. + // + // By registering this counter, we can keep the soft_reset release + // simpler. At least this way, it doesn't need to check two counters + // for zero. + initial reads_idle = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + reads_idle <= 1; + else if (start_read || M_AXI_ARVALID) + reads_idle <= 0; + else + reads_idle <= (reads_outstanding + == ((M_AXI_RVALID && M_AXI_RREADY && M_AXI_RLAST) ? 1:0)); + // }}} + + // Count how much data is in the memory device that we can read out + // {{{ + // In AXI, after you issue a write, you can't depend upon that data + // being present on the device and available for a read until the + // associated BVALID is returned. Therefore we don't count any memory + // as available to be read until BVALID comes back. Once a read + // command is issued, the memory is again no longer available to be + // read. Note also that we are counting bursts here. A second + // conversion below converts this count to bytes. + initial mem_data_available_w = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset) + mem_data_available_w <= 0; + else case({ M_AXI_BVALID, phantom_read }) + 2'b10: mem_data_available_w <= mem_data_available_w + 1; + 2'b01: mem_data_available_w <= mem_data_available_w - 1; + default: begin end + endcase + // }}} + + // + // Error detection + // {{{ + initial o_err = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + o_err <= 1'b0; + else begin + if (M_AXI_BVALID && M_AXI_BRESP[1]) + o_err <= 1'b1; + if (M_AXI_RVALID && M_AXI_RRESP[1]) + o_err <= 1'b1; + end + // }}} + + // + // Incoming stream overflow detection + // {{{ + // The overflow flag is set if ever an incoming value violates the + // stream protocol and changes while stalled. Internally, however, + // the overflow flag is ignored. It's provided for your information. + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + s_last_stalled <= 0; + else + s_last_stalled <= S_AXIS_TVALID && !S_AXIS_TREADY; + + always @(posedge S_AXI_ACLK) + if (S_AXIS_TVALID) + s_last_tdata <= S_AXIS_TDATA; + + initial o_overflow = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset) + o_overflow <= 0; + else if (s_last_stalled) + begin + if (!S_AXIS_TVALID) + o_overflow <= 1; + if (S_AXIS_TDATA != s_last_tdata) + o_overflow <= 1; + end + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Incoming FIFO + // {{{ + // Incoming data stream info the FIFO + // + //////////////////////////////////////////////////////////////////////// + // + // + assign S_AXIS_TREADY = !reset_fifo && !ififo_full && !vfifo_full; + assign read_from_fifo= (!skd_valid || (M_AXI_WVALID && M_AXI_WREADY)); + + sfifo #(.BW(C_AXIS_DATA_WIDTH), .LGFLEN(LGFIFO)) + ififo(S_AXI_ACLK, reset_fifo, + S_AXIS_TVALID && S_AXIS_TREADY, + S_AXIS_TDATA, ififo_full, ififo_fill, + read_from_fifo, ififo_data, ififo_empty); + + // + // We need a quick 1-element buffer here in order to keep the soft + // reset, which resets the FIFO pointer, from adjusting any FIFO data. + // {{{ + // Here's the rule: we need to fill the buffer before it ever gets + // used. Then, once used, it should be able to maintain 100% + // throughput. + initial skd_valid = 0; + always @(posedge S_AXI_ACLK) + if (reset_fifo) + skd_valid <= 0; + else if (!ififo_empty) + skd_valid <= 1; + else if (M_AXI_WVALID && M_AXI_WREADY) + skd_valid <= 0; + + always @(posedge S_AXI_ACLK) + if (!M_AXI_WVALID || M_AXI_WREADY) + begin + if (!skd_valid || M_AXI_WREADY) + skd_data <= ififo_data; + end + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // AXI write processing + // {{{ + // Write data from our FIFO onto the bus + // + //////////////////////////////////////////////////////////////////////// + // + // + + // start_write: determining when to issue a write burst + // {{{ + always @(*) + begin + start_write = 0; + + if (ififo_fill >= (1<<LGMAXBURST)) + start_write = 1; + if (vfifo_full || soft_reset || phantom_write) + start_write = 0; + if (mem_space_available_w == 0) + start_write = 0; + + if (M_AXI_WVALID && (!M_AXI_WREADY || !M_AXI_WLAST)) + start_write = 0; + if (M_AXI_AWVALID && !M_AXI_AWREADY) + start_write = 0; + if (o_err) + start_write = 0; + end + // }}} + + // Register the start write signal into AWVALID and phantom write + // {{{ + // phantom_write contains the start signal, but immediately clears + // on the next clock cycle. This allows us some time to calculate + // the data for the next burst which and if AWVALID remains high and + // not yet accepted. + initial phantom_write = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + phantom_write <= 0; + else + phantom_write <= start_write; + + // Set AWVALID to start_write if every the channel isn't stalled. + // Incidentally, start_write is guaranteed to be zero if the channel + // is stalled, since that signal is used by other things as well. + initial axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_awvalid <= 0; + else if (!M_AXI_AWVALID || M_AXI_AWREADY) + axi_awvalid <= start_write; + // }}} + + // Write address + // {{{ + // We insist on alignment. On every accepted burst, we step forward by + // one burst length. On reset, we reset the address at our first + // opportunity. + initial axi_awaddr = 0; + always @(posedge S_AXI_ACLK) + begin + if (M_AXI_AWVALID && M_AXI_AWREADY) + axi_awaddr[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB] + <= axi_awaddr[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB] +1; + + if ((!M_AXI_AWVALID || M_AXI_AWREADY) && soft_reset) + axi_awaddr <= 0; + + if (!S_AXI_ARESETN) + axi_awaddr <= 0; + + axi_awaddr[LGMAXBURST+ADDRLSB-1:0] <= 0; + end + // }}} + + // Write data channel valid + // {{{ + initial axi_wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_wvalid <= 0; + else if (start_write) + axi_wvalid <= 1; + else if (!M_AXI_WVALID || M_AXI_WREADY) + axi_wvalid <= M_AXI_WVALID && !M_AXI_WLAST; + // }}} + + // WLAST generation + // {{{ + // On the beginning of any burst, start a counter of the number of items + // in it. Once the counter gets to 1, set WLAST. + initial writes_pending = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + writes_pending <= 0; + else if (start_write) + writes_pending <= MAXBURST; + else if (M_AXI_WVALID && M_AXI_WREADY) + writes_pending <= writes_pending -1; + + always @(posedge S_AXI_ACLK) + if (start_write) + axi_wlast <= (LGMAXBURST == 0); + else if (!M_AXI_WVALID || M_AXI_WREADY) + axi_wlast <= (writes_pending == 1 + (M_AXI_WVALID ? 1:0)); + // }}} + + // Bus assignments based upon the above + // {{{ + assign M_AXI_AWVALID = axi_awvalid; + assign M_AXI_AWID = AXI_WRITE_ID; + assign M_AXI_AWADDR = axi_awaddr; + assign M_AXI_AWLEN = MAXBURST-1; + assign M_AXI_AWSIZE = ADDRLSB[2:0]; + assign M_AXI_AWBURST = 2'b01; + assign M_AXI_AWLOCK = 0; + assign M_AXI_AWCACHE = 0; + assign M_AXI_AWPROT = 0; + assign M_AXI_AWQOS = 0; + + assign M_AXI_WVALID = axi_wvalid; + assign M_AXI_WDATA = skd_data; +`ifdef AXI3 + assign M_AXI_WID = AXI_WRITE_ID; +`endif + assign M_AXI_WLAST = axi_wlast; + assign M_AXI_WSTRB = -1; + + assign M_AXI_BREADY = 1; + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // AXI read processing + // {{{ + // Read data into our FIFO + // + //////////////////////////////////////////////////////////////////////// + // + // + + // How much FIFO space is available? + // {{{ + // One we issue a read command, the FIFO space isn't available any more. + // That way we can determine when a second read can be issued--even + // before the first has returned--while also guaranteeing that there's + // always room in the outgoing FIFO for anything that might return. + // Remember: NEVER generate backpressure in a bus master + initial ofifo_space_available = (1<<LGFIFO); + always @(posedge S_AXI_ACLK) + if (reset_fifo) + ofifo_space_available <= (1<<LGFIFO); + else case({phantom_read, M_AXIS_TVALID && M_AXIS_TREADY}) + 2'b10: ofifo_space_available <= ofifo_space_available - MAXBURST; + 2'b01: ofifo_space_available <= ofifo_space_available + 1; + 2'b11: ofifo_space_available <= ofifo_space_available - MAXBURST + 1; + default: begin end + endcase + // }}} + + // Determine when to start a next read-from-memory-to-FIFO burst + // {{{ + always @(*) + begin + start_read = 1; + + // We can't read yet if we don't have space available. + // Note the comparison is carefully chosen to make certain + // it doesn't use all ofifo_space_available bits, but rather + // only the number of bits between LGFIFO and + // LGMAXBURST--nominally a single bit. + if (ofifo_space_available < MAXBURST) // FIFO space ? + start_read = 0; + + // If there's no memory available for us to read from, then + // we can't start a read yet. + if (!M_AXI_BVALID && mem_data_available_w == 0) + start_read = 0; + + // Don't start anything while waiting on a reset. Likewise, + // insist on a minimum one clock between read burst issuances. + if (soft_reset || phantom_read) + start_read = 0; + + // We can't start a read request if the AR* channel is stalled + if (M_AXI_ARVALID && !M_AXI_ARREADY) + start_read = 0; + + // Following a bus error, we come to a complete halt. Such a + // bus error is an indication that something *SERIOUSLY* went + // wrong--perhaps we aren't accessing the memory we are supposed + // to. To prevent damage to external devices, we disable + // ourselves entirely. There is no fall back. We only + // restart on a full bus restart. + if (o_err) + start_read = 0; + end + // }}} + + // Set phantom_read and ARVALID + // {{{ + initial phantom_read = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + phantom_read <= 0; + else + phantom_read <= start_read; + + initial axi_arvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arvalid <= 0; + else if (!M_AXI_ARVALID || M_AXI_ARREADY) + axi_arvalid <= start_read; + // }}} + + // Calculate the next ARADDR + // {{{ + initial axi_araddr = 0; + always @(posedge S_AXI_ACLK) + begin + if (M_AXI_ARVALID && M_AXI_ARREADY) + axi_araddr[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB] + <= axi_araddr[C_AXI_ADDR_WIDTH-1:LGMAXBURST+ADDRLSB]+ 1; + + if ((!M_AXI_ARVALID || M_AXI_ARREADY) && soft_reset) + axi_araddr <= 0; + + if (!S_AXI_ARESETN) + axi_araddr <= 0; + + axi_araddr[LGMAXBURST+ADDRLSB-1:0] <= 0; + end + // }}} + + // Assign values to our bus wires + // {{{ + assign M_AXI_ARVALID = axi_arvalid; + assign M_AXI_ARID = AXI_READ_ID; + assign M_AXI_ARADDR = axi_araddr; + assign M_AXI_ARLEN = MAXBURST-1; + assign M_AXI_ARSIZE = ADDRLSB[2:0]; + assign M_AXI_ARBURST = 2'b01; + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE = 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_ARQOS = 0; + + assign M_AXI_RREADY = 1; + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Outgoing AXI stream processing + // {{{ + // Send data out from the MM2S FIFO + // + //////////////////////////////////////////////////////////////////////// + // + // + + // We basically just stuff the data read from memory back into our + // outgoing FIFO here. The logic is quite straightforward. + assign write_to_fifo = M_AXI_RVALID && M_AXI_RREADY; + assign M_AXIS_TVALID = !ofifo_empty; + + sfifo #(.BW(C_AXIS_DATA_WIDTH), .LGFLEN(LGFIFO)) + ofifo(S_AXI_ACLK, reset_fifo, + write_to_fifo, + M_AXI_RDATA, ofifo_full, ofifo_fill, + M_AXIS_TVALID && M_AXIS_TREADY, M_AXIS_TDATA, ofifo_empty); + + always @(*) + o_mm2s_full = |ofifo_fill[LGFIFO:LGMAXBURST]; + // }}} + + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, M_AXI_BID, M_AXI_RID, + M_AXI_BRESP[0], M_AXI_RRESP[0], + ififo_empty, ofifo_full, ofifo_fill + // fifo_full, fifo_fill, fifo_empty, + }; + + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// {{{ +// +// The following are a subset of the formal properties used to verify this +// core. The full set of formal properties, together with the formal +// property set itself, are available for purchase. +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // FAXI_DEPTH controls the width of the counters in the bus property + // interface file. In order to support bursts of length 8 (or more), + // there's a minimum of 9. Otherwise, we'll just set this to the width + // of the AXI address bus. + localparam FAXI_DEPTH = (C_AXI_ADDR_WIDTH > 8) + ? (C_AXI_ADDR_WIDTH+1) : 9; + + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + // + // Wires necessary for interacting with the formal property file + // {{{ + // ... + // }}} + + // Other registers to keep simplify keeping track of our progress + // {{{ + reg [C_AXI_ADDR_WIDTH-1:0] faxi_rd_cknext, faxi_wr_cknext, + f_read_beat_addr, f_write_beat_addr, + faxi_read_beat_addr; + reg [C_AXI_ADDR_WIDTH-1:0] f_read_ckbeat_addr; + reg [FAXI_DEPTH-1:0] f_rd_bursts_after_check; + + reg [C_AXI_ADDR_WIDTH:0] f_vfill; + reg [C_AXI_ADDR_WIDTH:0] f_space_available, + f_data_available; + + reg [C_AXI_ADDR_WIDTH-1:0] f_read_checksum; + reg [C_AXI_ADDR_WIDTH:0] mem_space_available, mem_data_available; + // }}} + + + //////////////////////////////////////////////////////////////////////// + // + // The main AXI data interface bus property check + // + //////////////////////////////////////////////////////////////////////// + // + // + + // The number of beats in the maximum burst size is + // automatically determined from LGMAXBURST, and so its + // forced to be a power of two this way. + localparam MAXBURST=(1<<LGMAXBURST); + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .OPT_MAXBURST(MAXBURST-1), + .OPT_NARROW_BURST(1'b0), + .OPT_ASYNC_RESET(1'b0), // We don't use asynchronous resets + .OPT_EXCLUSIVE(1'b0), // We don't use the LOCK signal + .F_OPT_ASSUME_RESET(1'b1), // We aren't generating the reset + .F_OPT_NO_RESET(1'b1), + .F_LGDEPTH(FAXI_DEPTH) // Width of the counters + // }}} + ) faxi( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // Write signals + // {{{ + .i_axi_awvalid(M_AXI_AWVALID), + .i_axi_awready(M_AXI_AWREADY), + .i_axi_awid( M_AXI_AWID), + .i_axi_awaddr( M_AXI_AWADDR), + .i_axi_awlen( M_AXI_AWLEN), + .i_axi_awsize( M_AXI_AWSIZE), + .i_axi_awburst(M_AXI_AWBURST), + .i_axi_awlock( M_AXI_AWLOCK), + .i_axi_awcache(M_AXI_AWCACHE), + .i_axi_awprot( M_AXI_AWPROT), + .i_axi_awqos( M_AXI_AWQOS), + // + .i_axi_wvalid(M_AXI_WVALID), + .i_axi_wready(M_AXI_WREADY), + .i_axi_wdata( M_AXI_WDATA), + .i_axi_wstrb( M_AXI_WSTRB), + .i_axi_wlast( M_AXI_WLAST), + // + .i_axi_bvalid(M_AXI_BVALID), + .i_axi_bready(M_AXI_BREADY), + .i_axi_bid( M_AXI_BID), + .i_axi_bresp( M_AXI_BRESP), + // }}} + // Read signals + // {{{ + .i_axi_arvalid(M_AXI_ARVALID), + .i_axi_arready(M_AXI_ARREADY), + .i_axi_arid( M_AXI_ARID), + .i_axi_araddr( M_AXI_ARADDR), + .i_axi_arlen( M_AXI_ARLEN), + .i_axi_arsize( M_AXI_ARSIZE), + .i_axi_arburst(M_AXI_ARBURST), + .i_axi_arlock( M_AXI_ARLOCK), + .i_axi_arcache(M_AXI_ARCACHE), + .i_axi_arprot( M_AXI_ARPROT), + .i_axi_arqos( M_AXI_ARQOS), + // + // + .i_axi_rvalid(M_AXI_RVALID), + .i_axi_rready(M_AXI_RREADY), + .i_axi_rid( M_AXI_RID), + .i_axi_rdata( M_AXI_RDATA), + .i_axi_rlast( M_AXI_RLAST), + .i_axi_rresp( M_AXI_RRESP), + // }}} + // Induction signals + // {{{ + // ... + // }}} + // }}} + ); + + // Some quick sanity checks + // {{{ + always @(*) + begin + // + // ... + // + end + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Write sanity checking + // {{{ + always @(*) + mem_space_available = { mem_space_available_w, + {(LGMAXBURST+ADDRLSB){1'b0} } }; + + // Let's calculate the address of each write beat + // {{{ + initial f_write_beat_addr = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_write_beat_addr <= 0; + else if ((!M_AXI_WVALID || (M_AXI_WREADY && M_AXI_WLAST)) && soft_reset) + f_write_beat_addr <= 0; + else if (M_AXI_WVALID && M_AXI_WREADY) + f_write_beat_addr <= f_write_beat_addr + (1<<ADDRLSB); + // }}} + + // + // ... + // + + // Verify during any write burst that all the burst parameters are + // correct + // {{{ + // ... + // }}} + + always @(*) + if (M_AXI_AWVALID) + begin + assert(writes_pending == (1<<LGMAXBURST)); + // ... + end else + // ... + + always @(*) + assert(M_AXI_WVALID == (writes_pending != 0)); + + // + // ... + // + + // Check the writes-idle signal + // {{{ + // ... + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Read sanity checking + // {{{ + + always @(*) + mem_data_available = { mem_data_available_w, + {(LGMAXBURST+ADDRLSB){1'b0} } }; + // + // ... + // Check the reads-idle signal + // {{{ + // ... + // }}} + + // Regenerate the read beat address + // {{{ + // This works because we are using a single AXI ID for all of our reads. + // Therefore we can guarantee that reads will come back in order, thus + // we can count the return address here. + initial f_read_beat_addr = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_read_beat_addr <= 0; + else if (soft_reset && reads_idle) + f_read_beat_addr <= 0; + else if (M_AXI_RVALID && M_AXI_RREADY) + f_read_beat_addr <= f_read_beat_addr + (1<<ADDRLSB); + // }}} + + // Read burst checking + // {{{ + + // + // ... + // + + // }}} + + + // Match our read beat address to ARADDR + // {{{ + // ... + // }}} + + // Insist that our burst counters are consistent with bursts of a full + // length only + // {{{ + // ... + // }}} + + // RLAST checking + // {{{ + // ... + // }}} + + // Match the read beat address to the number of items remaining in this + // burst + // {{{ + // ... + // }}} + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Internal assertions (Induction) + // + //////////////////////////////////////////////////////////////////////// + // + // + + // + // On either error, or while waiting for a soft reset to complete + // nothing new should issue + // {{{ + always @(posedge S_AXI_ACLK) + if (f_past_valid && ($past(soft_reset) || $past(o_err))) + begin + assert(!$rose(M_AXI_AWVALID)); + assert(!$rose(M_AXI_WVALID)); + assert(!$rose(M_AXI_ARVALID)); + assert(!phantom_write); + assert(!phantom_read); + end + // }}} + + // + // Writes and reads always have enough room + + // Bus writes aren't allowed to drain the incoming FIFO dry + // {{{ + always @(posedge S_AXI_ACLK) + if (!soft_reset && M_AXI_WVALID) + assert(ififo_fill + (skd_valid ? 1:0) >= writes_pending); + // }}} + + // Bus reads aren't allowed to overflow the return FIFO + // {{{ + always @(posedge S_AXI_ACLK) + if (!soft_reset && M_AXI_RVALID) + assert(!ofifo_full); + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Write checks + // + + // Make sure the skid buffer only reads when either there's nothing + // held in the buffer, or the write channel has accepted data. Indeed, + // the write channel should never be active unless the skid buffer is + // full. + // {{{ + always @(*) + if (!soft_reset && !skd_valid) + assert(!M_AXI_WVALID); + + always @(*) + if (M_AXI_WVALID && M_AXI_WREADY) + begin + assert(read_from_fifo); + end else if (!skd_valid && !ififo_empty) + assert(read_from_fifo); + // }}} + + + //////////////////////////////////////////////////////////////////////// + // + // Read checks + // {{{ + + // No read checks in addition to the ones above + + // }}} + + //////////////////////////////////////// + // + // Errors or resets -- do we properly end gracefully + // + // {{{ + // Set o_err on any bus error. Only clear it on reset + // {{{ + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN)) + begin + if ($past(M_AXI_BVALID && M_AXI_BRESP[1])) + assert(o_err); + if ($past(M_AXI_RVALID && M_AXI_RRESP[1])) + assert(o_err); + if ($past(!M_AXI_BVALID || !M_AXI_BRESP[1]) + && $past(!M_AXI_RVALID || !M_AXI_RRESP[1])) + assert(!$rose(o_err)); + end + + // Only release soft_reset once the bus is idle + // {{{ + always @(posedge S_AXI_ACLK) + if (f_past_valid && $past(S_AXI_ARESETN) && $fell(soft_reset)) + begin + // + // ... + // + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assert(!M_AXI_ARVALID); + end + // }}} + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // FIFO checks + // {{{ + + // Check the global fill/emtpy values + // {{{ + always @(*) + assert(vfifo_full == (vfifo_fill == (1<<(C_AXI_ADDR_WIDTH-ADDRLSB)))); + + always @(*) + assert(vfifo_fill <= (1<<(C_AXI_ADDR_WIDTH-ADDRLSB))); + + always @(*) + assert(vfifo_empty == (vfifo_fill == 0)); + // }}} + + // An equation for vfill based upon our property checker's counters + // {{{ + always @(*) + begin + f_vfill = M_AXI_AWADDR - M_AXI_ARADDR; + f_vfill[C_AXI_ADDR_WIDTH] = 0; + if (!M_AXI_AWVALID) + f_vfill = f_vfill - (writes_pending << ADDRLSB); + // ... + if (skd_valid) + f_vfill = f_vfill + (1<<ADDRLSB); + f_vfill = f_vfill + ((ififo_fill + ofifo_fill)<<ADDRLSB); + end + // }}} + + // Make sure the virtual FIFO's fill matches that counter + // {{{ + always @(*) + if (!soft_reset) + begin + assert(vfifo_fill == (f_vfill >> ADDRLSB)); + assert(f_vfill <= (1<<(C_AXI_ADDR_WIDTH))); + + // Before the equation check, double check that we + // don't overflow any of our arithmetic. Then check our + // virtual FIFO's fill counter against the various internal + // FIFO fills and read counters. + + // These are just inequality checks, however, so we'll still + // need a full equation check elsewhere + // + // ... + // + end + + // Make certain we aren't overflowing in our subtraction above + always @(*) + if (M_AXI_ARVALID && !soft_reset) + assert(M_AXI_ARADDR != M_AXI_AWADDR); + // }}} + + // Check mem_space_available + // {{{ + always @(*) + begin + f_space_available = (M_AXI_AWADDR - M_AXI_ARADDR); + f_space_available[C_AXI_ADDR_WIDTH] = 0; + f_space_available = (1<<C_AXI_ADDR_WIDTH) - f_space_available; + if (M_AXI_AWVALID && !phantom_write) + f_space_available = f_space_available + - (1 << (LGMAXBURST+ADDRLSB)); + // + // ... + end + + always @(*) + begin + assert({ mem_data_available_w, {(LGMAXBURST){1'b0}} } + <= vfifo_fill); + // ... + assert(mem_data_available <= (1<<C_AXI_ADDR_WIDTH)); + assert(mem_space_available <= (1<<C_AXI_ADDR_WIDTH)); + if (!soft_reset) + begin + assert(mem_space_available == f_space_available); + + if (mem_space_available[C_AXI_ADDR_WIDTH]) + begin + assert(M_AXI_ARADDR == M_AXI_AWADDR); + assert(!M_AXI_AWVALID || phantom_write); + // ... + end + end + end + // }}} + + // Check mem_data_available + // {{{ + // First, generate an equation to describe it + always @(*) + begin + f_data_available = M_AXI_AWADDR - M_AXI_ARADDR; + f_data_available[C_AXI_ADDR_WIDTH] = 0; + // ... + if (M_AXI_ARVALID && !phantom_read) + f_data_available = f_data_available + - (1 << (ADDRLSB + LGMAXBURST)); + end + + // Then compare it against that equation + always @(*) + if (!soft_reset) + begin + assert(mem_data_available == f_data_available); + if (mem_data_available[C_AXI_ADDR_WIDTH]) + begin + assert(vfifo_fill[C_AXI_ADDR_WIDTH]); + assert(ofifo_empty); + end + end + // }}} + + // ofifo_space_available + // {{{ + always @(*) + if (!reset_fifo) + begin + // ... + + // Make sure we don't overflow above + assert(ofifo_space_available <= (1<<LGFIFO)); + end + // }}} + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Initial (only) constraints + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK) + if (!f_past_valid || $past(!S_AXI_ARESETN)) + begin + assert(!M_AXI_AWVALID); + assert(!M_AXI_WVALID); + assert(!M_AXI_ARVALID); + + assert(mem_space_available == (1<<C_AXI_ADDR_WIDTH)); + assert(mem_data_available == 0); + + assert(!phantom_read); + assert(!phantom_write); + + assert(vfifo_fill == 0); + end + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Formal contract checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // This core doesn't (yet) have any formal contract check + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [2:0] cvr_write_bursts, cvr_read_bursts; + (* anyconst *) reg cvr_high_speed; + + always @(posedge S_AXI_ACLK) + if (cvr_high_speed) + begin + assume(!$fell(S_AXIS_TVALID)); + // if (S_AXI_ARESETN && $past(S_AXIS_TVALID && !S_AXIS_TREADY)) + // assume(S_AXIS_TVALID && $stable(S_AXIS_TDATA)); + + assume(M_AXI_AWREADY || writes_pending > 0); + assume(M_AXIS_TREADY); + assume(M_AXI_WREADY); + assume(M_AXI_ARREADY); + end + + initial cvr_write_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset || o_err || o_overflow) + cvr_write_bursts <= 0; + else if (M_AXI_BVALID && !cvr_write_bursts[2]) + cvr_write_bursts <= cvr_write_bursts + 1; + + initial cvr_read_bursts = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || soft_reset || o_err || o_overflow) + cvr_read_bursts <= 0; + else if (M_AXI_RVALID && M_AXI_RLAST && !cvr_read_bursts[2]) + cvr_read_bursts <= cvr_read_bursts + 1; + + // + // ... + // + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && !soft_reset) + cover(cvr_read_bursts > 1 && cvr_write_bursts > 1); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && !soft_reset) + cover(cvr_read_bursts > 1 && cvr_write_bursts > 1 + && cvr_high_speed); + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Careless assumptions (i.e. constraints) + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // No careless assumptions. Indeed, no assumptions at all beyond + // what's in the bus property file, and some optional cover assumptions. + + // }}} + // }}} +`endif +endmodule diff --git a/rtl/wb2axip/axixbar.v b/rtl/wb2axip/axixbar.v new file mode 100644 index 0000000..f2e1ac6 --- /dev/null +++ b/rtl/wb2axip/axixbar.v @@ -0,0 +1,2585 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axixbar.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Create a full crossbar between NM AXI sources (masters), and NS +// AXI slaves. Every master can talk to any slave, provided it +// isn't already busy. +// {{{ +// Performance: This core has been designed with the goal of being able to push +// one transaction through the interconnect, from any master to +// any slave, per clock cycle. This may perhaps be its most unique +// feature. While throughput is good, latency is something else. +// +// The arbiter requires a clock to switch, then another clock to send data +// downstream. This creates a minimum two clock latency up front. The +// return path suffers another clock of latency as well, placing the +// minimum latency at four clocks. The minimum write latency is at +// least one clock longer, since the write data must wait for the write +// address before proceeeding. +// +// Note that this arbiter only forwards AxID fields. It does not use +// them in arbitration. As a result, only one master may ever make +// requests of any given slave at a time. All responses from a slave +// will be returned to that known master. This is a known limitation in +// this implementation which will be fixed (in time) with funding and +// interest. Until that time, in order for a second master to access +// a given slave, the first master must receive all of its acknowledgments. +// +// Usage: To use, you must first set NM and NS to the number of masters +// and the number of slaves you wish to connect to. You then need to +// adjust the addresses of the slaves, found SLAVE_ADDR array. Those +// bits that are relevant in SLAVE_ADDR to then also be set in SLAVE_MASK. +// Adjusting the data and address widths go without saying. +// +// Lower numbered masters are given priority in any "fight". +// +// Channel grants are given on the condition that 1) they are requested, +// 2) no other channel has a grant, 3) all of the responses have been +// received from the current channel, and 4) the internal counters are +// not overflowing. +// +// The core limits the number of outstanding transactions on any channel to +// 1<<LGMAXBURST-1. +// +// Channel grants are lost 1) after OPT_LINGER clocks of being idle, or +// 2) when another master requests an idle (but still lingering) channel +// assignment, or 3) once all the responses have been returned to the +// current channel, and the current master is requesting another channel. +// +// A special slave is allocated for the case of no valid address. +// +// Since the write channel has no address information, the write data +// channel always be delayed by at least one clock from the write address +// channel. +// +// If OPT_LOWPOWER is set, then unused values will be set to zero. +// This can also be used to help identify relevant values within any +// trace. +// +// Known issues: This module can be a challenge to wire up. +// +// In order to keep the build lint clean, it's important that every +// port be connected. In order to be flexible regarding the number of +// ports that can be connected, the various AXI signals, whether input +// or output, have been concatenated together across either all masters +// or all slaves. This can make the design a lesson in tediousness to +// wire up. +// +// I commonly wire this crossbar up using AutoFPGA--just to make certain +// that I do it right and don't make mistakes when wiring it up. This +// also handles the tediousness involved. +// +// I have also done this by hand. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// }}} +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module axixbar #( + // {{{ + parameter integer C_AXI_DATA_WIDTH = 32, + parameter integer C_AXI_ADDR_WIDTH = 32, + parameter integer C_AXI_ID_WIDTH = 2, + // + // NM is the number of masters driving incoming slave channels + parameter NM = 4, + // + // NS is the number of slaves connected to the crossbar, driven + // by the master channels output from this IP. + parameter NS = 8, + // + // SLAVE_ADDR is an array of addresses, describing each of + // {{{ + // the slave channels. It works tightly with SLAVE_MASK, + // so that when (ADDR & MASK == ADDR), the channel in question + // has been requested. + // + // It is an internal in the setup of this core to doubly map + // an address, such that (addr & SLAVE_MASK[k])==SLAVE_ADDR[k] + // for two separate values of k. + // + // Any attempt to access an address that is a hole in this + // address list will result in a returned xRESP value of + // INTERCONNECT_ERROR (2'b11) + // + // NOTE: This is only a nominal address set. I expect that + // any design using the crossbar will need to adjust both + // SLAVE_ADDR and SLAVE_MASK, if not also NM and NS. + parameter [NS*C_AXI_ADDR_WIDTH-1:0] SLAVE_ADDR = { + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b110, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b101, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b100, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b011, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b010, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 4'b0001, {(C_AXI_ADDR_WIDTH-4){1'b0}}, + 4'b0000, {(C_AXI_ADDR_WIDTH-4){1'b0}} }, + // }}} + // + // SLAVE_MASK: is an array, much like SLAVE_ADDR, describing + // {{{ + // which of the bits in SLAVE_ADDR are relevant. It is + // important to maintain for every slave that + // (~SLAVE_MASK[i] & SLAVE_ADDR[i]) == 0. + // + // NOTE: This value should be overridden by any implementation. + // Verilator lint_off WIDTH + parameter [NS*C_AXI_ADDR_WIDTH-1:0] SLAVE_MASK = { + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 3'b111, {(C_AXI_ADDR_WIDTH-3){1'b0}}, + 4'b1111, {(C_AXI_ADDR_WIDTH-4){1'b0}}, + 4'b1111, {(C_AXI_ADDR_WIDTH-4){1'b0}} }, + // Verilator lint_on WIDTH + // }}} + // + // OPT_LOWPOWER: If set, it forces all unused values to zero, + // {{{ + // preventing them from unnecessarily toggling. This will + // raise the logic count of the core, but might also lower + // the power used by the interconnect and the bus driven wires + // which (in my experience) tend to have a high fan out. + parameter [0:0] OPT_LOWPOWER = 0, + // }}} + // + // OPT_LINGER: Set this to the number of clocks an idle + // {{{ + // channel shall be left open before being closed. Once + // closed, it will take a minimum of two clocks before the + // channel can be opened and data transmitted through it again. + parameter OPT_LINGER = 4, + // }}} + // + // [EXPERIMENTAL] OPT_QOS: If set, the QOS transmission values + // {{{ + // will be honored when determining who wins arbitration for + // accessing a given slave. (This feature has not yet been + // verified) + parameter [0:0] OPT_QOS = 0, + // }}} + // + // LGMAXBURST: Specifies the log based two of the maximum + // {{{ + // number of bursts transactions that may be outstanding at any + // given time. This is different from the maximum number of + // outstanding beats. + parameter LGMAXBURST = 3 + // }}} + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // Write slave channels from the controlling AXI masters + // {{{ + input wire [NM-1:0] S_AXI_AWVALID, + output wire [NM-1:0] S_AXI_AWREADY, + input wire [NM*C_AXI_ID_WIDTH-1:0] S_AXI_AWID, + input wire [NM*C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [NM*8-1:0] S_AXI_AWLEN, + input wire [NM*3-1:0] S_AXI_AWSIZE, + input wire [NM*2-1:0] S_AXI_AWBURST, + // Verilator coverage_off + input wire [NM-1:0] S_AXI_AWLOCK, + input wire [NM*4-1:0] S_AXI_AWCACHE, + input wire [NM*3-1:0] S_AXI_AWPROT, + input wire [NM*4-1:0] S_AXI_AWQOS, + // Verilator coverage_on + // + input wire [NM-1:0] S_AXI_WVALID, + output wire [NM-1:0] S_AXI_WREADY, + input wire [NM*C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [NM*C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + input wire [NM-1:0] S_AXI_WLAST, + // + output wire [NM-1:0] S_AXI_BVALID, + input wire [NM-1:0] S_AXI_BREADY, + output wire [NM*C_AXI_ID_WIDTH-1:0] S_AXI_BID, + output wire [NM*2-1:0] S_AXI_BRESP, + // }}} + // Read slave channels from the controlling AXI masters + // {{{ + input wire [NM-1:0] S_AXI_ARVALID, + output wire [NM-1:0] S_AXI_ARREADY, + input wire [NM*C_AXI_ID_WIDTH-1:0] S_AXI_ARID, + input wire [NM*C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [NM*8-1:0] S_AXI_ARLEN, + input wire [NM*3-1:0] S_AXI_ARSIZE, + input wire [NM*2-1:0] S_AXI_ARBURST, + // Verilator coverage_off + input wire [NM-1:0] S_AXI_ARLOCK, + input wire [NM*4-1:0] S_AXI_ARCACHE, + input wire [NM*3-1:0] S_AXI_ARPROT, + input wire [NM*4-1:0] S_AXI_ARQOS, + // Verilator coverage_on + // + output wire [NM-1:0] S_AXI_RVALID, + input wire [NM-1:0] S_AXI_RREADY, + output wire [NM*C_AXI_ID_WIDTH-1:0] S_AXI_RID, + output wire [NM*C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [NM*2-1:0] S_AXI_RRESP, + output wire [NM-1:0] S_AXI_RLAST, + // }}} + // Write channel master outputs to the connected AXI slaves + // {{{ + output wire [NS-1:0] M_AXI_AWVALID, + input wire [NS-1:0] M_AXI_AWREADY, + output wire [NS*C_AXI_ID_WIDTH-1:0] M_AXI_AWID, + output wire [NS*C_AXI_ADDR_WIDTH-1:0] M_AXI_AWADDR, + output wire [NS*8-1:0] M_AXI_AWLEN, + output wire [NS*3-1:0] M_AXI_AWSIZE, + output wire [NS*2-1:0] M_AXI_AWBURST, + // Verilator coverage_off + output wire [NS-1:0] M_AXI_AWLOCK, + output wire [NS*4-1:0] M_AXI_AWCACHE, + output wire [NS*3-1:0] M_AXI_AWPROT, + output wire [NS*4-1:0] M_AXI_AWQOS, + // Verilator coverage_on + // + // + output wire [NS-1:0] M_AXI_WVALID, + input wire [NS-1:0] M_AXI_WREADY, + output wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_WDATA, + output wire [NS*C_AXI_DATA_WIDTH/8-1:0] M_AXI_WSTRB, + output wire [NS-1:0] M_AXI_WLAST, + // + input wire [NS-1:0] M_AXI_BVALID, + output wire [NS-1:0] M_AXI_BREADY, + input wire [NS*C_AXI_ID_WIDTH-1:0] M_AXI_BID, + input wire [NS*2-1:0] M_AXI_BRESP, + // }}} + // Read channel master outputs to the connected AXI slaves + // {{{ + output wire [NS-1:0] M_AXI_ARVALID, + input wire [NS-1:0] M_AXI_ARREADY, + output wire [NS*C_AXI_ID_WIDTH-1:0] M_AXI_ARID, + output wire [NS*C_AXI_ADDR_WIDTH-1:0] M_AXI_ARADDR, + output wire [NS*8-1:0] M_AXI_ARLEN, + output wire [NS*3-1:0] M_AXI_ARSIZE, + output wire [NS*2-1:0] M_AXI_ARBURST, + // Verilator coverage_off + output wire [NS-1:0] M_AXI_ARLOCK, + output wire [NS*4-1:0] M_AXI_ARCACHE, + output wire [NS*4-1:0] M_AXI_ARQOS, + output wire [NS*3-1:0] M_AXI_ARPROT, + // Verilator coverage_on + // + // + input wire [NS-1:0] M_AXI_RVALID, + output wire [NS-1:0] M_AXI_RREADY, + input wire [NS*C_AXI_ID_WIDTH-1:0] M_AXI_RID, + input wire [NS*C_AXI_DATA_WIDTH-1:0] M_AXI_RDATA, + input wire [NS*2-1:0] M_AXI_RRESP, + input wire [NS-1:0] M_AXI_RLAST + // }}} + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Internal signal declarations and definitions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Local parameters, derived from those above + // {{{ + // IW, AW, and DW, are short-hand abbreviations used locally. + localparam IW = C_AXI_ID_WIDTH; + localparam AW = C_AXI_ADDR_WIDTH; + localparam DW = C_AXI_DATA_WIDTH; + // LGLINGER tells us how many bits we need for counting how long + // to keep an udle channel open. + localparam LGLINGER = (OPT_LINGER>1) ? $clog2(OPT_LINGER+1) : 1; + // + localparam LGNM = (NM>1) ? $clog2(NM) : 1; + localparam LGNS = (NS>1) ? $clog2(NS+1) : 1; + // + // In order to use indexes, and hence fully balanced mux trees, it helps + // to make certain that we have a power of two based lookup. NMFULL + // is the number of masters in this lookup, with potentially some + // unused extra ones. NSFULL is defined similarly. + localparam NMFULL = (NM>1) ? (1<<LGNM) : 1; + localparam NSFULL = (NS>1) ? (1<<LGNS) : 2; + // + localparam [1:0] INTERCONNECT_ERROR = 2'b11; + // + // OPT_SKID_INPUT controls whether the input skid buffers register + // their outputs or not. If set, all skid buffers will cost one more + // clock of latency. It's not clear that there's a performance gain + // to be had by setting this. + localparam [0:0] OPT_SKID_INPUT = 0; + // + // OPT_BUFFER_DECODER determines whether or not the outputs of the + // address decoder will be buffered or not. If buffered, there will + // be an extra (registered) clock delay on each of the A* channels from + // VALID to issue. + localparam [0:0] OPT_BUFFER_DECODER = 1; + // + // OPT_AWW controls whether or not a W* beat may be issued to a slave + // at the same time as the first AW* beat gets sent to the slave. Set + // to 1'b1 for lower latency, at the potential cost of a greater + // combinatorial path length + localparam OPT_AWW = 1'b1; + // }}} + + genvar N,M; + integer iN, iM; + + reg [NSFULL-1:0] wrequest [0:NM-1]; + reg [NSFULL-1:0] rrequest [0:NM-1]; + reg [NSFULL-1:0] wrequested [0:NM]; + reg [NSFULL-1:0] rrequested [0:NM]; + reg [NS:0] wgrant [0:NM-1]; + reg [NS:0] rgrant [0:NM-1]; + reg [NM-1:0] mwgrant; + reg [NM-1:0] mrgrant; + reg [NS-1:0] swgrant; + reg [NS-1:0] srgrant; + + // verilator lint_off UNUSED + wire [LGMAXBURST-1:0] w_mawpending [0:NM-1]; + wire [LGMAXBURST-1:0] wlasts_pending [0:NM-1]; + wire [LGMAXBURST-1:0] w_mrpending [0:NM-1]; + // verilator lint_on UNUSED + reg [NM-1:0] mwfull; + reg [NM-1:0] mrfull; + reg [NM-1:0] mwempty; + reg [NM-1:0] mrempty; + // + wire [LGNS-1:0] mwindex [0:NMFULL-1]; + wire [LGNS-1:0] mrindex [0:NMFULL-1]; + wire [LGNM-1:0] swindex [0:NSFULL-1]; + wire [LGNM-1:0] srindex [0:NSFULL-1]; + + wire [NM-1:0] wdata_expected; + + // The shadow buffers + wire [NMFULL-1:0] m_awvalid, m_arvalid; + wire [NMFULL-1:0] m_wvalid; + wire [NM-1:0] dcd_awvalid, dcd_arvalid; + + wire [C_AXI_ID_WIDTH-1:0] m_awid [0:NMFULL-1]; + wire [C_AXI_ADDR_WIDTH-1:0] m_awaddr [0:NMFULL-1]; + wire [7:0] m_awlen [0:NMFULL-1]; + wire [2:0] m_awsize [0:NMFULL-1]; + wire [1:0] m_awburst [0:NMFULL-1]; + wire [NMFULL-1:0] m_awlock; + wire [3:0] m_awcache [0:NMFULL-1]; + wire [2:0] m_awprot [0:NMFULL-1]; + wire [3:0] m_awqos [0:NMFULL-1]; + // + wire [C_AXI_DATA_WIDTH-1:0] m_wdata [0:NMFULL-1]; + wire [C_AXI_DATA_WIDTH/8-1:0] m_wstrb [0:NMFULL-1]; + wire [NMFULL-1:0] m_wlast; + + wire [C_AXI_ID_WIDTH-1:0] m_arid [0:NMFULL-1]; + wire [C_AXI_ADDR_WIDTH-1:0] m_araddr [0:NMFULL-1]; + wire [8-1:0] m_arlen [0:NMFULL-1]; + wire [3-1:0] m_arsize [0:NMFULL-1]; + wire [2-1:0] m_arburst [0:NMFULL-1]; + wire [NMFULL-1:0] m_arlock; + wire [4-1:0] m_arcache [0:NMFULL-1]; + wire [2:0] m_arprot [0:NMFULL-1]; + wire [3:0] m_arqos [0:NMFULL-1]; + // + // + reg [NM-1:0] berr_valid; + reg [IW-1:0] berr_id [0:NM-1]; + // + reg [NM-1:0] rerr_none; + reg [NM-1:0] rerr_last; + reg [8:0] rerr_outstanding [0:NM-1]; + reg [IW-1:0] rerr_id [0:NM-1]; + + wire [NM-1:0] skd_awvalid, skd_awstall; + wire [NM-1:0] skd_arvalid, skd_arstall; + wire [IW-1:0] skd_awid [0:NM-1]; + wire [AW-1:0] skd_awaddr [0:NM-1]; + wire [8-1:0] skd_awlen [0:NM-1]; + wire [3-1:0] skd_awsize [0:NM-1]; + wire [2-1:0] skd_awburst [0:NM-1]; + wire [NM-1:0] skd_awlock; + wire [4-1:0] skd_awcache [0:NM-1]; + wire [3-1:0] skd_awprot [0:NM-1]; + wire [4-1:0] skd_awqos [0:NM-1]; + // + wire [IW-1:0] skd_arid [0:NM-1]; + wire [AW-1:0] skd_araddr [0:NM-1]; + wire [8-1:0] skd_arlen [0:NM-1]; + wire [3-1:0] skd_arsize [0:NM-1]; + wire [2-1:0] skd_arburst [0:NM-1]; + wire [NM-1:0] skd_arlock; + wire [4-1:0] skd_arcache [0:NM-1]; + wire [3-1:0] skd_arprot [0:NM-1]; + wire [4-1:0] skd_arqos [0:NM-1]; + + // Verilator lint_off UNUSED + reg [NSFULL-1:0] m_axi_awvalid; + reg [NSFULL-1:0] m_axi_awready; + reg [IW-1:0] m_axi_awid [0:NSFULL-1]; + reg [7:0] m_axi_awlen [0:NSFULL-1]; + + reg [NSFULL-1:0] m_axi_wvalid; + reg [NSFULL-1:0] m_axi_wready; + reg [NSFULL-1:0] m_axi_bvalid; + reg [NSFULL-1:0] m_axi_bready; + // Verilator lint_on UNUSED + reg [1:0] m_axi_bresp [0:NSFULL-1]; + reg [IW-1:0] m_axi_bid [0:NSFULL-1]; + + // Verilator lint_off UNUSED + reg [NSFULL-1:0] m_axi_arvalid; + reg [7:0] m_axi_arlen [0:NSFULL-1]; + reg [IW-1:0] m_axi_arid [0:NSFULL-1]; + reg [NSFULL-1:0] m_axi_arready; + // Verilator lint_on UNUSED + reg [NSFULL-1:0] m_axi_rvalid; + // Verilator lint_off UNUSED + reg [NSFULL-1:0] m_axi_rready; + // Verilator lint_on UNUSED + // + reg [IW-1:0] m_axi_rid [0:NSFULL-1]; + reg [DW-1:0] m_axi_rdata [0:NSFULL-1]; + reg [NSFULL-1:0] m_axi_rlast; + reg [2-1:0] m_axi_rresp [0:NSFULL-1]; + + reg [NM-1:0] slave_awaccepts; + reg [NM-1:0] slave_waccepts; + reg [NM-1:0] slave_raccepts; + + reg [NM-1:0] bskd_valid; + reg [NM-1:0] rskd_valid, rskd_rlast; + wire [NM-1:0] bskd_ready; + wire [NM-1:0] rskd_ready; + + wire [NMFULL-1:0] write_qos_lockout, + read_qos_lockout; + + reg [NSFULL-1:0] slave_awready, slave_wready, slave_arready; + + // m_axi_* convenience signals (write side) + // {{{ + always @(*) + begin + m_axi_awvalid = -1; + m_axi_awready = -1; + m_axi_wvalid = -1; + m_axi_wready = -1; + m_axi_bvalid = 0; + m_axi_bready = -1; + + m_axi_awvalid[NS-1:0] = M_AXI_AWVALID; + m_axi_awready[NS-1:0] = M_AXI_AWREADY; + m_axi_wvalid[NS-1:0] = M_AXI_WVALID; + m_axi_wready[NS-1:0] = M_AXI_WREADY; + m_axi_bvalid[NS-1:0] = M_AXI_BVALID; + m_axi_bready[NS-1:0] = M_AXI_BREADY; + + for(iM=0; iM<NS; iM=iM+1) + begin + m_axi_awid[iM] = M_AXI_AWID[ iM*IW +: IW]; + m_axi_awlen[iM] = M_AXI_AWLEN[ iM* 8 +: 8]; + + m_axi_bid[iM] = M_AXI_BID[iM* IW +: IW]; + m_axi_bresp[iM] = M_AXI_BRESP[iM* 2 +: 2]; + + m_axi_rid[iM] = M_AXI_RID[ iM*IW +: IW]; + m_axi_rdata[iM] = M_AXI_RDATA[iM*DW +: DW]; + m_axi_rresp[iM] = M_AXI_RRESP[iM* 2 +: 2]; + m_axi_rlast[iM] = M_AXI_RLAST[iM]; + end + for(iM=NS; iM<NSFULL; iM=iM+1) + begin + m_axi_awid[iM] = 0; + m_axi_awlen[iM] = 0; + + m_axi_bresp[iM] = INTERCONNECT_ERROR; + m_axi_bid[iM] = 0; + + m_axi_rid[iM] = 0; + m_axi_rdata[iM] = 0; + m_axi_rresp[iM] = INTERCONNECT_ERROR; + m_axi_rlast[iM] = 1; + end + end + // }}} + + // m_axi_* convenience signals (read side) + // {{{ + always @(*) + begin + m_axi_arvalid = 0; + m_axi_arready = 0; + m_axi_rvalid = 0; + m_axi_rready = 0; + for(iM=0; iM<NS; iM=iM+1) + begin + m_axi_arlen[iM] = M_AXI_ARLEN[iM* 8 +: 8]; + m_axi_arid[iM] = M_AXI_ARID[ iM*IW +: IW]; + end + for(iM=NS; iM<NSFULL; iM=iM+1) + begin + m_axi_arlen[iM] = 0; + m_axi_arid[iM] = 0; + end + + m_axi_arvalid[NS-1:0] = M_AXI_ARVALID; + m_axi_arready[NS-1:0] = M_AXI_ARREADY; + m_axi_rvalid[NS-1:0] = M_AXI_RVALID; + m_axi_rready[NS-1:0] = M_AXI_RREADY; + end + // }}} + + // slave_*ready convenience signals + // {{{ + always @(*) + begin + // These are designed to keep us from doing things like + // m_axi_*[m?index[N]] && m_axi_*[m?index[N]] && .. etc + // + // First, we'll set bits for all slaves--to include those that + // are undefined (but required by our static analysis tools). + slave_awready = -1; + slave_wready = -1; + slave_arready = -1; + // + // Here we do all of the combinatoric calculations, so the + // master only needs to reference one bit of this signal + slave_awready[NS-1:0] = (~M_AXI_AWVALID | M_AXI_AWREADY); + slave_wready[NS-1:0] = (~M_AXI_WVALID | M_AXI_WREADY); + slave_arready[NS-1:0] = (~M_AXI_ARVALID | M_AXI_ARREADY); + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Process our incoming signals: AW*, W*, and AR* + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate for(N=0; N<NM; N=N+1) + begin : W1_DECODE_WRITE_REQUEST + // {{{ + wire [NS:0] wdecode; + + // awskid, the skidbuffer for the incoming AW* channel + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2+1+4+3+4), + .OPT_OUTREG(OPT_SKID_INPUT) + // }}} + ) awskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID[N]), .o_ready(S_AXI_AWREADY[N]), + .i_data( + { S_AXI_AWID[N*IW +: IW], S_AXI_AWADDR[N*AW +: AW], + S_AXI_AWLEN[N*8 +: 8], S_AXI_AWSIZE[N*3 +: 3], + S_AXI_AWBURST[N*2 +: 2], S_AXI_AWLOCK[N], + S_AXI_AWCACHE[N*4 +: 4], S_AXI_AWPROT[N*3 +: 3], + S_AXI_AWQOS[N*4 +: 4] }), + .o_valid(skd_awvalid[N]), .i_ready(!skd_awstall[N]), + .o_data( + { skd_awid[N], skd_awaddr[N], skd_awlen[N], + skd_awsize[N], skd_awburst[N], skd_awlock[N], + skd_awcache[N], skd_awprot[N], skd_awqos[N] }) + // }}} + ); + // }}} + + // wraddr, decode the write channel's address request to a + // particular slave index + // {{{ + addrdecode #( + // {{{ + .AW(AW), .DW(IW+8+3+2+1+4+3+4), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(OPT_BUFFER_DECODER) + // }}} + ) wraddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(skd_awvalid[N]), .o_stall(skd_awstall[N]), + .i_addr(skd_awaddr[N]), .i_data({ skd_awid[N], + skd_awlen[N], skd_awsize[N], skd_awburst[N], + skd_awlock[N], skd_awcache[N], skd_awprot[N], + skd_awqos[N] }), + .o_valid(dcd_awvalid[N]), + .i_stall(!dcd_awvalid[N]||!slave_awaccepts[N]), + .o_decode(wdecode), .o_addr(m_awaddr[N]), + .o_data({ m_awid[N], m_awlen[N], m_awsize[N], + m_awburst[N], m_awlock[N], m_awcache[N], + m_awprot[N], m_awqos[N]}) + // }}} + ); + // }}} + + // wskid, the skid buffer for the incoming W* channel + // {{{ + skidbuffer #( + // {{{ + .DW(DW+DW/8+1), + .OPT_OUTREG(OPT_SKID_INPUT || OPT_BUFFER_DECODER) + // }}} + ) wskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_WVALID[N]), .o_ready(S_AXI_WREADY[N]), + .i_data( + { S_AXI_WDATA[N*DW +: DW], S_AXI_WSTRB[N*DW/8 +: DW/8], + S_AXI_WLAST[N] }), + .o_valid(m_wvalid[N]), .i_ready(slave_waccepts[N]), + .o_data({ m_wdata[N], m_wstrb[N], m_wlast[N] }) + // }}} + ); + // }}} + + // slave_awaccepts + // {{{ + always @(*) + begin + slave_awaccepts[N] = 1'b1; + + // Cannot accept/forward a packet without a bus grant + // This handles whether or not write data is still + // pending. + if (!mwgrant[N]) + slave_awaccepts[N] = 1'b0; + if (write_qos_lockout[N]) + slave_awaccepts[N] = 1'b0; + if (mwfull[N]) + slave_awaccepts[N] = 1'b0; + // Don't accept a packet unless its to the same slave + // the grant is issued for + if (!wrequest[N][mwindex[N]]) + slave_awaccepts[N] = 1'b0; + if (!wgrant[N][NS]) + begin + if (!slave_awready[mwindex[N]]) + slave_awaccepts[N] = 1'b0; + end else if (berr_valid[N] && !bskd_ready[N]) + begin + // Can't accept an write address channel request + // for the no-address-mapped channel if the + // B* channel is stalled, lest we lose the ID + // of the transaction + // + // !berr_valid[N] => we have to accept more + // write data before we can issue BVALID + slave_awaccepts[N] = 1'b0; + end + end + // }}} + + // slave_waccepts + // {{{ + always @(*) + begin + slave_waccepts[N] = 1'b1; + if (!mwgrant[N]) + slave_waccepts[N] = 1'b0; + if (!wdata_expected[N] && (!OPT_AWW || !slave_awaccepts[N])) + slave_waccepts[N] = 1'b0; + if (!wgrant[N][NS]) + begin + if (!slave_wready[mwindex[N]]) + slave_waccepts[N] = 1'b0; + end else if (berr_valid[N] && !bskd_ready[N]) + slave_waccepts[N] = 1'b0; + end + // }}} + + reg r_awvalid; + + always @(*) + begin + r_awvalid = dcd_awvalid[N] && !mwfull[N]; + wrequest[N]= 0; + if (!mwfull[N]) + wrequest[N][NS:0] = wdecode; + end + + assign m_awvalid[N] = r_awvalid; + + // QOS handling via write_qos_lockout + // {{{ + if (!OPT_QOS || NM == 1) + begin : WRITE_NO_QOS + + // If we aren't using QOS, then never lock any packets + // out from arbitration + assign write_qos_lockout[N] = 0; + + end else begin : WRITE_QOS + + // Lock out a master based upon a second master having + // a higher QOS request level + // {{{ + reg r_write_qos_lockout; + + initial r_write_qos_lockout = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_write_qos_lockout <= 0; + else begin + r_write_qos_lockout <= 0; + + for(iN=0; iN<NM; iN=iN+1) + if (iN != N) + begin + if (m_awvalid[N] + &&(|(wrequest[iN][NS-1:0] + & wdecode[NS-1:0])) + &&(m_awqos[N] < m_awqos[iN])) + r_write_qos_lockout <= 1; + end + end + + assign write_qos_lockout[N] = r_write_qos_lockout; + // }}} + end + // }}} + + end for (N=NM; N<NMFULL; N=N+1) + begin : UNUSED_WSKID_BUFFERS + // {{{ + // The following values are unused. They need to be defined + // so that our indexing scheme will work, but indexes should + // never actually reference them + assign m_awid[N] = 0; + assign m_awaddr[N] = 0; + assign m_awlen[N] = 0; + assign m_awsize[N] = 0; + assign m_awburst[N] = 0; + assign m_awlock[N] = 0; + assign m_awcache[N] = 0; + assign m_awprot[N] = 0; + assign m_awqos[N] = 0; + + assign m_awvalid[N] = 0; + + assign m_wvalid[N] = 0; + // + assign m_wdata[N] = 0; + assign m_wstrb[N] = 0; + assign m_wlast[N] = 0; + + assign write_qos_lockout[N] = 0; + // }}} + // }}} + end endgenerate + + // Read skid buffers and address decoding, slave_araccepts logic + generate for(N=0; N<NM; N=N+1) + begin : R1_DECODE_READ_REQUEST + // {{{ + reg r_arvalid; + wire [NS:0] rdecode; + + // arskid + // {{{ + skidbuffer #( + // {{{ + .DW(IW+AW+8+3+2+1+4+3+4), + .OPT_OUTREG(OPT_SKID_INPUT) + // }}} + ) arskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_ARVALID[N]), .o_ready(S_AXI_ARREADY[N]), + .i_data( + { S_AXI_ARID[N*IW +: IW], S_AXI_ARADDR[N*AW +: AW], + S_AXI_ARLEN[N*8 +: 8], S_AXI_ARSIZE[N*3 +: 3], + S_AXI_ARBURST[N*2 +: 2], S_AXI_ARLOCK[N], + S_AXI_ARCACHE[N*4 +: 4], S_AXI_ARPROT[N*3 +: 3], + S_AXI_ARQOS[N*4 +: 4] }), + .o_valid(skd_arvalid[N]), .i_ready(!skd_arstall[N]), + .o_data( + { skd_arid[N], skd_araddr[N], skd_arlen[N], + skd_arsize[N], skd_arburst[N], skd_arlock[N], + skd_arcache[N], skd_arprot[N], skd_arqos[N] }) + // }}} + ); + // }}} + + // Read address decoder + // {{{ + addrdecode #( + // {{{ + .AW(AW), .DW(IW+8+3+2+1+4+3+4), .NS(NS), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(OPT_BUFFER_DECODER) + // }}} + ) rdaddr( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(skd_arvalid[N]), .o_stall(skd_arstall[N]), + .i_addr(skd_araddr[N]), .i_data({ skd_arid[N], + skd_arlen[N], skd_arsize[N], skd_arburst[N], + skd_arlock[N], skd_arcache[N], skd_arprot[N], + skd_arqos[N] }), + .o_valid(dcd_arvalid[N]), + .i_stall(!m_arvalid[N] || !slave_raccepts[N]), + .o_decode(rdecode), .o_addr(m_araddr[N]), + .o_data({ m_arid[N], m_arlen[N], m_arsize[N], + m_arburst[N], m_arlock[N], m_arcache[N], + m_arprot[N], m_arqos[N]}) + // }}} + ); + // }}} + + always @(*) + begin + r_arvalid = dcd_arvalid[N] && !mrfull[N]; + rrequest[N] = 0; + if (!mrfull[N]) + rrequest[N][NS:0] = rdecode; + end + + assign m_arvalid[N] = r_arvalid; + + // slave_raccepts decoding + // {{{ + always @(*) + begin + slave_raccepts[N] = 1'b1; + if (!mrgrant[N]) + slave_raccepts[N] = 1'b0; + if (read_qos_lockout[N]) + slave_raccepts[N] = 1'b0; + if (mrfull[N]) + slave_raccepts[N] = 1'b0; + // If we aren't requesting access to the channel we've + // been granted access to, then we can't accept this + // verilator lint_off WIDTH + if (!rrequest[N][mrindex[N]]) + slave_raccepts[N] = 1'b0; + // verilator lint_on WIDTH + if (!rgrant[N][NS]) + begin + if (!slave_arready[mrindex[N]]) + slave_raccepts[N] = 1'b0; + end else if (!mrempty[N] || !rerr_none[N] || rskd_valid[N]) + slave_raccepts[N] = 1'b0; + end + // }}} + + // Read QOS logic + // {{{ + // read_qos_lockout will get set if a master with a higher + // QOS number is requesting a given slave. It will not + // affect existing outstanding packets, but will be used to + // prevent further packets from being sent to a given slave. + if (!OPT_QOS || NM == 1) + begin : READ_NO_QOS + + // If we aren't implementing QOS, then the lockout + // signal is never set + assign read_qos_lockout[N] = 0; + + end else begin : READ_QOS + // {{{ + // We set lockout if another master (with a higher + // QOS) is requesting this slave *and* the slave + // channel is currently stalled. + reg r_read_qos_lockout; + + initial r_read_qos_lockout = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_read_qos_lockout <= 0; + else begin + r_read_qos_lockout <= 0; + + for(iN=0; iN<NM; iN=iN+1) + if (iN != N) + begin + if (m_arvalid[iN] + && !slave_raccepts[N] + &&(|(rrequest[iN][NS-1:0] + & rdecode[NS-1:0])) + &&(m_arqos[N] < m_arqos[iN])) + r_read_qos_lockout <= 1; + end + end + + assign read_qos_lockout[N] = 0; + // }}} + end + // }}} + + end for (N=NM; N<NMFULL; N=N+1) + begin : UNUSED_RSKID_BUFFERS + // {{{ + assign m_arvalid[N] = 0; + assign m_arid[N] = 0; + assign m_araddr[N] = 0; + assign m_arlen[N] = 0; + assign m_arsize[N] = 0; + assign m_arburst[N] = 0; + assign m_arlock[N] = 0; + assign m_arcache[N] = 0; + assign m_arprot[N] = 0; + assign m_arqos[N] = 0; + + assign read_qos_lockout[N] = 0; + // }}} + // }}} + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Channel arbitration + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // wrequested + // {{{ + always @(*) + begin : W2_DECONFLICT_WRITE_REQUESTS + + for(iN=0; iN<=NM; iN=iN+1) + wrequested[iN] = 0; + + // Vivado may complain about too many bits for wrequested. + // This is (currrently) expected. mwindex is used to index + // into wrequested, and mwindex has LGNS bits, where LGNS + // is $clog2(NS+1) rather than $clog2(NS). The extra bits + // are defined to be zeros, but the point is they are defined. + // Therefore, no matter what mwindex is, it will always + // reference something valid. + wrequested[NM] = 0; + + for(iM=0; iM<NS; iM=iM+1) + begin + wrequested[0][iM] = 1'b0; + for(iN=1; iN<NM ; iN=iN+1) + begin + // Continue to request any channel with + // a grant and pending operations + if (wrequest[iN-1][iM] && wgrant[iN-1][iM]) + wrequested[iN][iM] = 1; + if (wrequest[iN-1][iM] && (!mwgrant[iN-1]||mwempty[iN-1])) + wrequested[iN][iM] = 1; + // Otherwise, if it's already claimed, then + // it can't be claimed again + if (wrequested[iN-1][iM]) + wrequested[iN][iM] = 1; + end + wrequested[NM][iM] = wrequest[NM-1][iM] || wrequested[NM-1][iM]; + end + end + // }}} + + // rrequested + // {{{ + always @(*) + begin : R2_DECONFLICT_READ_REQUESTS + + for(iN=0; iN<NM ; iN=iN+1) + rrequested[iN] = 0; + + // See the note above for wrequested. This applies to + // rrequested as well. + rrequested[NM] = 0; + + for(iM=0; iM<NS; iM=iM+1) + begin + rrequested[0][iM] = 0; + for(iN=1; iN<NM ; iN=iN+1) + begin + // Continue to request any channel with + // a grant and pending operations + if (rrequest[iN-1][iM] && rgrant[iN-1][iM]) + rrequested[iN][iM] = 1; + if (rrequest[iN-1][iM] && (!mrgrant[iN-1] || mrempty[iN-1])) + rrequested[iN][iM] = 1; + // Otherwise, if it's already claimed, then + // it can't be claimed again + if (rrequested[iN-1][iM]) + rrequested[iN][iM] = 1; + end + rrequested[NM][iM] = rrequest[NM-1][iM] || rrequested[NM-1][iM]; + end + end + // }}} + + + generate for(N=0; N<NM; N=N+1) + begin : W3_ARBITRATE_WRITE_REQUESTS + // {{{ + reg stay_on_channel; + reg requested_channel_is_available; + reg leave_channel; + reg [LGNS-1:0] requested_index; + wire linger; + reg [LGNS-1:0] r_mwindex; + + // The basic logic: + // 1. If we must stay_on_channel, then nothing changes + // 2. If the requested channel isn't available, then no grant + // is issued + // 3. Otherwise, if we need to leave this channel--such as if + // another master is requesting it, then we lose our grant + + // stay_on_channel + // {{{ + // We must stay on the channel if we aren't done working with it + // i.e. more writes requested, more acknowledgments expected, + // etc. + always @(*) + begin + stay_on_channel = |(wrequest[N][NS:0] & wgrant[N]); + if (write_qos_lockout[N]) + stay_on_channel = 0; + + // We must stay on this channel until we've received + // our last acknowledgment signal. Only then can we + // switch grants + if (mwgrant[N] && !mwempty[N]) + stay_on_channel = 1; + + // if berr_valid is true, we have a grant to the + // internal slave-error channel. While this grant + // exists, we cannot issue any others. + if (berr_valid[N]) + stay_on_channel = 1; + end + // }}} + + // requested_channel_is_available + // {{{ + always @(*) + begin + // The channel is available to us if 1) we want it, + // 2) no one else is using it, and 3) no one earlier + // has requested it + requested_channel_is_available = + |(wrequest[N][NS-1:0] & ~swgrant + & ~wrequested[N][NS-1:0]); + + // Of course, the error pseudo-channel is *always* + // available to us. + if (wrequest[N][NS]) + requested_channel_is_available = 1; + + // Likewise, if we are the only master, then the + // channel is always available on any request + if (NM < 2) + requested_channel_is_available = m_awvalid[N]; + end + // }}} + + // Linger option, and setting the "linger" flag + // {{{ + // If used, linger will hold on to a given channels grant + // for some number of clock ticks after the channel has become + // idle. This will spare future requests from the same master + // to the same slave from neding to go through the arbitration + // clock cycle again--potentially saving a clock period. If, + // however, the master in question requests a different slave + // or a different master requests this slave, then the linger + // option is voided and the grant given up anyway. + if (OPT_LINGER == 0) + begin : NO_LINGER + assign linger = 0; + end else begin : WRITE_LINGER + + reg [LGLINGER-1:0] linger_counter; + reg r_linger; + + initial r_linger = 0; + initial linger_counter = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || wgrant[N][NS]) + begin + r_linger <= 0; + linger_counter <= 0; + end else if (!mwempty[N] || bskd_valid[N]) + begin + // While the channel is in use, we set the + // linger counter + linger_counter <= OPT_LINGER; + r_linger <= 1; + end else if (linger_counter > 0) + begin + // Otherwise, we decrement it until it reaches + // zero + r_linger <= (linger_counter > 1); + linger_counter <= linger_counter - 1; + end else + r_linger <= 0; + + assign linger = r_linger; + end + // }}} + + // leave_channel + // {{{ + // True of another master is requesting access to this slave, + // or if we are requesting access to another slave. If QOS + // lockout is enabled, then we also leave the channel if a + // request with a higher QOS has arrived + always @(*) + begin + leave_channel = 0; + if (!m_awvalid[N] + && (!linger || wrequested[NM][mwindex[N]])) + // Leave the channel after OPT_LINGER counts + // of the channel being idle, or when someone + // else asks for the channel + leave_channel = 1; + if (m_awvalid[N] && !wrequest[N][mwindex[N]]) + // Need to leave this channel to connect + // to any other channel + leave_channel = 1; + if (write_qos_lockout[N]) + // Need to leave this channel for another higher + // priority request + leave_channel = 1; + end + // }}} + + // WRITE GRANT ALLOCATION + // {{{ + // Now that we've done our homework, we can switch grants + // if necessary + initial wgrant[N] = 0; + initial mwgrant[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + wgrant[N] <= 0; + mwgrant[N] <= 0; + end else if (!stay_on_channel) + begin + if (requested_channel_is_available) + begin + // Switch to a new channel + mwgrant[N] <= 1'b1; + wgrant[N] <= wrequest[N][NS:0]; + end else if (leave_channel) + begin + // Revoke the given grant + mwgrant[N] <= 1'b0; + wgrant[N] <= 0; + end + end + // }}} + + // mwindex (registered) + // {{{ + always @(wrequest[N]) + begin + requested_index = 0; + for(iM=0; iM<=NS; iM=iM+1) + if (wrequest[N][iM]) + requested_index= requested_index | iM[LGNS-1:0]; + end + + // Now for mwindex + initial r_mwindex = 0; + always @(posedge S_AXI_ACLK) + if (!stay_on_channel && requested_channel_is_available) + r_mwindex <= requested_index; + + assign mwindex[N] = r_mwindex; + // }}} + + end for (N=NM; N<NMFULL; N=N+1) + begin : EMPTY_WRITE_REQUEST + + assign mwindex[N] = 0; + // }}} + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin : R3_ARBITRATE_READ_REQUESTS + // {{{ + reg stay_on_channel; + reg requested_channel_is_available; + reg leave_channel; + reg [LGNS-1:0] requested_index; + wire linger; + reg [LGNS-1:0] r_mrindex; + + + // The basic logic: + // 1. If we must stay_on_channel, then nothing changes + // 2. If the requested channel isn't available, then no grant + // is issued + // 3. Otherwise, if we need to leave this channel--such as if + // another master is requesting it, then we lose our grant + + // stay_on_channel + // {{{ + // We must stay on the channel if we aren't done working with it + // i.e. more reads requested, more acknowledgments expected, + // etc. + always @(*) + begin + stay_on_channel = |(rrequest[N][NS:0] & rgrant[N]); + if (read_qos_lockout[N]) + stay_on_channel = 0; + + // We must stay on this channel until we've received + // our last acknowledgment signal. Only then can we + // switch grants + if (mrgrant[N] && !mrempty[N]) + stay_on_channel = 1; + + // if we have a grant to the internal slave-error + // channel, then we cannot issue a grant to any other + // while this grant is active + if (rgrant[N][NS] && (!rerr_none[N] || rskd_valid[N])) + stay_on_channel = 1; + end + // }}} + + // requested_channel_is_available + // {{{ + always @(*) + begin + // The channel is available to us if 1) we want it, + // 2) no one else is using it, and 3) no one earlier + // has requested it + requested_channel_is_available = + |(rrequest[N][NS-1:0] & ~srgrant + & ~rrequested[N][NS-1:0]); + + // Of course, the error pseudo-channel is *always* + // available to us. + if (rrequest[N][NS]) + requested_channel_is_available = 1; + + // Likewise, if we are the only master, then the + // channel is always available on any request + if (NM < 2) + requested_channel_is_available = m_arvalid[N]; + end + // }}} + + // Linger option, and setting the "linger" flag + // {{{ + // If used, linger will hold on to a given channels grant + // for some number of clock ticks after the channel has become + // idle. This will spare future requests from the same master + // to the same slave from neding to go through the arbitration + // clock cycle again--potentially saving a clock period. If, + // however, the master in question requests a different slave + // or a different master requests this slave, then the linger + // option is voided and the grant given up anyway. + if (OPT_LINGER == 0) + begin : NO_LINGER + assign linger = 0; + end else begin : READ_LINGER + reg r_linger; + reg [LGLINGER-1:0] linger_counter; + + initial r_linger = 0; + initial linger_counter = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || rgrant[N][NS]) + begin + r_linger <= 0; + linger_counter <= 0; + end else if (!mrempty[N] || rskd_valid[N]) + begin + linger_counter <= OPT_LINGER; + r_linger <= 1; + end else if (linger_counter > 0) + begin + r_linger <= (linger_counter > 1); + linger_counter <= linger_counter - 1; + end else + r_linger <= 0; + + assign linger = r_linger; + end + // }}} + + // leave_channel + // {{{ + // True of another master is requesting access to this slave, + // or if we are requesting access to another slave. If QOS + // lockout is enabled, then we also leave the channel if a + // request with a higher QOS has arrived + always @(*) + begin + leave_channel = 0; + if (!m_arvalid[N] + && (!linger || rrequested[NM][mrindex[N]])) + // Leave the channel after OPT_LINGER counts + // of the channel being idle, or when someone + // else asks for the channel + leave_channel = 1; + if (m_arvalid[N] && !rrequest[N][mrindex[N]]) + // Need to leave this channel to connect + // to any other channel + leave_channel = 1; + if (read_qos_lockout[N]) + leave_channel = 1; + end + // }}} + + + // READ GRANT ALLOCATION + // {{{ + initial rgrant[N] = 0; + initial mrgrant[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rgrant[N] <= 0; + mrgrant[N] <= 0; + end else if (!stay_on_channel) + begin + if (requested_channel_is_available) + begin + // Switching channels + mrgrant[N] <= 1'b1; + rgrant[N] <= rrequest[N][NS:0]; + end else if (leave_channel) + begin + mrgrant[N] <= 1'b0; + rgrant[N] <= 0; + end + end + // }}} + + // mrindex (registered) + // {{{ + always @(rrequest[N]) + begin + requested_index = 0; + for(iM=0; iM<=NS; iM=iM+1) + if (rrequest[N][iM]) + requested_index = requested_index|iM[LGNS-1:0]; + end + + initial r_mrindex = 0; + always @(posedge S_AXI_ACLK) + if (!stay_on_channel && requested_channel_is_available) + r_mrindex <= requested_index; + + assign mrindex[N] = r_mrindex; + // }}} + + end for (N=NM; N<NMFULL; N=N+1) + begin : EMPTY_READ_REQUEST + + assign mrindex[N] = 0; + // }}} + end endgenerate + + // Calculate swindex (registered) + generate for (M=0; M<NS; M=M+1) + begin : W4_SLAVE_WRITE_INDEX + // {{{ + // swindex is a per slave index, containing the index of the + // master that has currently won write arbitration and so + // has permission to access this slave + if (NM <= 1) + begin : ONE_MASTER + + // If there's only ever one master, that index is + // always the index of the one master. + assign swindex[M] = 0; + + end else begin : MULTIPLE_MASTERS + + reg [LGNM-1:0] reqwindex, r_swindex; + + // In the case of multiple masters, we follow the logic + // of the arbiter to generate the appropriate index + // here, and register it on the next clock cycle. If + // no slave has arbitration, the index will remain zero + always @(*) + begin + reqwindex = 0; + for(iN=0; iN<NM; iN=iN+1) + if ((!mwgrant[iN] || mwempty[iN]) + &&(wrequest[iN][M] && !wrequested[iN][M])) + reqwindex = reqwindex | iN[LGNM-1:0]; + end + + always @(posedge S_AXI_ACLK) + if (!swgrant[M]) + r_swindex <= reqwindex; + + assign swindex[M] = r_swindex; + end + + end for (M=NS; M<NSFULL; M=M+1) + begin : EMPTY_WRITE_INDEX + + assign swindex[M] = 0; + // }}} + end endgenerate + + // Calculate srindex (registered) + generate for (M=0; M<NS; M=M+1) + begin : R4_SLAVE_READ_INDEX + // {{{ + // srindex is an index to the master that has currently won + // read arbitration to the given slave. + + if (NM <= 1) + begin : ONE_MASTER + // If there's only one master, srindex can always + // point to that master--no longic required + assign srindex[M] = 0; + + end else begin : MULTIPLE_MASTERS + + reg [LGNM-1:0] reqrindex, r_srindex; + + // In the case of multiple masters, we'll follow the + // read arbitration logic to generate the index--first + // combinatorially, then we'll register it. + always @(*) + begin + reqrindex = 0; + for(iN=0; iN<NM; iN=iN+1) + if ((!mrgrant[iN] || mrempty[iN]) + &&(rrequest[iN][M] && !rrequested[iN][M])) + reqrindex = reqrindex | iN[LGNM-1:0]; + end + + always @(posedge S_AXI_ACLK) + if (!srgrant[M]) + r_srindex <= reqrindex; + + assign srindex[M] = r_srindex; + end + + end for (M=NS; M<NSFULL; M=M+1) + begin : EMPTY_READ_INDEX + + assign srindex[M] = 0; + // }}} + end endgenerate + + // swgrant and srgrant (combinatorial) + generate for(M=0; M<NS; M=M+1) + begin : SGRANT + // {{{ + + // s?grant is a convenience to tell a slave that some master + // has won arbitration and so has a grant to that slave. + + // swgrant: write arbitration + initial swgrant = 0; + always @(*) + begin + swgrant[M] = 0; + for(iN=0; iN<NM; iN=iN+1) + if (wgrant[iN][M]) + swgrant[M] = 1; + end + + initial srgrant = 0; + // srgrant: read arbitration + always @(*) + begin + srgrant[M] = 0; + for(iN=0; iN<NM; iN=iN+1) + if (rgrant[iN][M]) + srgrant[M] = 1; + end + // }}} + end endgenerate + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Generate the signals for the various slaves--the forward channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // Assign outputs to the various slaves + generate for(M=0; M<NS; M=M+1) + begin : W5_WRITE_SLAVE_OUTPUTS + // {{{ + reg axi_awvalid; + reg [IW-1:0] axi_awid; + reg [AW-1:0] axi_awaddr; + reg [7:0] axi_awlen; + reg [2:0] axi_awsize; + reg [1:0] axi_awburst; + reg axi_awlock; + reg [3:0] axi_awcache; + reg [2:0] axi_awprot; + reg [3:0] axi_awqos; + + reg axi_wvalid; + reg [DW-1:0] axi_wdata; + reg [DW/8-1:0] axi_wstrb; + reg axi_wlast; + // + reg axi_bready; + + reg sawstall, swstall; + reg awaccepts; + + // Control the slave's AW* channel + // {{{ + + // Personalize the slave_awaccepts signal + always @(*) + awaccepts = slave_awaccepts[swindex[M]]; + + always @(*) + sawstall= (M_AXI_AWVALID[M]&& !M_AXI_AWREADY[M]); + + initial axi_awvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !swgrant[M]) + axi_awvalid <= 0; + else if (!sawstall) + begin + axi_awvalid <= m_awvalid[swindex[M]] &&(awaccepts); + end + + initial axi_awid = 0; + initial axi_awaddr = 0; + initial axi_awlen = 0; + initial axi_awsize = 0; + initial axi_awburst = 0; + initial axi_awlock = 0; + initial axi_awcache = 0; + initial axi_awprot = 0; + initial axi_awqos = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!S_AXI_ARESETN || !swgrant[M])) + begin + // Under the OPT_LOWPOWER option, we clear all signals + // we aren't using + axi_awid <= 0; + axi_awaddr <= 0; + axi_awlen <= 0; + axi_awsize <= 0; + axi_awburst <= 0; + axi_awlock <= 0; + axi_awcache <= 0; + axi_awprot <= 0; + axi_awqos <= 0; + end else if (!sawstall) + begin + if (!OPT_LOWPOWER||(m_awvalid[swindex[M]]&&awaccepts)) + begin + // swindex[M] is defined as 0 above in the + // case where NM <= 1 + axi_awid <= m_awid[ swindex[M]]; + axi_awaddr <= m_awaddr[ swindex[M]]; + axi_awlen <= m_awlen[ swindex[M]]; + axi_awsize <= m_awsize[ swindex[M]]; + axi_awburst <= m_awburst[swindex[M]]; + axi_awlock <= m_awlock[ swindex[M]]; + axi_awcache <= m_awcache[swindex[M]]; + axi_awprot <= m_awprot[ swindex[M]]; + axi_awqos <= m_awqos[ swindex[M]]; + end else begin + axi_awid <= 0; + axi_awaddr <= 0; + axi_awlen <= 0; + axi_awsize <= 0; + axi_awburst <= 0; + axi_awlock <= 0; + axi_awcache <= 0; + axi_awprot <= 0; + axi_awqos <= 0; + end + end + // }}} + + // Control the slave's W* channel + // {{{ + always @(*) + swstall = (M_AXI_WVALID[M] && !M_AXI_WREADY[M]); + + initial axi_wvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !swgrant[M]) + axi_wvalid <= 0; + else if (!swstall) + begin + axi_wvalid <= (m_wvalid[swindex[M]]) + &&(slave_waccepts[swindex[M]]); + end + + initial axi_wdata = 0; + initial axi_wstrb = 0; + initial axi_wlast = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + axi_wlast <= 0; + end else if (OPT_LOWPOWER && !swgrant[M]) + begin + axi_wdata <= 0; + axi_wstrb <= 0; + axi_wlast <= 0; + end else if (!swstall) + begin + if (!OPT_LOWPOWER || (m_wvalid[swindex[M]]&&slave_waccepts[swindex[M]])) + begin + // If NM <= 1, swindex[M] is already defined + // to be zero above + axi_wdata <= m_wdata[swindex[M]]; + axi_wstrb <= m_wstrb[swindex[M]]; + axi_wlast <= m_wlast[swindex[M]]; + end else begin + axi_wdata <= 0; + axi_wstrb <= 0; + axi_wlast <= 0; + end + end + // }}} + + // + always @(*) + if (!swgrant[M]) + axi_bready = 1; + else + axi_bready = bskd_ready[swindex[M]]; + + // Combinatorial assigns + // {{{ + assign M_AXI_AWVALID[M] = axi_awvalid; + assign M_AXI_AWID[ M*IW +: IW] = axi_awid; + assign M_AXI_AWADDR[ M*AW +: AW] = axi_awaddr; + assign M_AXI_AWLEN[ M* 8 +: 8] = axi_awlen; + assign M_AXI_AWSIZE[ M* 3 +: 3] = axi_awsize; + assign M_AXI_AWBURST[M* 2 +: 2] = axi_awburst; + assign M_AXI_AWLOCK[ M] = axi_awlock; + assign M_AXI_AWCACHE[M* 4 +: 4] = axi_awcache; + assign M_AXI_AWPROT[ M* 3 +: 3] = axi_awprot; + assign M_AXI_AWQOS[ M* 4 +: 4] = axi_awqos; + // + // + assign M_AXI_WVALID[M] = axi_wvalid; + assign M_AXI_WDATA[M*DW +: DW] = axi_wdata; + assign M_AXI_WSTRB[M*DW/8 +: DW/8] = axi_wstrb; + assign M_AXI_WLAST[M] = axi_wlast; + // + // + assign M_AXI_BREADY[M] = axi_bready; + // }}} + // + // }}} + end endgenerate + + + generate for(M=0; M<NS; M=M+1) + begin : R5_READ_SLAVE_OUTPUTS + // {{{ + reg axi_arvalid; + reg [IW-1:0] axi_arid; + reg [AW-1:0] axi_araddr; + reg [7:0] axi_arlen; + reg [2:0] axi_arsize; + reg [1:0] axi_arburst; + reg axi_arlock; + reg [3:0] axi_arcache; + reg [2:0] axi_arprot; + reg [3:0] axi_arqos; + // + reg axi_rready; + wire arstall; + + assign arstall= axi_arvalid && !M_AXI_ARREADY[M]; + + initial axi_arvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !srgrant[M]) + axi_arvalid <= 0; + else if (!arstall) + axi_arvalid <= m_arvalid[srindex[M]] && slave_raccepts[srindex[M]]; + else if (M_AXI_ARREADY[M]) + axi_arvalid <= 0; + + initial axi_arid = 0; + initial axi_araddr = 0; + initial axi_arlen = 0; + initial axi_arsize = 0; + initial axi_arburst = 0; + initial axi_arlock = 0; + initial axi_arcache = 0; + initial axi_arprot = 0; + initial axi_arqos = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && (!S_AXI_ARESETN || !srgrant[M])) + begin + axi_arid <= 0; + axi_araddr <= 0; + axi_arlen <= 0; + axi_arsize <= 0; + axi_arburst <= 0; + axi_arlock <= 0; + axi_arcache <= 0; + axi_arprot <= 0; + axi_arqos <= 0; + end else if (!arstall) + begin + if (!OPT_LOWPOWER || (m_arvalid[srindex[M]] && slave_raccepts[srindex[M]])) + begin + // If NM <=1, srindex[M] is defined to be zero + axi_arid <= m_arid[ srindex[M]]; + axi_araddr <= m_araddr[ srindex[M]]; + axi_arlen <= m_arlen[ srindex[M]]; + axi_arsize <= m_arsize[ srindex[M]]; + axi_arburst <= m_arburst[srindex[M]]; + axi_arlock <= m_arlock[ srindex[M]]; + axi_arcache <= m_arcache[srindex[M]]; + axi_arprot <= m_arprot[ srindex[M]]; + axi_arqos <= m_arqos[ srindex[M]]; + end else begin + axi_arid <= 0; + axi_araddr <= 0; + axi_arlen <= 0; + axi_arsize <= 0; + axi_arburst <= 0; + axi_arlock <= 0; + axi_arcache <= 0; + axi_arprot <= 0; + axi_arqos <= 0; + end + end + + always @(*) + if (!srgrant[M]) + axi_rready = 1; + else + axi_rready = rskd_ready[srindex[M]]; + + // + assign M_AXI_ARVALID[M] = axi_arvalid; + assign M_AXI_ARID[ M*IW +: IW] = axi_arid; + assign M_AXI_ARADDR[ M*AW +: AW] = axi_araddr; + assign M_AXI_ARLEN[ M* 8 +: 8] = axi_arlen; + assign M_AXI_ARSIZE[ M* 3 +: 3] = axi_arsize; + assign M_AXI_ARBURST[M* 2 +: 2] = axi_arburst; + assign M_AXI_ARLOCK[ M] = axi_arlock; + assign M_AXI_ARCACHE[M* 4 +: 4] = axi_arcache; + assign M_AXI_ARPROT[ M* 3 +: 3] = axi_arprot; + assign M_AXI_ARQOS[ M* 4 +: 4] = axi_arqos; + // + assign M_AXI_RREADY[M] = axi_rready; + // + // }}} + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Generate the signals for the various masters--the return channel + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // Return values + generate for (N=0; N<NM; N=N+1) + begin : W6_WRITE_RETURN_CHANNEL + // {{{ + reg [1:0] i_axi_bresp; + reg [IW-1:0] i_axi_bid; + + // Write error (no slave selected) state machine + // {{{ + initial berr_valid[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + berr_valid[N] <= 0; + else if (wgrant[N][NS] && m_wvalid[N] && m_wlast[N] + && slave_waccepts[N]) + berr_valid[N] <= 1; + else if (bskd_ready[N]) + berr_valid[N] <= 0; + + always @(*) + if (berr_valid[N]) + bskd_valid[N] = 1; + else + bskd_valid[N] = mwgrant[N]&&m_axi_bvalid[mwindex[N]]; + + always @(posedge S_AXI_ACLK) + if (m_awvalid[N] && slave_awaccepts[N]) + berr_id[N] <= m_awid[N]; + + always @(*) + if (wgrant[N][NS]) + begin + i_axi_bid = berr_id[N]; + i_axi_bresp = INTERCONNECT_ERROR; + end else begin + i_axi_bid = m_axi_bid[mwindex[N]]; + i_axi_bresp = m_axi_bresp[mwindex[N]]; + end + // }}} + + // bskid, the B* channel skidbuffer + // {{{ + skidbuffer #( + // {{{ + .DW(IW+2), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(1) + // }}} + ) bskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(bskd_valid[N]), .o_ready(bskd_ready[N]), + .i_data({ i_axi_bid, i_axi_bresp }), + .o_valid(S_AXI_BVALID[N]), .i_ready(S_AXI_BREADY[N]), + .o_data({ S_AXI_BID[N*IW +: IW], S_AXI_BRESP[N*2 +: 2]}) + // }}} + ); + // }}} + // }}} + end endgenerate + + // Return values + generate for (N=0; N<NM; N=N+1) + begin : R6_READ_RETURN_CHANNEL + // {{{ + + reg [DW-1:0] i_axi_rdata; + reg [IW-1:0] i_axi_rid; + reg [2-1:0] i_axi_rresp; + + // generate the read response + // {{{ + // Here we have two choices. We can either generate our + // response from the slave itself, or from our internally + // generated (no-slave exists) FSM. + always @(*) + if (rgrant[N][NS]) + rskd_valid[N] = !rerr_none[N]; + else + rskd_valid[N] = mrgrant[N] && m_axi_rvalid[mrindex[N]]; + + always @(*) + if (rgrant[N][NS]) + begin + i_axi_rid = rerr_id[N]; + i_axi_rdata = 0; + rskd_rlast[N] = rerr_last[N]; + i_axi_rresp = INTERCONNECT_ERROR; + end else begin + i_axi_rid = m_axi_rid[mrindex[N]]; + i_axi_rdata = m_axi_rdata[mrindex[N]]; + rskd_rlast[N]= m_axi_rlast[mrindex[N]]; + i_axi_rresp = m_axi_rresp[mrindex[N]]; + end + // }}} + + // rskid, the outgoing read skidbuffer + // {{{ + // Since our various read signals are all combinatorially + // determined, we'll throw them into an outgoing skid buffer + // to register them (per spec) and to make it easier to meet + // timing. + skidbuffer #( + // {{{ + .DW(IW+DW+1+2), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(1) + // }}} + ) rskid( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(rskd_valid[N]), .o_ready(rskd_ready[N]), + .i_data({ i_axi_rid, i_axi_rdata, rskd_rlast[N], i_axi_rresp }), + .o_valid(S_AXI_RVALID[N]), .i_ready(S_AXI_RREADY[N]), + .o_data( + { S_AXI_RID[N*IW +: IW], S_AXI_RDATA[N*DW +: DW], + S_AXI_RLAST[N], S_AXI_RRESP[N*2 +: 2] }) + // }}} + ); + // }}} + // }}} + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Count pending transactions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate for (N=0; N<NM; N=N+1) + begin : W7_COUNT_PENDING_WRITES + // {{{ + + reg [LGMAXBURST-1:0] awpending, wpending; + reg r_wdata_expected; + + // awpending, and the associated flags mwempty and mwfull + // {{{ + // awpending is a count of all of the AW* packets that have + // been forwarded to the slave, but for which the slave has + // yet to return a B* response. This number can be as large + // as (1<<LGMAXBURST)-1. The two associated flags, mwempty + // and mwfull, are there to keep us from checking awempty==0 + // and &awempty respectively. + initial awpending = 0; + initial mwempty[N] = 1; + initial mwfull[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + awpending <= 0; + mwempty[N] <= 1; + mwfull[N] <= 0; + end else case ({(m_awvalid[N] && slave_awaccepts[N]), + (bskd_valid[N] && bskd_ready[N])}) + 2'b01: begin + awpending <= awpending - 1; + mwempty[N] <= (awpending <= 1); + mwfull[N] <= 0; + end + 2'b10: begin + awpending <= awpending + 1; + mwempty[N] <= 0; + mwfull[N] <= &awpending[LGMAXBURST-1:1]; + end + default: begin end + endcase + + // Just so we can access this counter elsewhere, let's make + // it available outside of this generate block. (The formal + // section uses this.) + assign w_mawpending[N] = awpending; + // }}} + + // r_wdata_expected and wdata_expected + // {{{ + // This section keeps track of whether or not we are expecting + // more W* data from the given burst. It's designed to keep us + // from accepting new W* information before the AW* portion + // has been routed to the new slave. + // + // Addition: wpending. wpending counts the number of write + // bursts that are pending, based upon the write channel. + // Bursts are counted from AWVALID & AWREADY, and decremented + // once we see the WVALID && WREADY signal. Packets should + // not be accepted without a prior (or concurrent) + // AWVALID && AWREADY. + initial r_wdata_expected = 0; + initial wpending = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + r_wdata_expected <= 0; + wpending <= 0; + end else case ({(m_awvalid[N] && slave_awaccepts[N]), + (m_wvalid[N]&&slave_waccepts[N] && m_wlast[N])}) + 2'b01: begin + r_wdata_expected <= (wpending > 1); + wpending <= wpending - 1; + end + 2'b10: begin + wpending <= wpending + 1; + r_wdata_expected <= 1; + end + default: begin end + endcase + + assign wdata_expected[N] = r_wdata_expected; + + assign wlasts_pending[N] = wpending; + // }}} + // }}} + end endgenerate + + generate for (N=0; N<NM; N=N+1) + begin : R7_COUNT_PENDING_READS + // {{{ + + reg [LGMAXBURST-1:0] rpending; + + // rpending, and its associated mrempty and mrfull + // {{{ + // rpending counts the number of read transactions that have + // been accepted, but for which rlast has yet to be returned. + // This specifically counts grants to valid slaves. The error + // slave is excluded from this count. mrempty and mrfull have + // analogous definitions to mwempty and mwfull, being equal to + // rpending == 0 and (&rpending) respectfully. + initial rpending = 0; + initial mrempty[N] = 1; + initial mrfull[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rpending <= 0; + mrempty[N]<= 1; + mrfull[N] <= 0; + end else case ({(m_arvalid[N] && slave_raccepts[N] && !rgrant[N][NS]), + (rskd_valid[N] && rskd_ready[N] + && rskd_rlast[N] && !rgrant[N][NS])}) + 2'b01: begin + rpending <= rpending - 1; + mrempty[N] <= (rpending == 1); + mrfull[N] <= 0; + end + 2'b10: begin + rpending <= rpending + 1; + mrfull[N] <= &rpending[LGMAXBURST-1:1]; + mrempty[N] <= 0; + end + default: begin end + endcase + + assign w_mrpending[N] = rpending; + // }}} + + // Read error state machine, rerr_outstanding and rerr_id + // {{{ + // rerr_outstanding is the count of read *beats* that remain + // to be returned to a master from a non-existent slave. + // rerr_last is true on the last of these read beats, + // equivalent to rerr_outstanding == 1, and rerr_none is true + // when the error state machine is idle + initial rerr_outstanding[N] = 0; + initial rerr_last[N] = 0; + initial rerr_none[N] = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + rerr_outstanding[N] <= 0; + rerr_last[N] <= 0; + rerr_none[N] <= 1; + end else if (!rerr_none[N]) + begin + if (!rskd_valid[N] || rskd_ready[N]) + begin + rerr_none[N] <= (rerr_outstanding[N] == 1); + rerr_last[N] <= (rerr_outstanding[N] == 2); + rerr_outstanding[N] <= rerr_outstanding[N] - 1; + end + end else if (m_arvalid[N] && rrequest[N][NS] + && slave_raccepts[N]) + begin + rerr_none[N] <= 0; + rerr_last[N] <= (m_arlen[N] == 0); + rerr_outstanding[N] <= m_arlen[N] + 1; + end + + // rerr_id is the ARID field of the currently outstanding + // error. It's used when generating a read response to a + // non-existent slave. + initial rerr_id[N] = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN && OPT_LOWPOWER) + rerr_id[N] <= 0; + else if (m_arvalid[N] && slave_raccepts[N]) + begin + if (rrequest[N][NS] || !OPT_LOWPOWER) + // A low-logic definition + rerr_id[N] <= m_arid[N]; + else + rerr_id[N] <= 0; + end else if (OPT_LOWPOWER && rerr_last[N] + && (!rskd_valid[N] || rskd_ready[N])) + rerr_id[N] <= 0; + // }}} + +`ifdef FORMAL + always @(*) + assert(rerr_none[N] == (rerr_outstanding[N] == 0)); + always @(*) + assert(rerr_last[N] == (rerr_outstanding[N] == 1)); + always @(*) + if (OPT_LOWPOWER && rerr_none[N]) + assert(rerr_id[N] == 0); +`endif + // }}} + end endgenerate + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // (Partial) Parameter validation + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + initial begin + if (NM == 0) begin + $display("At least one master must be defined"); + $stop; + end + + if (NS == 0) begin + $display("At least one slave must be defined"); + $stop; + end + end + // }}} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property verification section +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = LGMAXBURST+9; + + //////////////////////////////////////////////////////////////////////// + // + // Declare signals used for formal checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // + // ... + // + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Initial/reset value checking + // {{{ + initial assert(NS >= 1); + initial assert(NM >= 1); + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Check the arbiter signals for consistency + // {{{ + generate for(N=0; N<NM; N=N+1) + begin : F1_CHECK_MASTER_GRANTS + // {{{ + // Write grants + always @(*) + for(iM=0; iM<=NS; iM=iM+1) + begin + if (wgrant[N][iM]) + begin + assert((wgrant[N] ^ (1<<iM))==0); + assert(mwgrant[N]); + assert(mwindex[N] == iM); + if (iM < NS) + begin + assert(swgrant[iM]); + assert(swindex[iM] == N); + end + end + end + + always @(*) + if (mwgrant[N]) + assert(wgrant[N] != 0); + + always @(*) + if (wrequest[N][NS]) + assert(wrequest[N][NS-1:0] == 0); + + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && f_past_valid && bskd_valid[N]) + begin + assert($stable(wgrant[N])); + assert($stable(mwindex[N])); + end + + //////////////////////////////////////////////////////////////// + // + // Read grant checking + // + always @(*) + for(iM=0; iM<=NS; iM=iM+1) + begin + if (rgrant[N][iM]) + begin + assert((rgrant[N] ^ (1<<iM))==0); + assert(mrgrant[N]); + assert(mrindex[N] == iM); + if (iM < NS) + begin + assert(srgrant[iM]); + assert(srindex[iM] == N); + end + end + end + + always @(*) + if (mrgrant[N]) + assert(rgrant[N] != 0); + + always @(posedge S_AXI_ACLK) + if (S_AXI_ARESETN && f_past_valid && S_AXI_RVALID[N]) + begin + assert($stable(rgrant[N])); + assert($stable(mrindex[N])); + if (!rgrant[N][NS]) + assert(!mrempty[N]); + end + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI signaling check, (incoming) master side + // {{{ + //////////////////////////////////////////////////////////////////////// + // + generate for(N=0; N<NM; N=N+1) + begin : F2_CHECK_MASTERS + // {{{ + faxi_slave #( + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(DW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_ASSUME_RESET(1'b1), + .F_AXI_MAXSTALL(0), + .F_AXI_MAXRSTALL(2), + .F_AXI_MAXDELAY(0), + .F_OPT_READCHECK(0), + .F_OPT_NO_RESET(1), + .F_LGDEPTH(F_LGDEPTH)) + mstri(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awid( S_AXI_AWID[ N*IW +:IW]), + .i_axi_awaddr( S_AXI_AWADDR[ N*AW +:AW]), + .i_axi_awlen( S_AXI_AWLEN[ N* 8 +: 8]), + .i_axi_awsize( S_AXI_AWSIZE[ N* 3 +: 3]), + .i_axi_awburst(S_AXI_AWBURST[N* 2 +: 2]), + .i_axi_awlock( S_AXI_AWLOCK[ N]), + .i_axi_awcache(S_AXI_AWCACHE[N* 4 +: 4]), + .i_axi_awprot( S_AXI_AWPROT[ N* 3 +: 3]), + .i_axi_awqos( S_AXI_AWQOS[ N* 4 +: 4]), + .i_axi_awvalid(S_AXI_AWVALID[N]), + .i_axi_awready(S_AXI_AWREADY[N]), + // + .i_axi_wdata( S_AXI_WDATA[ N*DW +: DW]), + .i_axi_wstrb( S_AXI_WSTRB[ N*DW/8 +: DW/8]), + .i_axi_wlast( S_AXI_WLAST[ N]), + .i_axi_wvalid(S_AXI_WVALID[N]), + .i_axi_wready(S_AXI_WREADY[N]), + // + .i_axi_bid( S_AXI_BID[ N*IW +:IW]), + .i_axi_bresp( S_AXI_BRESP[ N*2 +: 2]), + .i_axi_bvalid(S_AXI_BVALID[N]), + .i_axi_bready(S_AXI_BREADY[N]), + // + .i_axi_arid( S_AXI_ARID[ N*IW +:IW]), + .i_axi_arready(S_AXI_ARREADY[N]), + .i_axi_araddr( S_AXI_ARADDR[ N*AW +:AW]), + .i_axi_arlen( S_AXI_ARLEN[ N* 8 +: 8]), + .i_axi_arsize( S_AXI_ARSIZE[ N* 3 +: 3]), + .i_axi_arburst(S_AXI_ARBURST[N* 2 +: 2]), + .i_axi_arlock( S_AXI_ARLOCK[ N]), + .i_axi_arcache(S_AXI_ARCACHE[N* 4 +: 4]), + .i_axi_arprot( S_AXI_ARPROT[ N* 3 +: 3]), + .i_axi_arqos( S_AXI_ARQOS[ N* 4 +: 4]), + .i_axi_arvalid(S_AXI_ARVALID[N]), + // + // + .i_axi_rid( S_AXI_RID[ N*IW +: IW]), + .i_axi_rdata( S_AXI_RDATA[ N*DW +: DW]), + .i_axi_rresp( S_AXI_RRESP[ N* 2 +: 2]), + .i_axi_rlast( S_AXI_RLAST[ N]), + .i_axi_rvalid(S_AXI_RVALID[N]), + .i_axi_rready(S_AXI_RREADY[N]), + // + // ... + // + ); + + // + // ... + // + + // + // Check full/empty flags + // + + always @(*) + begin + assert(mwfull[N] == &w_mawpending[N]); + assert(mwempty[N] == (w_mawpending[N] == 0)); + end + + always @(*) + begin + assert(mrfull[N] == &w_mrpending[N]); + assert(mrempty[N] == (w_mrpending[N] == 0)); + end + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI signaling check, (outgoing) slave side + // {{{ + //////////////////////////////////////////////////////////////////////// + // + generate for(M=0; M<NS; M=M+1) + begin : F3_CHECK_SLAVES + // {{{ + faxi_master #( + .C_AXI_ID_WIDTH(IW), + .C_AXI_DATA_WIDTH(DW), + .C_AXI_ADDR_WIDTH(AW), + .F_OPT_ASSUME_RESET(1'b1), + .F_AXI_MAXSTALL(2), + .F_AXI_MAXRSTALL(0), + .F_AXI_MAXDELAY(2), + .F_OPT_READCHECK(0), + .F_OPT_NO_RESET(1), + .F_LGDEPTH(F_LGDEPTH)) + slvi(.i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awid( M_AXI_AWID[ M*IW+:IW]), + .i_axi_awaddr( M_AXI_AWADDR[ M*AW +: AW]), + .i_axi_awlen( M_AXI_AWLEN[ M*8 +: 8]), + .i_axi_awsize( M_AXI_AWSIZE[ M*3 +: 3]), + .i_axi_awburst(M_AXI_AWBURST[M*2 +: 2]), + .i_axi_awlock( M_AXI_AWLOCK[ M]), + .i_axi_awcache(M_AXI_AWCACHE[M*4 +: 4]), + .i_axi_awprot( M_AXI_AWPROT[ M*3 +: 3]), + .i_axi_awqos( M_AXI_AWQOS[ M*4 +: 4]), + .i_axi_awvalid(M_AXI_AWVALID[M]), + .i_axi_awready(M_AXI_AWREADY[M]), + // + .i_axi_wready(M_AXI_WREADY[M]), + .i_axi_wdata( M_AXI_WDATA[ M*DW +: DW]), + .i_axi_wstrb( M_AXI_WSTRB[ M*DW/8 +: DW/8]), + .i_axi_wlast( M_AXI_WLAST[ M]), + .i_axi_wvalid(M_AXI_WVALID[M]), + // + .i_axi_bid( M_AXI_BID[ M*IW +: IW]), + .i_axi_bresp( M_AXI_BRESP[ M*2 +: 2]), + .i_axi_bvalid(M_AXI_BVALID[M]), + .i_axi_bready(M_AXI_BREADY[M]), + // + .i_axi_arid( M_AXI_ARID[ M*IW +:IW]), + .i_axi_araddr( M_AXI_ARADDR[ M*AW +:AW]), + .i_axi_arlen( M_AXI_ARLEN[ M*8 +: 8]), + .i_axi_arsize( M_AXI_ARSIZE[ M*3 +: 3]), + .i_axi_arburst(M_AXI_ARBURST[M*2 +: 2]), + .i_axi_arlock( M_AXI_ARLOCK[ M]), + .i_axi_arcache(M_AXI_ARCACHE[M* 4 +: 4]), + .i_axi_arprot( M_AXI_ARPROT[ M* 3 +: 3]), + .i_axi_arqos( M_AXI_ARQOS[ M* 4 +: 4]), + .i_axi_arvalid(M_AXI_ARVALID[M]), + .i_axi_arready(M_AXI_ARREADY[M]), + // + // + .i_axi_rresp( M_AXI_RRESP[ M*2 +: 2]), + .i_axi_rvalid(M_AXI_RVALID[M]), + .i_axi_rdata( M_AXI_RDATA[ M*DW +: DW]), + .i_axi_rready(M_AXI_RREADY[M]), + .i_axi_rlast( M_AXI_RLAST[ M]), + .i_axi_rid( M_AXI_RID[ M*IW +: IW]), + // + // ... + // + ); + + // + // ... + // + + always @(*) + if (M_AXI_AWVALID[M]) + assert(((M_AXI_AWADDR[M*AW +:AW]^SLAVE_ADDR[M*AW +:AW]) + & SLAVE_MASK[M*AW +: AW]) == 0); + + always @(*) + if (M_AXI_ARVALID[M]) + assert(((M_AXI_ARADDR[M*AW +:AW]^SLAVE_ADDR[M*AW +:AW]) + & SLAVE_MASK[M*AW +: AW]) == 0); + // }}} + end endgenerate + // }}} + + // m_axi_* convenience signals + // {{{ + // ... + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // ... + // {{{ + //////////////////////////////////////////////////////////////////////// + // + generate for(N=0; N<NM; N=N+1) + begin : // ... + // {{{ + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Double buffer checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate for(N=0; N<NM; N=N+1) + begin : F4_DOUBLE_BUFFER_CHECKS + // {{{ + // ... + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // Can every master reach every slave? + // Can things transition without dropping the request line(s)? + generate for(N=0; N<NM; N=N+1) + begin : F5_COVER_CONNECTIVITY_FROM_MASTER + // {{{ + // ... + // }}} + end endgenerate + + //////////////////////////////////////////////////////////////////////// + // + // Focused check: How fast can one master talk to each of the slaves? + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // ... + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Focused check: How fast can one master talk to a particular slave? + // We'll pick master 1 and slave 1. + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // ... + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Poor man's cover check + // {{{ + // ... + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Negation check + // {{{ + // Pick a particular value. Assume the value doesn't show up on the + // input. Prove it doesn't show up on the output. This will check for + // ... + // 1. Stuck bits on the output channel + // 2. Cross-talk between channels + // + //////////////////////////////////////////////////////////////////////// + // + // + // ... + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Artificially constraining assumptions + // {{{ + // Ideally, this section should be empty--there should be no + // assumptions here. The existence of these assumptions should + // give you an idea of where I'm at with this project. + // + //////////////////////////////////////////////////////////////////////// + // + // + generate for(N=0; N<NM; N=N+1) + begin : F6_LIMITING_ASSUMPTIONS + + if (!OPT_WRITES) + begin + always @(*) + assume(S_AXI_AWVALID[N] == 0); + always @(*) + assert(wgrant[N] == 0); + always @(*) + assert(mwgrant[N] == 0); + always @(*) + assert(S_AXI_BVALID[N]== 0); + end + + if (!OPT_READS) + begin + always @(*) + assume(S_AXI_ARVALID [N]== 0); + always @(*) + assert(rgrant[N] == 0); + always @(*) + assert(S_AXI_RVALID[N] == 0); + end + + end endgenerate + + always@(*) + assert(OPT_READS | OPT_WRITES); + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/axixclk.v b/rtl/wb2axip/axixclk.v new file mode 100644 index 0000000..670f606 --- /dev/null +++ b/rtl/wb2axip/axixclk.v @@ -0,0 +1,312 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axixclk.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Cross AXI clock domains +// +// Performance: +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axixclk #( + // {{{ + parameter integer C_S_AXI_ID_WIDTH = 2, + parameter integer C_S_AXI_DATA_WIDTH = 32, + parameter integer C_S_AXI_ADDR_WIDTH = 6, + // Some useful short-hand definitions + // localparam AW = C_S_AXI_ADDR_WIDTH, + // localparam DW = C_S_AXI_DATA_WIDTH, + // localparam IW = C_S_AXI_ID_WIDTH, + // + parameter [0:0] OPT_WRITE_ONLY = 1'b0, + parameter [0:0] OPT_READ_ONLY = 1'b0, + parameter XCLOCK_FFS = 2, + parameter LGFIFO = 5 + // }}} + ) ( + // {{{ + // Users to add ports here + + // User ports ends + // Do not modify the ports beyond this line + + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_AWID, + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, + input wire [7 : 0] S_AXI_AWLEN, + input wire [2 : 0] S_AXI_AWSIZE, + input wire [1 : 0] S_AXI_AWBURST, + input wire S_AXI_AWLOCK, + input wire [3 : 0] S_AXI_AWCACHE, + input wire [2 : 0] S_AXI_AWPROT, + input wire [3 : 0] S_AXI_AWQOS, + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + // + input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, + input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, + input wire S_AXI_WLAST, + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + // + output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_BID, + output wire [1 : 0] S_AXI_BRESP, + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + // + input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_ARID, + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, + input wire [7 : 0] S_AXI_ARLEN, + input wire [2 : 0] S_AXI_ARSIZE, + input wire [1 : 0] S_AXI_ARBURST, + input wire S_AXI_ARLOCK, + input wire [3 : 0] S_AXI_ARCACHE, + input wire [2 : 0] S_AXI_ARPROT, + input wire [3 : 0] S_AXI_ARQOS, + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + // + output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_RID, + output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, + output wire [1 : 0] S_AXI_RRESP, + output wire S_AXI_RLAST, + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + + // + // Downstream port + // + input wire M_AXI_ACLK, + output wire M_AXI_ARESETN, + // + output wire [C_S_AXI_ID_WIDTH-1 : 0] M_AXI_AWID, + output wire [C_S_AXI_ADDR_WIDTH-1 : 0] M_AXI_AWADDR, + output wire [7 : 0] M_AXI_AWLEN, + output wire [2 : 0] M_AXI_AWSIZE, + output wire [1 : 0] M_AXI_AWBURST, + output wire M_AXI_AWLOCK, + output wire [3 : 0] M_AXI_AWCACHE, + output wire [2 : 0] M_AXI_AWPROT, + output wire [3 : 0] M_AXI_AWQOS, + output wire M_AXI_AWVALID, + input wire M_AXI_AWREADY, + // + output wire [C_S_AXI_DATA_WIDTH-1 : 0] M_AXI_WDATA, + output wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] M_AXI_WSTRB, + output wire M_AXI_WLAST, + output wire M_AXI_WVALID, + input wire M_AXI_WREADY, + // + input wire [C_S_AXI_ID_WIDTH-1 : 0] M_AXI_BID, + input wire [1 : 0] M_AXI_BRESP, + input wire M_AXI_BVALID, + output wire M_AXI_BREADY, + // + output wire [C_S_AXI_ID_WIDTH-1 : 0] M_AXI_ARID, + output wire [C_S_AXI_ADDR_WIDTH-1 : 0] M_AXI_ARADDR, + output wire [7 : 0] M_AXI_ARLEN, + output wire [2 : 0] M_AXI_ARSIZE, + output wire [1 : 0] M_AXI_ARBURST, + output wire M_AXI_ARLOCK, + output wire [3 : 0] M_AXI_ARCACHE, + output wire [2 : 0] M_AXI_ARPROT, + output wire [3 : 0] M_AXI_ARQOS, + output wire M_AXI_ARVALID, + input wire M_AXI_ARREADY, + // + input wire [C_S_AXI_ID_WIDTH-1 : 0] M_AXI_RID, + input wire [C_S_AXI_DATA_WIDTH-1 : 0] M_AXI_RDATA, + input wire [1 : 0] M_AXI_RRESP, + input wire M_AXI_RLAST, + input wire M_AXI_RVALID, + output wire M_AXI_RREADY + // }}} + ); + + reg [2:0] mreset; + + (* ASYNC_REG = "TRUE" *) initial mreset = 3'b000; + always @(posedge M_AXI_ACLK, negedge S_AXI_ARESETN) + if (!S_AXI_ARESETN) + mreset <= 3'b000; + else + mreset <= { mreset[1:0], 1'b1 }; + + assign M_AXI_ARESETN = mreset[2]; + + generate if (OPT_READ_ONLY) + begin : READ_ONLY + + assign M_AXI_AWID = 0; + assign M_AXI_AWADDR = 0; + assign M_AXI_AWLEN = 0; + assign M_AXI_AWSIZE = 0; + assign M_AXI_AWBURST= 0; + assign M_AXI_AWLOCK = 0; + assign M_AXI_AWCACHE= 0; + assign M_AXI_AWPROT = 0; + assign M_AXI_AWQOS = 0; + // Either way we do these we're wrong, so don't try accessing + // the write side of the bus when OPT_READ_ONLY is set or your + // design will hang. + + assign M_AXI_AWVALID = 1'b0; + assign S_AXI_AWREADY = 1'b0; + + assign M_AXI_WDATA = 0; + assign M_AXI_WSTRB = 0; + assign M_AXI_WLAST = 0; + + assign M_AXI_WVALID = 1'b0; + assign S_AXI_WREADY = 1'b0; + + assign S_AXI_BID = 0; + assign S_AXI_BRESP = 2'b11; + + assign M_AXI_BREADY = 1'b0; + assign S_AXI_BVALID = 1'b0; + + end else begin : WRITE_FIFO + wire awfull, awempty, wfull, wempty, bfull, bempty; + + afifo #(.LGFIFO(LGFIFO), + .NFF(XCLOCK_FFS), + .WIDTH(C_S_AXI_ID_WIDTH + C_S_AXI_ADDR_WIDTH + + 8 + 3 + 2 + 1 + 4 + 3 + 4)) + awfifo(S_AXI_ACLK, S_AXI_ARESETN, S_AXI_AWVALID&& S_AXI_AWREADY, + { S_AXI_AWID, S_AXI_AWADDR, + S_AXI_AWLEN, S_AXI_AWSIZE, S_AXI_AWBURST, + S_AXI_AWLOCK, + S_AXI_AWCACHE, S_AXI_AWPROT, S_AXI_AWQOS }, + awfull, + M_AXI_ACLK, M_AXI_ARESETN, M_AXI_AWREADY, + { M_AXI_AWID, M_AXI_AWADDR, + M_AXI_AWLEN, M_AXI_AWSIZE, M_AXI_AWBURST, + M_AXI_AWLOCK, + M_AXI_AWCACHE, M_AXI_AWPROT, M_AXI_AWQOS }, + awempty); + + assign M_AXI_AWVALID = !awempty; + assign S_AXI_AWREADY = !awfull; + + afifo #(.LGFIFO(LGFIFO), + .NFF(XCLOCK_FFS), + .WIDTH(C_S_AXI_DATA_WIDTH + C_S_AXI_DATA_WIDTH/8 + 1)) + wfifo(S_AXI_ACLK, S_AXI_ARESETN, S_AXI_WVALID&& S_AXI_WREADY, + { S_AXI_WDATA, S_AXI_WSTRB, S_AXI_WLAST }, + wfull, + M_AXI_ACLK, M_AXI_ARESETN, M_AXI_WREADY, + { M_AXI_WDATA, M_AXI_WSTRB, M_AXI_WLAST }, + wempty); + + assign M_AXI_WVALID = !wempty; + assign S_AXI_WREADY = !wfull; + + afifo #(.LGFIFO(LGFIFO), + .NFF(XCLOCK_FFS), + .WIDTH(C_S_AXI_ID_WIDTH + 2)) + bfifo(M_AXI_ACLK, M_AXI_ARESETN, M_AXI_BVALID&& M_AXI_BREADY, + { M_AXI_BID, M_AXI_BRESP }, bfull, + S_AXI_ACLK, S_AXI_ARESETN, S_AXI_BREADY, + { S_AXI_BID, S_AXI_BRESP }, bempty); + + assign S_AXI_BVALID = !bempty; + assign M_AXI_BREADY = !bfull; + end endgenerate + + generate if (OPT_WRITE_ONLY) + begin : NO_READS + + assign M_AXI_ARID = 0; + assign M_AXI_ARADDR = 0; + assign M_AXI_ARLEN = 0; + assign M_AXI_ARSIZE = 0; + assign M_AXI_ARBURST= 0; + assign M_AXI_ARLOCK = 0; + assign M_AXI_ARCACHE= 0; + assign M_AXI_ARPROT = 0; + assign M_AXI_ARQOS = 0; + // Either way we do these we're wrong, so don't try accessing + // the write side of the bus when OPT_READ_ONLY is set or your + // design will hang. + + assign M_AXI_ARVALID = 1'b0; + assign S_AXI_ARREADY = 1'b0; + + assign S_AXI_RID = 0; + assign S_AXI_RDATA = 2'b11; + assign S_AXI_RLAST = 1'b1; + assign S_AXI_RRESP = 2'b11; + + assign M_AXI_RREADY = 1'b0; + assign S_AXI_RVALID = 1'b0; + + end else begin : READ_FIFO + wire arfull, arempty, rfull, rempty; + + afifo #(.LGFIFO(LGFIFO), + .NFF(XCLOCK_FFS), + .WIDTH(C_S_AXI_ID_WIDTH + C_S_AXI_ADDR_WIDTH + + 8 + 3 + 2 + 1 + 4 + 3 + 4)) + arfifo(S_AXI_ACLK, S_AXI_ARESETN, S_AXI_ARVALID&& S_AXI_ARREADY, + { S_AXI_ARID, S_AXI_ARADDR, + S_AXI_ARLEN, S_AXI_ARSIZE, S_AXI_ARBURST, + S_AXI_ARLOCK, + S_AXI_ARCACHE, S_AXI_ARPROT, S_AXI_ARQOS }, + arfull, + M_AXI_ACLK, M_AXI_ARESETN, M_AXI_ARREADY, + { M_AXI_ARID, M_AXI_ARADDR, + M_AXI_ARLEN, M_AXI_ARSIZE, M_AXI_ARBURST, + M_AXI_ARLOCK, + M_AXI_ARCACHE, M_AXI_ARPROT, M_AXI_ARQOS }, + arempty); + + assign M_AXI_ARVALID = !arempty; + assign S_AXI_ARREADY = !arfull; + + + afifo #(.LGFIFO(LGFIFO), + .NFF(XCLOCK_FFS), + .WIDTH(C_S_AXI_ID_WIDTH + C_S_AXI_DATA_WIDTH+3)) + rfifo(M_AXI_ACLK, M_AXI_ARESETN, M_AXI_RVALID&& M_AXI_RREADY, + { M_AXI_RID, M_AXI_RDATA, M_AXI_RLAST, M_AXI_RRESP }, + rfull, + S_AXI_ACLK, S_AXI_ARESETN, S_AXI_RREADY, + { S_AXI_RID, S_AXI_RDATA, S_AXI_RLAST, S_AXI_RRESP }, + rempty); + + assign S_AXI_RVALID = !rempty; + assign M_AXI_RREADY = !rfull; + + end endgenerate + +endmodule diff --git a/rtl/wb2axip/axlite2wbsp.v b/rtl/wb2axip/axlite2wbsp.v new file mode 100644 index 0000000..b11b365 --- /dev/null +++ b/rtl/wb2axip/axlite2wbsp.v @@ -0,0 +1,568 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: axlite2wbsp.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Take an AXI lite input, and convert it into WB. +// +// We'll treat AXI as two separate busses: one for writes, another for +// reads, further, we'll insist that the two channels AXI uses for writes +// combine into one channel for our purposes. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2016-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module axlite2wbsp #( + // {{{ + parameter C_AXI_DATA_WIDTH = 32,// Width of the AXI R&W data + parameter C_AXI_ADDR_WIDTH = 28, // AXI Address width + parameter LGFIFO = 4, +`ifdef FORMAL + parameter F_MAXSTALL = 3, + parameter F_MAXDELAY = 3, +`endif + parameter [0:0] OPT_READONLY = 1'b0, + parameter [0:0] OPT_WRITEONLY = 1'b0, + localparam AXILLSB = $clog2(C_AXI_DATA_WIDTH/8) + // }}} + ) ( + // {{{ + input wire i_clk, // System clock + input wire i_axi_reset_n, + + // AXI write address channel signals + // {{{ + input wire i_axi_awvalid, + output wire o_axi_awready, + input wire [C_AXI_ADDR_WIDTH-1:0] i_axi_awaddr, + input wire [2:0] i_axi_awprot, + // }}} + // AXI write data channel signals + // {{{ + input wire i_axi_wvalid, + output wire o_axi_wready, + input wire [C_AXI_DATA_WIDTH-1:0] i_axi_wdata, + input wire [C_AXI_DATA_WIDTH/8-1:0] i_axi_wstrb, + // }}} + // AXI write response channel signals + // {{{ + output wire o_axi_bvalid, + input wire i_axi_bready, + output wire [1:0] o_axi_bresp, + // }}} + // AXI read address channel signals + // {{{ + input wire i_axi_arvalid, + output wire o_axi_arready, + input wire [C_AXI_ADDR_WIDTH-1:0] i_axi_araddr, + input wire [2:0] i_axi_arprot, + // }}} + // AXI read data channel signals + // {{{ + output wire o_axi_rvalid, + input wire i_axi_rready, + output wire [C_AXI_DATA_WIDTH-1:0] o_axi_rdata, + output wire [1:0] o_axi_rresp, + // }}} + // Wishbone signals + // {{{ + // We'll share the clock and the reset + output wire o_reset, + output wire o_wb_cyc, + output wire o_wb_stb, + output wire o_wb_we, + output wire [C_AXI_ADDR_WIDTH-AXILLSB-1:0] o_wb_addr, + output wire [C_AXI_DATA_WIDTH-1:0] o_wb_data, + output wire [C_AXI_DATA_WIDTH/8-1:0] o_wb_sel, + input wire i_wb_stall, + input wire i_wb_ack, + input wire [(C_AXI_DATA_WIDTH-1):0] i_wb_data, + input wire i_wb_err + // }}} + // }}} + ); + + // Local definitions + // {{{ + localparam DW = C_AXI_DATA_WIDTH; + localparam AW = C_AXI_ADDR_WIDTH-AXILLSB; + // + // + // + + wire [(AW-1):0] w_wb_addr, r_wb_addr; + wire [(DW-1):0] w_wb_data; + wire [(DW/8-1):0] w_wb_sel, r_wb_sel; + wire r_wb_err, r_wb_cyc, r_wb_stb, r_wb_stall, r_wb_ack; + wire w_wb_err, w_wb_cyc, w_wb_stb, w_wb_stall, w_wb_ack; + + // verilator lint_off UNUSED + wire r_wb_we, w_wb_we; + + assign r_wb_we = 1'b0; + assign w_wb_we = 1'b1; + // verilator lint_on UNUSED + +`ifdef FORMAL + // {{{ + // Verilator lint_off UNUSED + localparam F_LGDEPTH = LGFIFO+1; + wire [LGFIFO:0] f_wr_fifo_first, f_rd_fifo_first, + f_wr_fifo_mid, f_rd_fifo_mid, + f_wr_fifo_last, f_rd_fifo_last; + // Verilator lint_on UNUSED + wire [(F_LGDEPTH-1):0] f_wb_nreqs, f_wb_nacks, + f_wb_outstanding; + wire [(F_LGDEPTH-1):0] f_wb_wr_nreqs, f_wb_wr_nacks, + f_wb_wr_outstanding; + wire [(F_LGDEPTH-1):0] f_wb_rd_nreqs, f_wb_rd_nacks, + f_wb_rd_outstanding; + wire f_pending_awvalid, f_pending_wvalid; + // }}} +`endif + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite write channel to WB processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (!OPT_READONLY) + begin : AXI_WR + // {{{ + axilwr2wbsp #( + // {{{ + // .F_LGDEPTH(F_LGDEPTH), + // .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), // .AW(AW), + .LGFIFO(LGFIFO) + // }}} + ) axi_write_decoder ( + // {{{ + .i_clk(i_clk), .i_axi_reset_n(i_axi_reset_n), + // + .i_axi_awvalid(i_axi_awvalid), + .o_axi_awready(o_axi_awready), + .i_axi_awaddr( i_axi_awaddr), + .i_axi_awprot( i_axi_awprot), + // + .i_axi_wvalid( i_axi_wvalid), + .o_axi_wready( o_axi_wready), + .i_axi_wdata( i_axi_wdata), + .i_axi_wstrb( i_axi_wstrb), + // + .o_axi_bvalid(o_axi_bvalid), + .i_axi_bready(i_axi_bready), + .o_axi_bresp(o_axi_bresp), + // + .o_wb_cyc( w_wb_cyc), + .o_wb_stb( w_wb_stb), + .o_wb_addr( w_wb_addr), + .o_wb_data( w_wb_data), + .o_wb_sel( w_wb_sel), + .i_wb_stall(w_wb_stall), + .i_wb_ack( w_wb_ack), + .i_wb_err( w_wb_err) +`ifdef FORMAL + // {{{ + , + .f_first(f_wr_fifo_first), + .f_mid( f_wr_fifo_mid), + .f_last( f_wr_fifo_last), + .f_wpending({ f_pending_awvalid, f_pending_wvalid }) + // }}} +`endif + // }}} + ); + // }}} + end else begin : EMPTY_WRITES + // {{{ + assign w_wb_cyc = 0; + assign w_wb_stb = 0; + assign w_wb_addr = 0; + assign w_wb_data = 0; + assign w_wb_sel = 0; + assign o_axi_awready = 0; + assign o_axi_wready = 0; + assign o_axi_bvalid = (i_axi_wvalid); + assign o_axi_bresp = 2'b11; +`ifdef FORMAL + assign f_wr_fifo_first = 0; + assign f_wr_fifo_mid = 0; + assign f_wr_fifo_last = 0; + assign f_pending_awvalid=0; + assign f_pending_wvalid =0; +`endif + // }}} + end endgenerate + assign w_wb_we = 1'b1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite read channel to WB processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (!OPT_WRITEONLY) + begin : AXI_RD + // {{{ + axilrd2wbsp #( + // {{{ + // .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .LGFIFO(LGFIFO) + // }}} + ) axi_read_decoder( + // {{{ + .i_clk(i_clk), .i_axi_reset_n(i_axi_reset_n), + // + .i_axi_arvalid(i_axi_arvalid), + .o_axi_arready(o_axi_arready), + .i_axi_araddr( i_axi_araddr), + .i_axi_arprot( i_axi_arprot), + // + .o_axi_rvalid(o_axi_rvalid), + .i_axi_rready(i_axi_rready), + .o_axi_rdata( o_axi_rdata), + .o_axi_rresp( o_axi_rresp), + // + .o_wb_cyc( r_wb_cyc), + .o_wb_stb( r_wb_stb), + .o_wb_addr( r_wb_addr), + .o_wb_sel( r_wb_sel), + .i_wb_stall(r_wb_stall), + .i_wb_ack( r_wb_ack), + .i_wb_data( i_wb_data), + .i_wb_err( r_wb_err) +`ifdef FORMAL + // {{{ + , + .f_first(f_rd_fifo_first), + .f_mid( f_rd_fifo_mid), + .f_last( f_rd_fifo_last) + // }}} +`endif + // }}} + ); + // }}} + end else begin : EMPTY_READS + // {{{ + assign r_wb_cyc = 0; + assign r_wb_stb = 0; + assign r_wb_addr = 0; + // + assign o_axi_arready = 1'b1; + assign o_axi_rvalid = (i_axi_arvalid)&&(o_axi_arready); + assign o_axi_rresp = (i_axi_arvalid) ? 2'b11 : 2'b00; + assign o_axi_rdata = 0; +`ifdef FORMAL + assign f_rd_fifo_first = 0; + assign f_rd_fifo_mid = 0; + assign f_rd_fifo_last = 0; +`endif + // }}} + end endgenerate + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // The arbiter that pastes the two sides together + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + + generate if (OPT_READONLY) + begin : ARB_RD + // {{{ + assign o_wb_cyc = r_wb_cyc; + assign o_wb_stb = r_wb_stb; + assign o_wb_we = 1'b0; + assign o_wb_addr = r_wb_addr; + assign o_wb_data = 32'h0; + assign o_wb_sel = r_wb_sel; + assign r_wb_ack = i_wb_ack; + assign r_wb_stall= i_wb_stall; + assign r_wb_ack = i_wb_ack; + assign r_wb_err = i_wb_err; + +`ifdef FORMAL + fwb_master #(.DW(DW), .AW(AW), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_STALL(F_MAXSTALL), + .F_MAX_ACK_DELAY(F_MAXDELAY)) + f_wb(i_clk, !i_axi_reset_n, + o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data, + o_wb_sel, + i_wb_ack, i_wb_stall, i_wb_data, i_wb_err, + f_wb_nreqs, f_wb_nacks, f_wb_outstanding); + + assign f_wb_rd_nreqs = f_wb_nreqs; + assign f_wb_rd_nacks = f_wb_nacks; + assign f_wb_rd_outstanding = f_wb_outstanding; + // + assign f_wb_wr_nreqs = 0; + assign f_wb_wr_nacks = 0; + assign f_wb_wr_outstanding = 0; +`endif + // }}} + end else if (OPT_WRITEONLY) + begin : ARB_WR + // {{{ + assign o_wb_cyc = w_wb_cyc; + assign o_wb_stb = w_wb_stb; + assign o_wb_we = 1'b1; + assign o_wb_addr = w_wb_addr; + assign o_wb_data = w_wb_data; + assign o_wb_sel = w_wb_sel; + assign w_wb_ack = i_wb_ack; + assign w_wb_stall= i_wb_stall; + assign w_wb_ack = i_wb_ack; + assign w_wb_err = i_wb_err; + +`ifdef FORMAL + fwb_master #(.DW(DW), .AW(AW), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_STALL(F_MAXSTALL), + .F_MAX_ACK_DELAY(F_MAXDELAY)) + f_wb(i_clk, !i_axi_reset_n, + o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data, + o_wb_sel, + i_wb_ack, i_wb_stall, i_wb_data, i_wb_err, + f_wb_nreqs, f_wb_nacks, f_wb_outstanding); + + assign f_wb_wr_nreqs = f_wb_nreqs; + assign f_wb_wr_nacks = f_wb_nacks; + assign f_wb_wr_outstanding = f_wb_outstanding; + // + assign f_wb_rd_nreqs = 0; + assign f_wb_rd_nacks = 0; + assign f_wb_rd_outstanding = 0; +`endif + // }}} + end else begin : ARB_WB + // {{{ + wbarbiter #( + // {{{ + .DW(DW), .AW(AW) +`ifdef FORMAL + , .F_LGDEPTH(F_LGDEPTH), + .F_MAX_STALL(F_MAXSTALL), + .F_MAX_ACK_DELAY(F_MAXDELAY) +`endif + // }}} + ) readorwrite( + // {{{ + .i_clk(i_clk), .i_reset(!i_axi_reset_n), + // Channel A - Reads + // {{{ + .i_a_cyc(r_wb_cyc), .i_a_stb(r_wb_stb), + .i_a_we(1'b0), + .i_a_adr(r_wb_addr), + .i_a_dat(w_wb_data), + .i_a_sel(r_wb_sel), + .o_a_stall(r_wb_stall), + .o_a_ack(r_wb_ack), + .o_a_err(r_wb_err), + // }}} + // Channel B + // {{{ + .i_b_cyc(w_wb_cyc), .i_b_stb(w_wb_stb), + .i_b_we(1'b1), + .i_b_adr(w_wb_addr), + .i_b_dat(w_wb_data), + .i_b_sel(w_wb_sel), + .o_b_stall(w_wb_stall), + .o_b_ack(w_wb_ack), + .o_b_err(w_wb_err), + // }}} + // Arbitrated outgoing channel + // {{{ + .o_cyc(o_wb_cyc), .o_stb(o_wb_stb), .o_we(o_wb_we), + .o_adr(o_wb_addr), + .o_dat(o_wb_data), + .o_sel(o_wb_sel), + .i_stall(i_wb_stall), + .i_ack(i_wb_ack), + .i_err(i_wb_err) + // }}} +`ifdef FORMAL + // {{{ + , + .f_nreqs(f_wb_nreqs), .f_nacks(f_wb_nacks), + .f_outstanding(f_wb_outstanding), + .f_a_nreqs(f_wb_rd_nreqs), .f_a_nacks(f_wb_rd_nacks), + .f_a_outstanding(f_wb_rd_outstanding), + .f_b_nreqs(f_wb_wr_nreqs), .f_b_nacks(f_wb_wr_nacks), + .f_b_outstanding(f_wb_wr_outstanding) + // }}} +`endif + // }}} + ); + // }}} + end endgenerate + // }}} + assign o_reset = (i_axi_reset_n == 1'b0); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Formal declarations + // {{{ + reg f_past_valid; + + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + + wire [(F_LGDEPTH-1):0] f_axi_rd_outstanding, + f_axi_wr_outstanding, + f_axi_awr_outstanding; + + wire [LGFIFO:0] f_awr_fifo_axi_used, + f_rd_fifo_axi_used; + // }}} + + initial assume(!i_axi_reset_n); + always @(*) + if (!f_past_valid) + assume(!i_axi_reset_n); + + faxil_slave #( + // {{{ + // .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_LGDEPTH), + .F_AXI_MAXWAIT(0), + .F_AXI_MAXDELAY(0) + // }}} + ) f_axi( + // {{{ + .i_clk(i_clk), .i_axi_reset_n(i_axi_reset_n), + // AXI write address channnel + .i_axi_awvalid(i_axi_awvalid), + .i_axi_awready(o_axi_awready), + .i_axi_awaddr( i_axi_awaddr), + .i_axi_awprot( i_axi_awprot), + // AXI write data channel + .i_axi_wvalid( i_axi_wvalid), + .i_axi_wready( o_axi_wready), + .i_axi_wdata( i_axi_wdata), + .i_axi_wstrb( i_axi_wstrb), + // AXI write acknowledgement channel + .i_axi_bvalid(o_axi_bvalid), + .i_axi_bready(i_axi_bready), + .i_axi_bresp( o_axi_bresp), + // AXI read address channel + .i_axi_arvalid(i_axi_arvalid), + .i_axi_arready(o_axi_arready), + .i_axi_araddr( i_axi_araddr), + .i_axi_arprot( i_axi_arprot), + // AXI read data return + .i_axi_rvalid( o_axi_rvalid), + .i_axi_rready( i_axi_rready), + .i_axi_rdata( o_axi_rdata), + .i_axi_rresp( o_axi_rresp), + // Quantify where we are within a transaction + .f_axi_rd_outstanding( f_axi_rd_outstanding), + .f_axi_wr_outstanding( f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding) + // }}} + ); + + assign f_awr_fifo_axi_used = f_wr_fifo_first - f_wr_fifo_last; + assign f_rd_fifo_axi_used = f_rd_fifo_first - f_rd_fifo_last; + + always @(*) + begin + assert(f_axi_rd_outstanding == f_rd_fifo_axi_used); + assert(f_axi_awr_outstanding == f_awr_fifo_axi_used+ (f_pending_awvalid?1:0)); + assert(f_axi_wr_outstanding == f_awr_fifo_axi_used+ (f_pending_wvalid?1:0)); + end + + always @(*) + if (OPT_READONLY) + begin + assert(f_axi_awr_outstanding == 0); + assert(f_axi_wr_outstanding == 0); + end + + always @(*) + if (OPT_WRITEONLY) + begin + assert(f_axi_rd_outstanding == 0); + end + + // + initial assert((!OPT_READONLY)||(!OPT_WRITEONLY)); + + always @(*) + if (OPT_READONLY) + begin + assume(i_axi_awvalid == 0); + assume(i_axi_wvalid == 0); + + assert(o_axi_bvalid == 0); + end + + always @(*) + if (OPT_WRITEONLY) + begin + assume(i_axi_arvalid == 0); + assert(o_axi_rvalid == 0); + end + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused_formal; + assign unused_formal = &{ 1'b0, f_wb_nreqs, f_wb_nacks, + f_wb_outstanding, f_wb_wr_nreqs, f_wb_wr_nacks, + f_wb_wr_outstanding, f_wb_rd_nreqs, f_wb_rd_nacks, + f_wb_rd_outstanding }; + // Verilator lint_on UNUSED + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/axlite_wrapper.vhd b/rtl/wb2axip/axlite_wrapper.vhd new file mode 100644 index 0000000..591d56d --- /dev/null +++ b/rtl/wb2axip/axlite_wrapper.vhd @@ -0,0 +1,136 @@ +-------------------------------------------------------------------------------- +-- +-- Filename: axlite_wrapper.vhd +-- +-- Project: WB2AXIPSP: bus bridges and other odds and ends +-- +-- Purpose: When wrapped with this wrapper, the axlite2wbsp.v core was +-- verified to work in FPGA silicon via Vivado. +-- +-- Thank you Ambroz for donating this code! +-- +-- Creator: Ambroz Bizjak +-- +-------------------------------------------------------------------------------- +-- +-- Copyright (C) 2019-2023, Gisselquist Technology, LLC +-- +-- This file is part of the WB2AXIP project. +-- +-- The WB2AXIP project contains free software and gateware, licensed under the +-- Apache License, Version 2.0 (the "License"). You may not use this project, +-- or this file, except in compliance with the License. You may obtain a copy +-- of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +-- License for the specific language governing permissions and limitations +-- under the License. +-- +-------------------------------------------------------------------------------- +-- +-- +library IEEE; +use IEEE.STD_LOGIC_1164.ALL; + +entity axlite2wbsp_wrapper is + generic ( + C_AXI_ADDR_WIDTH : integer := 28; + LGFIFO : integer := 4; + F_MAXSTALL : integer := 3; + F_MAXDELAY : integer := 3; + + --- Must not be changed. + C_AXI_DATA_WIDTH : integer := 32 + ); + port ( + s_axi_aclk : in std_logic; + s_axi_aresetn : in std_logic; + + s_axi_awready : out std_logic; + s_axi_awaddr : in std_logic_vector(C_AXI_ADDR_WIDTH-1 downto 0); + s_axi_awcache : in std_logic_vector(3 downto 0); + s_axi_awprot : in std_logic_vector(2 downto 0); + s_axi_awvalid : in std_logic; + s_axi_wready : out std_logic; + s_axi_wdata : in std_logic_vector(C_AXI_DATA_WIDTH-1 downto 0); + s_axi_wstrb : in std_logic_vector(C_AXI_DATA_WIDTH/8-1 downto 0); + s_axi_wvalid : in std_logic; + s_axi_bresp : out std_logic_vector(1 downto 0); + s_axi_bvalid : out std_logic; + s_axi_bready : in std_logic; + s_axi_arready : out std_logic; + s_axi_araddr : in std_logic_vector(C_AXI_ADDR_WIDTH-1 downto 0); + s_axi_arcache : in std_logic_vector(3 downto 0); + s_axi_arprot : in std_logic_vector(2 downto 0); + s_axi_arvalid : in std_logic; + s_axi_rresp : out std_logic_vector(1 downto 0); + s_axi_rvalid : out std_logic; + s_axi_rdata : out std_logic_vector(C_AXI_DATA_WIDTH-1 downto 0); + s_axi_rready : in std_logic; + + m_wb_reset : out std_logic; + m_wb_cyc : out std_logic; + m_wb_stb : out std_logic; + m_wb_we : out std_logic; + m_wb_adr : out std_logic_vector(C_AXI_ADDR_WIDTH-2-1 downto 0); + m_wb_dat_w : out std_logic_vector(C_AXI_DATA_WIDTH-1 downto 0); + m_wb_sel : out std_logic_vector(C_AXI_DATA_WIDTH/8-1 downto 0); + m_wb_ack : in std_logic; + m_wb_stall : in std_logic; + m_wb_dat_r : in std_logic_vector(C_AXI_DATA_WIDTH-1 downto 0); + m_wb_err : in std_logic + ); +end axlite2wbsp_wrapper; + +architecture Behavioral of axlite2wbsp_wrapper is + +begin + axlite2wbsp : entity work.axlite2wbsp + generic map ( + C_AXI_ADDR_WIDTH => C_AXI_ADDR_WIDTH, + LGFIFO => LGFIFO, + F_MAXSTALL => F_MAXSTALL, + F_MAXDELAY => F_MAXDELAY + ) + port map ( + i_clk => s_axi_aclk, + i_axi_reset_n => s_axi_aresetn, + o_axi_awready => s_axi_awready, + i_axi_awaddr => s_axi_awaddr, + i_axi_awcache => s_axi_awcache, + i_axi_awprot => s_axi_awprot, + i_axi_awvalid => s_axi_awvalid, + o_axi_wready => s_axi_wready, + i_axi_wdata => s_axi_wdata, + i_axi_wstrb => s_axi_wstrb, + i_axi_wvalid => s_axi_wvalid, + o_axi_bresp => s_axi_bresp, + o_axi_bvalid => s_axi_bvalid, + i_axi_bready => s_axi_bready, + o_axi_arready => s_axi_arready, + i_axi_araddr => s_axi_araddr, + i_axi_arcache => s_axi_arcache, + i_axi_arprot => s_axi_arprot, + i_axi_arvalid => s_axi_arvalid, + o_axi_rresp => s_axi_rresp, + o_axi_rvalid => s_axi_rvalid, + o_axi_rdata => s_axi_rdata, + i_axi_rready => s_axi_rready, + o_reset => m_wb_reset, + o_wb_cyc => m_wb_cyc, + o_wb_stb => m_wb_stb, + o_wb_we => m_wb_we, + o_wb_addr => m_wb_adr, + o_wb_data => m_wb_dat_w, + o_wb_sel => m_wb_sel, + i_wb_ack => m_wb_ack, + i_wb_stall => m_wb_stall, + i_wb_data => m_wb_dat_r, + i_wb_err => m_wb_err + ); + +end Behavioral; diff --git a/rtl/wb2axip/demoaxi.v b/rtl/wb2axip/demoaxi.v new file mode 100644 index 0000000..1babb05 --- /dev/null +++ b/rtl/wb2axip/demoaxi.v @@ -0,0 +1,720 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: demoaxi.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Demonstrate an AXI-lite bus design. The goal of this design +// is to support a completely pipelined AXI-lite transaction +// which can transfer one data item per clock. +// +// Note that the AXI spec requires that there be no combinatorial +// logic between input ports and output ports. Hence all of the *valid +// and *ready signals produced here are registered. This forces us into +// the buffered handshake strategy. +// +// Some curious variable meanings below: +// +// !axi_arvalid is synonymous with having a request, but stalling because +// of a current request sitting in axi_rvalid with !axi_rready +// !axi_awvalid is also synonymous with having an axi address being +// received, but either the axi_bvalid && !axi_bready, or +// no write data has been received +// !axi_wvalid is similar to axi_awvalid. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2018-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// +`timescale 1 ns / 1 ps +// }}} +module demoaxi #( + // {{{ + // Users to add parameters here + parameter [0:0] OPT_READ_SIDEEFFECTS = 1, + // User parameters ends + // Do not modify the parameters beyond this line + // Width of S_AXI data bus + parameter integer C_S_AXI_DATA_WIDTH = 32, + // Width of S_AXI address bus + parameter integer C_S_AXI_ADDR_WIDTH = 8 + // }}} + ) ( + // {{{ + // Users to add ports here + // No user ports (yet) in this design + // User ports ends + + // Do not modify the ports beyond this line + // Global Clock Signal + input wire S_AXI_ACLK, + // Global Reset Signal. This Signal is Active LOW + input wire S_AXI_ARESETN, + // Write address (issued by master, acceped by Slave) + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, + // Write channel Protection type. This signal indicates the + // privilege and security level of the transaction, and whether + // the transaction is a data access or an instruction access. + input wire [2 : 0] S_AXI_AWPROT, + // Write address valid. This signal indicates that the master + // signaling valid write address and control information. + input wire S_AXI_AWVALID, + // Write address ready. This signal indicates that the slave + // is ready to accept an address and associated control signals. + output wire S_AXI_AWREADY, + // Write data (issued by master, acceped by Slave) + input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, + // Write strobes. This signal indicates which byte lanes hold + // valid data. There is one write strobe bit for each eight + // bits of the write data bus. + input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, + // Write valid. This signal indicates that valid write + // data and strobes are available. + input wire S_AXI_WVALID, + // Write ready. This signal indicates that the slave + // can accept the write data. + output wire S_AXI_WREADY, + // Write response. This signal indicates the status + // of the write transaction. + output wire [1 : 0] S_AXI_BRESP, + // Write response valid. This signal indicates that the channel + // is signaling a valid write response. + output wire S_AXI_BVALID, + // Response ready. This signal indicates that the master + // can accept a write response. + input wire S_AXI_BREADY, + // Read address (issued by master, acceped by Slave) + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, + // Protection type. This signal indicates the privilege + // and security level of the transaction, and whether the + // transaction is a data access or an instruction access. + input wire [2 : 0] S_AXI_ARPROT, + // Read address valid. This signal indicates that the channel + // is signaling valid read address and control information. + input wire S_AXI_ARVALID, + // Read address ready. This signal indicates that the slave is + // ready to accept an address and associated control signals. + output wire S_AXI_ARREADY, + // Read data (issued by slave) + output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, + // Read response. This signal indicates the status of the + // read transfer. + output wire [1 : 0] S_AXI_RRESP, + // Read valid. This signal indicates that the channel is + // signaling the required read data. + output wire S_AXI_RVALID, + // Read ready. This signal indicates that the master can + // accept the read data and response information. + input wire S_AXI_RREADY + // }}} + ); + + // Local declarations + // {{{ + // AXI4LITE signals + reg axi_awready; + reg axi_wready; + reg axi_bvalid; + reg axi_arready; + reg [C_S_AXI_DATA_WIDTH-1 : 0] axi_rdata; + reg axi_rvalid; + + // Example-specific design signals + // local parameter for addressing 32 bit / 64 bit C_S_AXI_DATA_WIDTH + // ADDR_LSB is used for addressing 32/64 bit registers/memories + // ADDR_LSB = 2 for 32 bits (n downto 2) + // ADDR_LSB = 3 for 64 bits (n downto 3) + localparam integer ADDR_LSB = 2; + localparam integer AW = C_S_AXI_ADDR_WIDTH-2; + localparam integer DW = C_S_AXI_DATA_WIDTH; + //---------------------------------------------- + //-- Signals for user logic register space example + //------------------------------------------------ + reg [DW-1:0] slv_mem [0:63]; + + // I/O Connections assignments + + assign S_AXI_AWREADY = axi_awready; + assign S_AXI_WREADY = axi_wready; + assign S_AXI_BRESP = 2'b00; // The OKAY response + assign S_AXI_BVALID = axi_bvalid; + assign S_AXI_ARREADY = axi_arready; + assign S_AXI_RDATA = axi_rdata; + assign S_AXI_RRESP = 2'b00; // The OKAY response + assign S_AXI_RVALID = axi_rvalid; + // Implement axi_*wready generation + // }}} + ////////////////////////////////////// + // + // Read processing + // + // + wire valid_read_request, + read_response_stall; + + assign valid_read_request = S_AXI_ARVALID || !S_AXI_ARREADY; + assign read_response_stall = S_AXI_RVALID && !S_AXI_RREADY; + + // + // The read response channel valid signal + // + initial axi_rvalid = 1'b0; + always @(posedge S_AXI_ACLK ) + if (!S_AXI_ARESETN) + axi_rvalid <= 0; + else if (read_response_stall) + // Need to stay valid as long as the return path is stalled + axi_rvalid <= 1'b1; + else if (valid_read_request) + axi_rvalid <= 1'b1; + else + // Any stall has cleared, so we can always + // clear the valid signal in this case + axi_rvalid <= 1'b0; + + reg [C_S_AXI_ADDR_WIDTH-1 : 0] pre_raddr, rd_addr; + + // Buffer the address + always @(posedge S_AXI_ACLK) + if (S_AXI_ARREADY) + pre_raddr <= S_AXI_ARADDR; + + always @(*) + if (!axi_arready) + rd_addr = pre_raddr; + else + rd_addr = S_AXI_ARADDR; + + // + // Read the data + // + always @(posedge S_AXI_ACLK) + if (!read_response_stall + &&(!OPT_READ_SIDEEFFECTS || valid_read_request)) + // If the outgoing channel is not stalled (above) + // then read + axi_rdata <= slv_mem[rd_addr[AW+ADDR_LSB-1:ADDR_LSB]]; + + // + // The read address channel ready signal + // + initial axi_arready = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arready <= 1'b1; + else if (read_response_stall) + begin + // Outgoing channel is stalled + // As long as something is already in the buffer, + // axi_arready needs to stay low + axi_arready <= !valid_read_request; + end else + axi_arready <= 1'b1; + + ////////////////////////////////////// + // + // Write processing + // + // + reg [C_S_AXI_ADDR_WIDTH-1 : 0] pre_waddr, waddr; + reg [C_S_AXI_DATA_WIDTH-1 : 0] pre_wdata, wdata; + reg [(C_S_AXI_DATA_WIDTH/8)-1 : 0] pre_wstrb, wstrb; + + wire valid_write_address, valid_write_data, + write_response_stall; + + assign valid_write_address = S_AXI_AWVALID || !axi_awready; + assign valid_write_data = S_AXI_WVALID || !axi_wready; + assign write_response_stall= S_AXI_BVALID && !S_AXI_BREADY; + + // + // The write address channel ready signal + // + initial axi_awready = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_awready <= 1'b1; + else if (write_response_stall) + begin + // The output channel is stalled + // If our buffer is full, we need to remain stalled + // Likewise if it is empty, and there's a request, + // we'll need to stall. + axi_awready <= !valid_write_address; + end else if (valid_write_data) + // The output channel is clear, and write data + // are available + axi_awready <= 1'b1; + else + // If we were ready before, then remain ready unless an + // address unaccompanied by data shows up + axi_awready <= ((axi_awready)&&(!S_AXI_AWVALID)); + // This is equivalent to + // axi_awready <= !valid_write_address + + // + // The write data channel ready signal + // + initial axi_wready = 1'b1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_wready <= 1'b1; + else if (write_response_stall) + // The output channel is stalled + // We can remain ready until valid + // write data shows up + axi_wready <= !valid_write_data; + else if (valid_write_address) + // The output channel is clear, and a write address + // is available + axi_wready <= 1'b1; + else + // if we were ready before, and there's no new data avaialble + // to cause us to stall, remain ready + axi_wready <= (axi_wready)&&(!S_AXI_WVALID); + // This is equivalent to + // axi_wready <= !valid_write_data + + + // Buffer the address + always @(posedge S_AXI_ACLK) + if (S_AXI_AWREADY) + pre_waddr <= S_AXI_AWADDR; + + // Buffer the data + always @(posedge S_AXI_ACLK) + if (S_AXI_WREADY) + begin + pre_wdata <= S_AXI_WDATA; + pre_wstrb <= S_AXI_WSTRB; + end + + always @(*) + if (!axi_awready) + // Read the write address from our "buffer" + waddr = pre_waddr; + else + waddr = S_AXI_AWADDR; + + always @(*) + if (!axi_wready) + begin + // Read the write data from our "buffer" + wstrb = pre_wstrb; + wdata = pre_wdata; + end else begin + wstrb = S_AXI_WSTRB; + wdata = S_AXI_WDATA; + end + + // + // Actually (finally) write the data + // + always @(posedge S_AXI_ACLK ) + // If the output channel isn't stalled, and + if (!write_response_stall + // If we have a valid address, and + && valid_write_address + // If we have valid data + && valid_write_data) + begin + if (wstrb[0]) + slv_mem[waddr[AW+ADDR_LSB-1:ADDR_LSB]][7:0] + <= wdata[7:0]; + if (wstrb[1]) + slv_mem[waddr[AW+ADDR_LSB-1:ADDR_LSB]][15:8] + <= wdata[15:8]; + if (wstrb[2]) + slv_mem[waddr[AW+ADDR_LSB-1:ADDR_LSB]][23:16] + <= wdata[23:16]; + if (wstrb[3]) + slv_mem[waddr[AW+ADDR_LSB-1:ADDR_LSB]][31:24] + <= wdata[31:24]; + end + + // + // The write response channel valid signal + // + initial axi_bvalid = 1'b0; + always @(posedge S_AXI_ACLK ) + if (!S_AXI_ARESETN) + axi_bvalid <= 1'b0; + // + // The outgoing response channel should indicate a valid write if ... + // 1. We have a valid address, and + else if (valid_write_address + // 2. We had valid data + && valid_write_data) + // It doesn't matter here if we are stalled or not + // We can keep setting ready as often as we want + axi_bvalid <= 1'b1; + else if (S_AXI_BREADY) + // Otherwise, if BREADY was true, then it was just accepted + // and can return to idle now + axi_bvalid <= 1'b0; + + // Make Verilator happy + // Verilator lint_off UNUSED + wire [4*ADDR_LSB+5:0] unused; + assign unused = { S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_AWADDR[ADDR_LSB-1:0], + rd_addr[ADDR_LSB-1:0], + waddr[ADDR_LSB-1:0], + S_AXI_ARADDR[ADDR_LSB-1:0] }; + // Verilator lint_on UNUSED + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam F_LGDEPTH = 4; + + reg f_past_valid; + wire [(F_LGDEPTH-1):0] f_axi_awr_outstanding, + f_axi_wr_outstanding, + f_axi_rd_outstanding; + + faxil_slave #(// .C_AXI_DATA_WIDTH(C_S_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_S_AXI_ADDR_WIDTH), + // .F_OPT_NO_READS(1'b0), + // .F_OPT_NO_WRITES(1'b0), + .F_OPT_XILINX(1), + .F_LGDEPTH(F_LGDEPTH)) + properties ( + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr(S_AXI_AWADDR), + .i_axi_awprot(S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp(S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr(S_AXI_ARADDR), + .i_axi_arprot(S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + // + .f_axi_rd_outstanding(f_axi_rd_outstanding), + .f_axi_wr_outstanding(f_axi_wr_outstanding), + .f_axi_awr_outstanding(f_axi_awr_outstanding)); + + initial f_past_valid = 1'b0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1'b1; + + /////// + // + // Properties necessary to pass induction + always @(*) + if (S_AXI_ARESETN) + begin + if (!S_AXI_RVALID) + assert(f_axi_rd_outstanding == 0); + else if (!S_AXI_ARREADY) + assert((f_axi_rd_outstanding == 2)||(f_axi_rd_outstanding == 1)); + else + assert(f_axi_rd_outstanding == 1); + end + + always @(*) + if (S_AXI_ARESETN) + begin + if (axi_bvalid) + begin + assert(f_axi_awr_outstanding == 1+(axi_awready ? 0:1)); + assert(f_axi_wr_outstanding == 1+(axi_wready ? 0:1)); + end else begin + assert(f_axi_awr_outstanding == (axi_awready ? 0:1)); + assert(f_axi_wr_outstanding == (axi_wready ? 0:1)); + end + end + + + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // + // In addition to making sure the design returns a value, any value, + // let's cover returning three values on adjacent clocks--just to prove + // we can. + // + //////////////////////////////////////////////////////////////////////// + // + // + always @(posedge S_AXI_ACLK ) + if ((f_past_valid)&&(S_AXI_ARESETN)) + cover(($past((S_AXI_BVALID && S_AXI_BREADY))) + &&($past((S_AXI_BVALID && S_AXI_BREADY),2)) + &&(S_AXI_BVALID && S_AXI_BREADY)); + + always @(posedge S_AXI_ACLK ) + if ((f_past_valid)&&(S_AXI_ARESETN)) + cover(($past((S_AXI_RVALID && S_AXI_RREADY))) + &&($past((S_AXI_RVALID && S_AXI_RREADY),2)) + &&(S_AXI_RVALID && S_AXI_RREADY)); + + // Let's go just one further, and verify we can do three returns in a + // row. Why? It might just be possible that one value was waiting + // already, and so we haven't yet tested that two requests could be + // made in a row. + always @(posedge S_AXI_ACLK ) + if ((f_past_valid)&&(S_AXI_ARESETN)) + cover(($past((S_AXI_BVALID && S_AXI_BREADY))) + &&($past((S_AXI_BVALID && S_AXI_BREADY),2)) + &&($past((S_AXI_BVALID && S_AXI_BREADY),3)) + &&(S_AXI_BVALID && S_AXI_BREADY)); + + always @(posedge S_AXI_ACLK ) + if ((f_past_valid)&&(S_AXI_ARESETN)) + cover(($past((S_AXI_RVALID && S_AXI_RREADY))) + &&($past((S_AXI_RVALID && S_AXI_RREADY),2)) + &&($past((S_AXI_RVALID && S_AXI_RREADY),3)) + &&(S_AXI_RVALID && S_AXI_RREADY)); + + // + // Let's create a sophisticated cover statement designed to show off + // how our core can handle stalls and non-valids, synchronizing + // across multiple scenarios + reg [22:0] fw_wrdemo_pipe, fr_wrdemo_pipe; + always @(*) + if (!S_AXI_ARESETN) + fw_wrdemo_pipe = 0; + else begin + fw_wrdemo_pipe[0] = (S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[1] = fr_wrdemo_pipe[0] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[2] = fr_wrdemo_pipe[1] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + // + // + fw_wrdemo_pipe[3] = fr_wrdemo_pipe[2] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[4] = fr_wrdemo_pipe[3] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[5] = fr_wrdemo_pipe[4] + &&(!S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[6] = fr_wrdemo_pipe[5] + &&(S_AXI_AWVALID) + &&( S_AXI_WVALID) + &&( S_AXI_BREADY); + fw_wrdemo_pipe[7] = fr_wrdemo_pipe[6] + &&(!S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&( S_AXI_BREADY); + fw_wrdemo_pipe[8] = fr_wrdemo_pipe[7] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[9] = fr_wrdemo_pipe[8] +// &&(S_AXI_AWVALID) +// &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[10] = fr_wrdemo_pipe[9] +// &&(S_AXI_AWVALID) +// &&(S_AXI_WVALID) + // &&(S_AXI_BREADY); + &&(S_AXI_BREADY); + fw_wrdemo_pipe[11] = fr_wrdemo_pipe[10] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(!S_AXI_BREADY); + fw_wrdemo_pipe[12] = fr_wrdemo_pipe[11] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[13] = fr_wrdemo_pipe[12] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[14] = fr_wrdemo_pipe[13] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(f_axi_awr_outstanding == 0) + &&(f_axi_wr_outstanding == 0) + &&(S_AXI_BREADY); + // + // + // + fw_wrdemo_pipe[15] = fr_wrdemo_pipe[14] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[16] = fr_wrdemo_pipe[15] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[17] = fr_wrdemo_pipe[16] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[18] = fr_wrdemo_pipe[17] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(!S_AXI_BREADY); + fw_wrdemo_pipe[19] = fr_wrdemo_pipe[18] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[20] = fr_wrdemo_pipe[19] + &&(S_AXI_AWVALID) + &&(S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[21] = fr_wrdemo_pipe[20] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + fw_wrdemo_pipe[22] = fr_wrdemo_pipe[21] + &&(!S_AXI_AWVALID) + &&(!S_AXI_WVALID) + &&(S_AXI_BREADY); + end + + always @(posedge S_AXI_ACLK) + fr_wrdemo_pipe <= fw_wrdemo_pipe; + + always @(*) + if (S_AXI_ARESETN) + begin + cover(fw_wrdemo_pipe[0]); + cover(fw_wrdemo_pipe[1]); + cover(fw_wrdemo_pipe[2]); + cover(fw_wrdemo_pipe[3]); + cover(fw_wrdemo_pipe[4]); + cover(fw_wrdemo_pipe[5]); + cover(fw_wrdemo_pipe[6]); + cover(fw_wrdemo_pipe[7]); // + cover(fw_wrdemo_pipe[8]); + cover(fw_wrdemo_pipe[9]); + cover(fw_wrdemo_pipe[10]); + cover(fw_wrdemo_pipe[11]); + cover(fw_wrdemo_pipe[12]); + cover(fw_wrdemo_pipe[13]); + cover(fw_wrdemo_pipe[14]); + cover(fw_wrdemo_pipe[15]); + cover(fw_wrdemo_pipe[16]); + cover(fw_wrdemo_pipe[17]); + cover(fw_wrdemo_pipe[18]); + cover(fw_wrdemo_pipe[19]); + cover(fw_wrdemo_pipe[20]); + cover(fw_wrdemo_pipe[21]); + cover(fw_wrdemo_pipe[22]); + end + + // + // Now let's repeat, but for a read demo + reg [10:0] fw_rddemo_pipe, fr_rddemo_pipe; + always @(*) + if (!S_AXI_ARESETN) + fw_rddemo_pipe = 0; + else begin + fw_rddemo_pipe[0] = (S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[1] = fr_rddemo_pipe[0] + &&(!S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[2] = fr_rddemo_pipe[1] + &&(!S_AXI_ARVALID) + &&(S_AXI_RREADY); + // + // + fw_rddemo_pipe[3] = fr_rddemo_pipe[2] + &&(S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[4] = fr_rddemo_pipe[3] + &&(S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[5] = fr_rddemo_pipe[4] + &&(S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[6] = fr_rddemo_pipe[5] + &&(S_AXI_ARVALID) + &&(!S_AXI_RREADY); + fw_rddemo_pipe[7] = fr_rddemo_pipe[6] + &&(S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[8] = fr_rddemo_pipe[7] + &&(S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[9] = fr_rddemo_pipe[8] + &&(!S_AXI_ARVALID) + &&(S_AXI_RREADY); + fw_rddemo_pipe[10] = fr_rddemo_pipe[9] + &&(f_axi_rd_outstanding == 0); + end + + initial fr_rddemo_pipe = 0; + always @(posedge S_AXI_ACLK) + fr_rddemo_pipe <= fw_rddemo_pipe; + + always @(*) + begin + cover(fw_rddemo_pipe[0]); + cover(fw_rddemo_pipe[1]); + cover(fw_rddemo_pipe[2]); + cover(fw_rddemo_pipe[3]); + cover(fw_rddemo_pipe[4]); + cover(fw_rddemo_pipe[5]); + cover(fw_rddemo_pipe[6]); + cover(fw_rddemo_pipe[7]); + cover(fw_rddemo_pipe[8]); + cover(fw_rddemo_pipe[9]); + cover(fw_rddemo_pipe[10]); + end +`endif +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/demofull.v b/rtl/wb2axip/demofull.v new file mode 100644 index 0000000..5d0c604 --- /dev/null +++ b/rtl/wb2axip/demofull.v @@ -0,0 +1,1285 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: demofull.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Demonstrate a formally verified AXI4 core with a (basic) +// interface. This interface is explained below. +// +// Performance: This core has been designed for a total throughput of one beat +// per clock cycle. Both read and write channels can achieve +// this. The write channel will also introduce two clocks of latency, +// assuming no other latency from the master. This means it will take +// a minimum of 3+AWLEN clock cycles per transaction of (1+AWLEN) beats, +// including both address and acknowledgment cycles. The read channel +// will introduce a single clock of latency, requiring 2+ARLEN cycles +// per transaction of 1+ARLEN beats. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module demofull #( + // {{{ + parameter integer C_S_AXI_ID_WIDTH = 2, + parameter integer C_S_AXI_DATA_WIDTH = 32, + parameter integer C_S_AXI_ADDR_WIDTH = 6, + parameter [0:0] OPT_LOCK = 1'b0, + parameter [0:0] OPT_LOCKID = 1'b1, + parameter [0:0] OPT_LOWPOWER = 1'b0, + // Some useful short-hand definitions + localparam LSB = $clog2(C_S_AXI_DATA_WIDTH)-3 + // }}} + ) ( + // {{{ + // User ports + // {{{ + // A very basic protocol-independent peripheral interface + // 1. A value will be written any time o_we is true + // 2. A value will be read any time o_rd is true + // 3. Such a slave might just as easily be written as: + // + // always @(posedge S_AXI_ACLK) + // if (o_we) + // begin + // for(k=0; k<C_S_AXI_DATA_WIDTH/8; k=k+1) + // begin + // if (o_wstrb[k]) + // mem[o_waddr[AW-1:LSB]][k*8+:8] <= o_wdata[k*8+:8] + // end + // end + // + // always @(posedge S_AXI_ACLK) + // if (o_rd) + // i_rdata <= mem[o_raddr[AW-1:LSB]]; + // + // 4. The rule on the input is that i_rdata must be registered, + // and that it must only change if o_rd is true. Violating + // this rule will cause this core to violate the AXI + // protocol standard, as this value is not registered within + // this core + output reg o_we, + output reg [C_S_AXI_ADDR_WIDTH-LSB-1:0] o_waddr, + output reg [C_S_AXI_DATA_WIDTH-1:0] o_wdata, + output reg [C_S_AXI_DATA_WIDTH/8-1:0] o_wstrb, + // + output reg o_rd, + output reg [C_S_AXI_ADDR_WIDTH-LSB-1:0] o_raddr, + input wire [C_S_AXI_DATA_WIDTH-1:0] i_rdata, + // + // User ports ends + // }}} + // Do not modify the ports beyond this line + // AXI signals + // {{{ + // Global Clock Signal + input wire S_AXI_ACLK, + // Global Reset Signal. This Signal is Active LOW + input wire S_AXI_ARESETN, + + // Write address channel + // {{{ + // Write Address ID + input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_AWID, + // Write address + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, + // Burst length. The burst length gives the exact number of + // transfers in a burst + input wire [7 : 0] S_AXI_AWLEN, + // Burst size. This signal indicates the size of each transfer + // in the burst + input wire [2 : 0] S_AXI_AWSIZE, + // Burst type. The burst type and the size information, + // determine how the address for each transfer within the burst + // is calculated. + input wire [1 : 0] S_AXI_AWBURST, + // Lock type. Provides additional information about the + // atomic characteristics of the transfer. + input wire S_AXI_AWLOCK, + // Memory type. This signal indicates how transactions + // are required to progress through a system. + input wire [3 : 0] S_AXI_AWCACHE, + // Protection type. This signal indicates the privilege + // and security level of the transaction, and whether + // the transaction is a data access or an instruction access. + input wire [2 : 0] S_AXI_AWPROT, + // Quality of Service, QoS identifier sent for each + // write transaction. + input wire [3 : 0] S_AXI_AWQOS, + // Region identifier. Permits a single physical interface + // on a slave to be used for multiple logical interfaces. + // Write address valid. This signal indicates that + // the channel is signaling valid write address and + // control information. + input wire S_AXI_AWVALID, + // Write address ready. This signal indicates that + // the slave is ready to accept an address and associated + // control signals. + output wire S_AXI_AWREADY, + // }}} + // Write data channel + // {{{ + // Write Data + input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, + // Write strobes. This signal indicates which byte + // lanes hold valid data. There is one write strobe + // bit for each eight bits of the write data bus. + input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, + // Write last. This signal indicates the last transfer + // in a write burst. + input wire S_AXI_WLAST, + // Optional User-defined signal in the write data channel. + // Write valid. This signal indicates that valid write + // data and strobes are available. + input wire S_AXI_WVALID, + // Write ready. This signal indicates that the slave + // can accept the write data. + output wire S_AXI_WREADY, + // }}} + // Write response channel + // {{{ + // Response ID tag. This signal is the ID tag of the + // write response. + output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_BID, + // Write response. This signal indicates the status + // of the write transaction. + output wire [1 : 0] S_AXI_BRESP, + // Optional User-defined signal in the write response channel. + // Write response valid. This signal indicates that the + // channel is signaling a valid write response. + output wire S_AXI_BVALID, + // Response ready. This signal indicates that the master + // can accept a write response. + input wire S_AXI_BREADY, + // }}} + // Read address channel + // {{{ + // Read address ID. This signal is the identification + // tag for the read address group of signals. + input wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_ARID, + // Read address. This signal indicates the initial + // address of a read burst transaction. + input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, + // Burst length. The burst length gives the exact number of + // transfers in a burst + input wire [7 : 0] S_AXI_ARLEN, + // Burst size. This signal indicates the size of each transfer + // in the burst + input wire [2 : 0] S_AXI_ARSIZE, + // Burst type. The burst type and the size information, + // determine how the address for each transfer within the + // burst is calculated. + input wire [1 : 0] S_AXI_ARBURST, + // Lock type. Provides additional information about the + // atomic characteristics of the transfer. + input wire S_AXI_ARLOCK, + // Memory type. This signal indicates how transactions + // are required to progress through a system. + input wire [3 : 0] S_AXI_ARCACHE, + // Protection type. This signal indicates the privilege + // and security level of the transaction, and whether + // the transaction is a data access or an instruction access. + input wire [2 : 0] S_AXI_ARPROT, + // Quality of Service, QoS identifier sent for each + // read transaction. + input wire [3 : 0] S_AXI_ARQOS, + // Region identifier. Permits a single physical interface + // on a slave to be used for multiple logical interfaces. + // Optional User-defined signal in the read address channel. + // Write address valid. This signal indicates that + // the channel is signaling valid read address and + // control information. + input wire S_AXI_ARVALID, + // Read address ready. This signal indicates that + // the slave is ready to accept an address and associated + // control signals. + output wire S_AXI_ARREADY, + // }}} + // Read data (return) channel + // {{{ + // Read ID tag. This signal is the identification tag + // for the read data group of signals generated by the slave. + output wire [C_S_AXI_ID_WIDTH-1 : 0] S_AXI_RID, + // Read Data + output wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, + // Read response. This signal indicates the status of + // the read transfer. + output wire [1 : 0] S_AXI_RRESP, + // Read last. This signal indicates the last transfer + // in a read burst. + output wire S_AXI_RLAST, + // Optional User-defined signal in the read address channel. + // Read valid. This signal indicates that the channel + // is signaling the required read data. + output wire S_AXI_RVALID, + // Read ready. This signal indicates that the master can + // accept the read data and response information. + input wire S_AXI_RREADY + // }}} + // }}} + // }}} + ); + + // Local declarations + // {{{ + // More useful shorthand definitions + localparam AW = C_S_AXI_ADDR_WIDTH; + localparam DW = C_S_AXI_DATA_WIDTH; + localparam IW = C_S_AXI_ID_WIDTH; + // Double buffer the write response channel only + reg [IW-1 : 0] r_bid; + reg r_bvalid; + reg [IW-1 : 0] axi_bid; + reg axi_bvalid; + + reg axi_awready, axi_wready; + reg [AW-1:0] waddr; + wire [AW-1:0] next_wr_addr; + + // Vivado will warn about wlen only using 4-bits. This is + // to be expected, since the axi_addr module only needs to use + // the bottom four bits of wlen to determine address increments + reg [7:0] wlen; + // Vivado will also warn about the top bit of wsize being unused. + // This is also to be expected for a DATA_WIDTH of 32-bits. + reg [2:0] wsize; + reg [1:0] wburst; + + wire m_awvalid, m_awlock; + reg m_awready; + wire [AW-1:0] m_awaddr; + wire [1:0] m_awburst; + wire [2:0] m_awsize; + wire [7:0] m_awlen; + wire [IW-1:0] m_awid; + + wire [AW-1:0] next_rd_addr; + + // Vivado will warn about rlen only using 4-bits. This is + // to be expected, since for a DATA_WIDTH of 32-bits, the axi_addr + // module only uses the bottom four bits of rlen to determine + // address increments + reg [7:0] rlen; + // Vivado will also warn about the top bit of wsize being unused. + // This is also to be expected for a DATA_WIDTH of 32-bits. + reg [2:0] rsize; + reg [1:0] rburst; + reg [IW-1:0] rid; + reg rlock; + reg axi_arready; + reg [8:0] axi_rlen; + reg [AW-1:0] raddr; + + // Read skid buffer + reg rskd_valid, rskd_last, rskd_lock; + wire rskd_ready; + reg [IW-1:0] rskd_id; + + // Exclusive address register checking + reg exclusive_write, block_write; + wire write_lock_valid; + reg axi_exclusive_write; + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AW Skid buffer + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + skidbuffer #( + // {{{ + .DW(AW+2+3+1+8+IW), + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(1'b0) + // }}} + ) awbuf( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data({ S_AXI_AWADDR, S_AXI_AWBURST, S_AXI_AWSIZE, + S_AXI_AWLOCK, S_AXI_AWLEN, S_AXI_AWID }), + .o_valid(m_awvalid), .i_ready(m_awready), + .o_data({ m_awaddr, m_awburst, m_awsize, + m_awlock, m_awlen, m_awid }) + // }}} + ); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // axi_awready, axi_wready + // {{{ + initial axi_awready = 1; + initial axi_wready = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + begin + axi_awready <= 1; + axi_wready <= 0; + end else if (m_awvalid && m_awready) + begin + axi_awready <= 0; + axi_wready <= 1; + end else if (S_AXI_WVALID && S_AXI_WREADY) + begin + axi_awready <= (S_AXI_WLAST)&&(!S_AXI_BVALID || S_AXI_BREADY); + axi_wready <= (!S_AXI_WLAST); + end else if (!axi_awready) + begin + if (S_AXI_WREADY) + axi_awready <= 1'b0; + else if (r_bvalid && !S_AXI_BREADY) + axi_awready <= 1'b0; + else + axi_awready <= 1'b1; + end + // }}} + + // Exclusive write calculation + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_LOCK) + begin + exclusive_write <= 0; + block_write <= 0; + end else if (m_awvalid && m_awready) + begin + exclusive_write <= 1'b0; + block_write <= 1'b0; + if (write_lock_valid) + exclusive_write <= 1'b1; + else if (m_awlock) + block_write <= 1'b1; + end else if (m_awready) + begin + exclusive_write <= 1'b0; + block_write <= 1'b0; + end + + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_LOCK) + axi_exclusive_write <= 0; + else if (!S_AXI_BVALID || S_AXI_BREADY) + begin + axi_exclusive_write <= exclusive_write; + if (OPT_LOWPOWER && (!S_AXI_WVALID || !S_AXI_WREADY || !S_AXI_WLAST) + && !r_bvalid) + axi_exclusive_write <= 0; + end + // }}} + + // Next write address calculation + // {{{ + always @(posedge S_AXI_ACLK) + if (m_awready) + begin + waddr <= m_awaddr; + wburst <= m_awburst; + wsize <= m_awsize; + wlen <= m_awlen; + end else if (S_AXI_WVALID) + waddr <= next_wr_addr; + + axi_addr #( + // {{{ + .AW(AW), .DW(DW) + // }}} + ) get_next_wr_addr( + // {{{ + waddr, wsize, wburst, wlen, + next_wr_addr + // }}} + ); + // }}} + + // o_w* + // {{{ + always @(posedge S_AXI_ACLK) + begin + o_we <= (S_AXI_WVALID && S_AXI_WREADY); + o_waddr <= waddr[AW-1:LSB]; + o_wdata <= S_AXI_WDATA; + if (block_write) + o_wstrb <= 0; + else + o_wstrb <= S_AXI_WSTRB; + + if (!S_AXI_ARESETN) + o_we <= 0; + if (OPT_LOWPOWER && (!S_AXI_ARESETN || !S_AXI_WVALID + || !S_AXI_WREADY)) + begin + o_waddr <= 0; + o_wdata <= 0; + o_wstrb <= 0; + end + end + // }}} + + // + // Write return path + // {{{ + // r_bvalid + // {{{ + initial r_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + r_bvalid <= 1'b0; + else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST + &&(S_AXI_BVALID && !S_AXI_BREADY)) + r_bvalid <= 1'b1; + else if (S_AXI_BREADY) + r_bvalid <= 1'b0; + // }}} + + // r_bid, axi_bid + // {{{ + initial r_bid = 0; + initial axi_bid = 0; + always @(posedge S_AXI_ACLK) + begin + if (m_awready && (!OPT_LOWPOWER || m_awvalid)) + r_bid <= m_awid; + + if (!S_AXI_BVALID || S_AXI_BREADY) + axi_bid <= r_bid; + + if (OPT_LOWPOWER && !S_AXI_ARESETN) + begin + r_bid <= 0; + axi_bid <= 0; + end + end + // }}} + + // axi_bvalid + // {{{ + initial axi_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_bvalid <= 0; + else if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST) + axi_bvalid <= 1; + else if (S_AXI_BREADY) + axi_bvalid <= r_bvalid; + // }}} + + // m_awready + // {{{ + always @(*) + begin + m_awready = axi_awready; + if (S_AXI_WVALID && S_AXI_WREADY && S_AXI_WLAST + && (!S_AXI_BVALID || S_AXI_BREADY)) + m_awready = 1; + end + // }}} + + // At one time, axi_awready was the same as S_AXI_AWREADY. Now, though, + // with the extra write address skid buffer, this is no longer the case. + // S_AXI_AWREADY is handled/created/managed by the skid buffer. + // + // assign S_AXI_AWREADY = axi_awready; + // + // The rest of these signals can be set according to their registered + // values above. + assign S_AXI_WREADY = axi_wready; + assign S_AXI_BVALID = axi_bvalid; + assign S_AXI_BID = axi_bid; + // + // This core does not produce any bus errors, nor does it support + // exclusive access, so 2'b00 will always be the correct response. + assign S_AXI_BRESP = { 1'b0, axi_exclusive_write }; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read processing + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // axi_arready + // {{{ + initial axi_arready = 1; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_arready <= 1; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_arready <= (S_AXI_ARLEN==0)&&(o_rd); + else if (o_rd) + axi_arready <= (axi_rlen <= 1); + // }}} + + // axi_rlen + // {{{ + initial axi_rlen = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axi_rlen <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY) + axi_rlen <= S_AXI_ARLEN + (o_rd ? 0:1); + else if (o_rd) + axi_rlen <= axi_rlen - 1; + // }}} + + // Next read address calculation + // {{{ + always @(posedge S_AXI_ACLK) + if (o_rd) + raddr <= next_rd_addr; + else if (S_AXI_ARREADY) + begin + raddr <= S_AXI_ARADDR; + if (OPT_LOWPOWER && !S_AXI_ARVALID) + raddr <= 0; + end + + // r* + // {{{ + always @(posedge S_AXI_ACLK) + if (S_AXI_ARREADY) + begin + rburst <= S_AXI_ARBURST; + rsize <= S_AXI_ARSIZE; + rlen <= S_AXI_ARLEN; + rid <= S_AXI_ARID; + rlock <= S_AXI_ARLOCK && S_AXI_ARVALID && OPT_LOCK; + + if (OPT_LOWPOWER && !S_AXI_ARVALID) + begin + rburst <= 0; + rsize <= 0; + rlen <= 0; + rid <= 0; + rlock <= 0; + end + end + // }}} + + axi_addr #( + // {{{ + .AW(AW), .DW(DW) + // }}} + ) get_next_rd_addr( + // {{{ + (S_AXI_ARREADY ? S_AXI_ARADDR : raddr), + (S_AXI_ARREADY ? S_AXI_ARSIZE : rsize), + (S_AXI_ARREADY ? S_AXI_ARBURST: rburst), + (S_AXI_ARREADY ? S_AXI_ARLEN : rlen), + next_rd_addr + // }}} + ); + // }}} + + // o_rd, o_raddr + // {{{ + always @(*) + begin + o_rd = (S_AXI_ARVALID || !S_AXI_ARREADY); + if (S_AXI_RVALID && !S_AXI_RREADY) + o_rd = 0; + if (rskd_valid && !rskd_ready) + o_rd = 0; + o_raddr = (S_AXI_ARREADY ? S_AXI_ARADDR[AW-1:LSB] : raddr[AW-1:LSB]); + end + // }}} + + // rskd_valid + // {{{ + initial rskd_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + rskd_valid <= 0; + else if (o_rd) + rskd_valid <= 1; + else if (rskd_ready) + rskd_valid <= 0; + // }}} + + // rskd_id + // {{{ + always @(posedge S_AXI_ACLK) + if (!rskd_valid || rskd_ready) + begin + if (S_AXI_ARVALID && S_AXI_ARREADY) + rskd_id <= S_AXI_ARID; + else + rskd_id <= rid; + end + // }}} + + // rskd_last + // {{{ + initial rskd_last = 0; + always @(posedge S_AXI_ACLK) + if (!rskd_valid || rskd_ready) + begin + rskd_last <= 0; + if (o_rd && axi_rlen == 1) + rskd_last <= 1; + if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN == 0) + rskd_last <= 1; + end + // }}} + + // rskd_lock + // {{{ + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_LOCK) + rskd_lock <= 1'b0; + else if (!rskd_valid || rskd_ready) + begin + rskd_lock <= 0; + if (!OPT_LOWPOWER || o_rd) + begin + if (S_AXI_ARVALID && S_AXI_ARREADY) + rskd_lock <= S_AXI_ARLOCK; + else + rskd_lock <= rlock; + end + end + // }}} + + + // Outgoing read skidbuffer + // {{{ + skidbuffer #( + // {{{ + .OPT_LOWPOWER(OPT_LOWPOWER), + .OPT_OUTREG(1), + .DW(IW+2+DW) + // }}} + ) rskid ( + // {{{ + .i_clk(S_AXI_ACLK), .i_reset(!S_AXI_ARESETN), + .i_valid(rskd_valid), .o_ready(rskd_ready), + .i_data({ rskd_id, rskd_lock, rskd_last, i_rdata }), + .o_valid(S_AXI_RVALID), .i_ready(S_AXI_RREADY), + .o_data({ S_AXI_RID, S_AXI_RRESP[0], S_AXI_RLAST, + S_AXI_RDATA }) + // }}} + ); + // }}} + + assign S_AXI_RRESP[1] = 1'b0; + assign S_AXI_ARREADY = axi_arready; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Exclusive address caching + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (OPT_LOCK && !OPT_LOCKID) + begin : EXCLUSIVE_ACCESS_BLOCK + // {{{ + // The AXI4 specification requires that we check one address + // per ID. This isn't that. This algorithm checks one ID, + // whichever the last ID was. It's designed to be lighter on + // the logic requirements, and (unnoticably) not (fully) spec + // compliant. (The difference, if noticed at all, will be in + // performance when multiple masters try to perform an exclusive + // transaction at once.) + + // Local declarations + // {{{ + reg w_valid_lock_request, w_cancel_lock, + w_lock_request, + lock_valid, returned_lock_valid; + reg [AW-LSB-1:0] lock_start, lock_end; + reg [3:0] lock_len; + reg [1:0] lock_burst; + reg [2:0] lock_size; + reg [IW-1:0] lock_id; + reg w_write_lock_valid; + // }}} + + // w_lock_request + // {{{ + always @(*) + begin + w_lock_request = 0; + if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLOCK) + w_lock_request = 1; + end + // }}} + + // w_valid_lock_request + // {{{ + always @(*) + begin + w_valid_lock_request = 0; + if (w_lock_request) + w_valid_lock_request = 1; + if (o_we && o_waddr == S_AXI_ARADDR[AW-1:LSB]) + w_valid_lock_request = 0; + end + // }}} + + // returned_lock_valid + // {{{ + initial returned_lock_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + returned_lock_valid <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY + && S_AXI_ARLOCK && S_AXI_ARID== lock_id) + returned_lock_valid <= 0; + else if (w_cancel_lock) + returned_lock_valid <= 0; + else if (rskd_valid && rskd_lock && rskd_ready) + returned_lock_valid <= lock_valid; + // }}} + + // w_cancel_lock + // {{{ + always @(*) + w_cancel_lock = (lock_valid && w_lock_request) + || (lock_valid && o_we + && o_waddr >= lock_start + && o_waddr <= lock_end + && o_wstrb != 0); + // }}} + + // lock_valid + // {{{ + initial lock_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_LOCK) + lock_valid <= 0; + else begin + if (S_AXI_ARVALID && S_AXI_ARREADY + && S_AXI_ARLOCK && S_AXI_ARID== lock_id) + lock_valid <= 0; + if (w_cancel_lock) + lock_valid <= 0; + if (w_valid_lock_request) + lock_valid <= 1; + end + // }}} + + // lock_start, lock_end, lock_len, lock_size, lock_id + // {{{ + always @(posedge S_AXI_ACLK) + if (w_valid_lock_request) + begin + lock_start <= S_AXI_ARADDR[C_S_AXI_ADDR_WIDTH-1:LSB]; + lock_end <= S_AXI_ARADDR[C_S_AXI_ADDR_WIDTH-1:LSB] + + ((S_AXI_ARBURST == 2'b00) ? 0 : S_AXI_ARLEN[3:0]); + lock_len <= S_AXI_ARLEN[3:0]; + lock_burst <= S_AXI_ARBURST; + lock_size <= S_AXI_ARSIZE; + lock_id <= S_AXI_ARID; + end + // }}} + + // w_write_lock_valid + // {{{ + always @(*) + begin + w_write_lock_valid = returned_lock_valid; + if (!m_awvalid || !m_awready || !m_awlock || !lock_valid) + w_write_lock_valid = 0; + if (m_awaddr[C_S_AXI_ADDR_WIDTH-1:LSB] != lock_start) + w_write_lock_valid = 0; + if (m_awid != lock_id) + w_write_lock_valid = 0; + if (m_awlen[3:0] != lock_len) // MAX transfer size is 16 beats + w_write_lock_valid = 0; + if (m_awburst != 2'b01 && lock_len != 0) + w_write_lock_valid = 0; + if (m_awsize != lock_size) + w_write_lock_valid = 0; + end + // }}} + + assign write_lock_valid = w_write_lock_valid; + // }}} + end else if (OPT_LOCK) // && OPT_LOCKID + begin : EXCLUSIVE_ACCESS_PER_ID + // {{{ + + genvar gk; + wire [(1<<IW)-1:0] write_lock_valid_per_id; + + for(gk=0; gk<(1<<IW); gk=gk+1) + begin : PER_ID_LOGIC + // {{{ + // Local declarations + // {{{ + reg w_valid_lock_request, + w_cancel_lock, + lock_valid, returned_lock_valid; + reg [1:0] lock_burst; + reg [2:0] lock_size; + reg [3:0] lock_len; + reg [AW-LSB-1:0] lock_start, lock_end; + reg w_write_lock_valid; + // }}} + + // valid_lock_request + // {{{ + always @(*) + begin + w_valid_lock_request = 0; + if (S_AXI_ARVALID && S_AXI_ARREADY + && S_AXI_ARID == gk[IW-1:0] + && S_AXI_ARLOCK) + w_valid_lock_request = 1; + if (o_we && o_waddr == S_AXI_ARADDR[AW-1:LSB]) + w_valid_lock_request = 0; + end + // }}} + + // returned_lock_valid + // {{{ + initial returned_lock_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + returned_lock_valid <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY + &&S_AXI_ARLOCK&&S_AXI_ARID== gk[IW-1:0]) + returned_lock_valid <= 0; + else if (w_cancel_lock) + returned_lock_valid <= 0; + else if (rskd_valid && rskd_lock && rskd_ready + && rskd_id == gk[IW-1:0]) + returned_lock_valid <= lock_valid; + // }}} + + // w_cancel_lock + // {{{ + always @(*) + w_cancel_lock=(lock_valid&&w_valid_lock_request) + || (lock_valid && o_we + && o_waddr >= lock_start + && o_waddr <= lock_end + && o_wstrb != 0); + // }}} + + // lock_valid + // {{{ + initial lock_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN || !OPT_LOCK) + lock_valid <= 0; + else begin + if (S_AXI_ARVALID && S_AXI_ARREADY + && S_AXI_ARLOCK + && S_AXI_ARID == gk[IW-1:0]) + lock_valid <= 0; + if (w_cancel_lock) + lock_valid <= 0; + if (w_valid_lock_request) + lock_valid <= 1; + end + // }}} + + // lock_start, lock_end, lock_len, lock_size + // {{{ + always @(posedge S_AXI_ACLK) + if (w_valid_lock_request) + begin + lock_start <= S_AXI_ARADDR[C_S_AXI_ADDR_WIDTH-1:LSB]; + // Verilator lint_off WIDTH + lock_end <= S_AXI_ARADDR[C_S_AXI_ADDR_WIDTH-1:LSB] + + ((S_AXI_ARBURST == 2'b00) ? 4'h0 : S_AXI_ARLEN[3:0]); + // Verilator lint_on WIDTH + lock_len <= S_AXI_ARLEN[3:0]; + lock_size <= S_AXI_ARSIZE; + lock_burst <= S_AXI_ARBURST; + end + // }}} + + // w_write_lock_valid + // {{{ + always @(*) + begin + w_write_lock_valid = returned_lock_valid; + if (!m_awvalid || !m_awready || !m_awlock || !lock_valid) + w_write_lock_valid = 0; + if (m_awaddr[C_S_AXI_ADDR_WIDTH-1:LSB] != lock_start) + w_write_lock_valid = 0; + if (m_awid[IW-1:0] != gk[IW-1:0]) + w_write_lock_valid = 0; + if (m_awlen[3:0] != lock_len) // MAX transfer size is 16 beats + w_write_lock_valid = 0; + if (m_awburst != 2'b01 && lock_len != 0) + w_write_lock_valid = 0; + if (m_awsize != lock_size) + w_write_lock_valid = 0; + end + // }}} + + assign write_lock_valid_per_id[gk]= w_write_lock_valid; + // }}} + end + + assign write_lock_valid = |write_lock_valid_per_id; + // }}} + end else begin : NO_LOCKING + // {{{ + + assign write_lock_valid = 1'b0; + // Verilator lint_off UNUSED + wire unused_lock; + assign unused_lock = &{ 1'b0, S_AXI_ARLOCK, S_AXI_AWLOCK }; + // Verilator lint_on UNUSED + // }}} + end endgenerate + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWCACHE, S_AXI_AWPROT, S_AXI_AWQOS, + S_AXI_ARCACHE, S_AXI_ARPROT, S_AXI_ARQOS }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // The following properties are only some of the properties used + // to verify this core + // + // Local (formal) register declarations + // {{{ + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge S_AXI_ACLK) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(!S_AXI_ARESETN); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI slave properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + faxi_slave #( + // {{{ + .C_AXI_ID_WIDTH(C_S_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_S_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_S_AXI_ADDR_WIDTH)) + // ... + // }}} + f_slave( + // {{{ + .i_clk(S_AXI_ACLK), + .i_axi_reset_n(S_AXI_ARESETN), + // + // Address write channel + // {{{ + .i_axi_awvalid(m_awvalid), + .i_axi_awready(m_awready), + .i_axi_awid( m_awid), + .i_axi_awaddr( m_awaddr), + .i_axi_awlen( m_awlen), + .i_axi_awsize( m_awsize), + .i_axi_awburst(m_awburst), + .i_axi_awlock( m_awlock), + .i_axi_awcache(4'h0), + .i_axi_awprot( 3'h0), + .i_axi_awqos( 4'h0), + // }}} + // Write Data Channel + // {{{ + // Write Data + .i_axi_wdata(S_AXI_WDATA), + .i_axi_wstrb(S_AXI_WSTRB), + .i_axi_wlast(S_AXI_WLAST), + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + // }}} + // Write response + // {{{ + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bid( S_AXI_BID), + .i_axi_bresp( S_AXI_BRESP), + // }}} + // Read address channel + // {{{ + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_arid( S_AXI_ARID), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arlen( S_AXI_ARLEN), + .i_axi_arsize( S_AXI_ARSIZE), + .i_axi_arburst(S_AXI_ARBURST), + .i_axi_arlock( S_AXI_ARLOCK), + .i_axi_arcache(S_AXI_ARCACHE), + .i_axi_arprot( S_AXI_ARPROT), + .i_axi_arqos( S_AXI_ARQOS), + // }}} + // Read data return channel + // {{{ + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rid(S_AXI_RID), + .i_axi_rdata(S_AXI_RDATA), + .i_axi_rresp(S_AXI_RRESP), + .i_axi_rlast(S_AXI_RLAST), + // }}} + // + // ... + // }}} + ); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + + // + // ... + // + + always @(*) + if (r_bvalid) + assert(S_AXI_BVALID); + + always @(*) + assert(axi_awready == (!S_AXI_WREADY&& !r_bvalid)); + + always @(*) + if (axi_awready) + assert(!S_AXI_WREADY); + + + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read induction properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + + // + // ... + // + + + always @(posedge S_AXI_ACLK) + if (f_past_valid && axi_rlen == 0) + assert(S_AXI_ARREADY); + + always @(posedge S_AXI_ACLK) + assert(axi_rlen <= 256); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Lowpower checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + if (OPT_LOWPOWER && S_AXI_ARESETN) + begin + if (!rskd_valid) + assert(!rskd_lock); + if (faxi_awr_nbursts == 0) + begin + assert(S_AXI_BRESP == 2'b00); + // assert(S_AXI_BID == 0); + end + + if (!S_AXI_RVALID) + begin + assert(S_AXI_RID == 0); + assert(S_AXI_RDATA == 0); + assert(S_AXI_RRESP == 2'b00); + end + + if (!o_we) + begin + assert(o_waddr == 0); + assert(o_wdata == 0); + assert(o_wstrb == 0); + end + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Contract checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // ... + // + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg f_wr_cvr_valid, f_rd_cvr_valid; + reg [4:0] f_dbl_rd_count, f_dbl_wr_count; + + initial f_wr_cvr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_wr_cvr_valid <= 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN > 4) + f_wr_cvr_valid <= 1; + + always @(*) + cover(!S_AXI_BVALID && axi_awready && !m_awvalid + && f_wr_cvr_valid /* && ... */)); + + initial f_rd_cvr_valid = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_rd_cvr_valid <= 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN > 4) + f_rd_cvr_valid <= 1; + + always @(*) + cover(S_AXI_ARREADY && f_rd_cvr_valid /* && ... */); + + // + // Generate cover statements associated with multiple successive bursts + // + // These will be useful for demonstrating the throughput of the core. + // + + initial f_dbl_wr_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dbl_wr_count = 0; + else if (S_AXI_AWVALID && S_AXI_AWREADY && S_AXI_AWLEN == 3) + begin + if (!(&f_dbl_wr_count)) + f_dbl_wr_count <= f_dbl_wr_count + 1; + end + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 1)); //! + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 3)); //! + + always @(*) + cover(S_AXI_ARESETN && (f_dbl_wr_count > 3) && !m_awvalid + &&(!S_AXI_AWVALID && !S_AXI_WVALID && !S_AXI_BVALID) + && (faxi_awr_nbursts == 0) + && (faxi_wr_pending == 0)); + + initial f_dbl_rd_count = 0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + f_dbl_rd_count = 0; + else if (S_AXI_ARVALID && S_AXI_ARREADY && S_AXI_ARLEN == 3) + begin + if (!(&f_dbl_rd_count)) + f_dbl_rd_count <= f_dbl_rd_count + 1; + end + + always @(*) + cover(!S_AXI_ARESETN && (f_dbl_rd_count > 3) + /* && ... */ + && !S_AXI_ARVALID && !S_AXI_RVALID); + + generate if (OPT_LOCK) + begin + always @(*) + cover(S_AXI_ARESETN && S_AXI_BVALID && S_AXI_BREADY + && S_AXI_BRESP == 2'b01); + end endgenerate + +`ifdef VERIFIC + cover property (@(posedge S_AXI_ACLK) + disable iff (!S_AXI_ARESETN) + // Accept a burst request for 4 beats + (S_AXI_ARVALID && S_AXI_ARREADY && (S_AXI_ARLEN == 3) + ##1 S_AXI_ARVALID [*3]) [*3] + ##1 1 [*0:12] + // The return to idle + ##1 (!S_AXI_ARVALID && !o_rd && !rskd_valid && !S_AXI_RVALID) + ); +`endif + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // "Careless" assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/easyaxil.v b/rtl/wb2axip/easyaxil.v new file mode 100644 index 0000000..0c88b11 --- /dev/null +++ b/rtl/wb2axip/easyaxil.v @@ -0,0 +1,524 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: easyaxil +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Demonstrates a simple AXI-Lite interface. +// +// This was written in light of my last demonstrator, for which others +// declared that it was much too complicated to understand. The goal of +// this demonstrator is to have logic that's easier to understand, use, +// and copy as needed. +// +// Since there are two basic approaches to AXI-lite signaling, both with +// and without skidbuffers, this example demonstrates both so that the +// differences can be compared and contrasted. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module easyaxil #( + // {{{ + // + // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite + // is fixed at a width of 32-bits by Xilinx def'n, and 2) since + // we only ever have 4 configuration words. + parameter C_AXI_ADDR_WIDTH = 4, + localparam C_AXI_DATA_WIDTH = 32, + parameter [0:0] OPT_SKIDBUFFER = 1'b0, + parameter [0:0] OPT_LOWPOWER = 0 + // }}} + ) ( + // {{{ + input wire S_AXI_ACLK, + input wire S_AXI_ARESETN, + // + input wire S_AXI_AWVALID, + output wire S_AXI_AWREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR, + input wire [2:0] S_AXI_AWPROT, + // + input wire S_AXI_WVALID, + output wire S_AXI_WREADY, + input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA, + input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB, + // + output wire S_AXI_BVALID, + input wire S_AXI_BREADY, + output wire [1:0] S_AXI_BRESP, + // + input wire S_AXI_ARVALID, + output wire S_AXI_ARREADY, + input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR, + input wire [2:0] S_AXI_ARPROT, + // + output wire S_AXI_RVALID, + input wire S_AXI_RREADY, + output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA, + output wire [1:0] S_AXI_RRESP + // }}} + ); + + //////////////////////////////////////////////////////////////////////// + // + // Register/wire signal declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3; + + wire i_reset = !S_AXI_ARESETN; + + wire axil_write_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] awskd_addr; + // + wire [C_AXI_DATA_WIDTH-1:0] wskd_data; + wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb; + reg axil_bvalid; + // + wire axil_read_ready; + wire [C_AXI_ADDR_WIDTH-ADDRLSB-1:0] arskd_addr; + reg [C_AXI_DATA_WIDTH-1:0] axil_read_data; + reg axil_read_valid; + + reg [31:0] r0, r1, r2, r3; + wire [31:0] wskd_r0, wskd_r1, wskd_r2, wskd_r3; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite signaling + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // + // Write signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_WRITE + // {{{ + wire awskd_valid, wskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) + axilawskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY), + .i_data(S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(awskd_valid), .i_ready(axil_write_ready), + .o_data(awskd_addr)); + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8)) + axilwskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY), + .i_data({ S_AXI_WDATA, S_AXI_WSTRB }), + .o_valid(wskd_valid), .i_ready(axil_write_ready), + .o_data({ wskd_data, wskd_strb })); + + assign axil_write_ready = awskd_valid && wskd_valid + && (!S_AXI_BVALID || S_AXI_BREADY); + // }}} + end else begin : SIMPLE_WRITES + // {{{ + reg axil_awready; + + initial axil_awready = 1'b0; + always @(posedge S_AXI_ACLK) + if (!S_AXI_ARESETN) + axil_awready <= 1'b0; + else + axil_awready <= !axil_awready + && (S_AXI_AWVALID && S_AXI_WVALID) + && (!S_AXI_BVALID || S_AXI_BREADY); + + assign S_AXI_AWREADY = axil_awready; + assign S_AXI_WREADY = axil_awready; + + assign awskd_addr = S_AXI_AWADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; + assign wskd_data = S_AXI_WDATA; + assign wskd_strb = S_AXI_WSTRB; + + assign axil_write_ready = axil_awready; + // }}} + end endgenerate + + initial axil_bvalid = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_bvalid <= 0; + else if (axil_write_ready) + axil_bvalid <= 1; + else if (S_AXI_BREADY) + axil_bvalid <= 0; + + assign S_AXI_BVALID = axil_bvalid; + assign S_AXI_BRESP = 2'b00; + // }}} + + // + // Read signaling + // + // {{{ + + generate if (OPT_SKIDBUFFER) + begin : SKIDBUFFER_READ + // {{{ + wire arskd_valid; + + skidbuffer #(.OPT_OUTREG(0), + .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(C_AXI_ADDR_WIDTH-ADDRLSB)) + axilarskid(// + .i_clk(S_AXI_ACLK), .i_reset(i_reset), + .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY), + .i_data(S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]), + .o_valid(arskd_valid), .i_ready(axil_read_ready), + .o_data(arskd_addr)); + + assign axil_read_ready = arskd_valid + && (!axil_read_valid || S_AXI_RREADY); + // }}} + end else begin : SIMPLE_READS + // {{{ + reg axil_arready; + + always @(*) + axil_arready = !S_AXI_RVALID; + + assign arskd_addr = S_AXI_ARADDR[C_AXI_ADDR_WIDTH-1:ADDRLSB]; + assign S_AXI_ARREADY = axil_arready; + assign axil_read_ready = (S_AXI_ARVALID && S_AXI_ARREADY); + // }}} + end endgenerate + + initial axil_read_valid = 1'b0; + always @(posedge S_AXI_ACLK) + if (i_reset) + axil_read_valid <= 1'b0; + else if (axil_read_ready) + axil_read_valid <= 1'b1; + else if (S_AXI_RREADY) + axil_read_valid <= 1'b0; + + assign S_AXI_RVALID = axil_read_valid; + assign S_AXI_RDATA = axil_read_data; + assign S_AXI_RRESP = 2'b00; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI-lite register logic + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // apply_wstrb(old_data, new_data, write_strobes) + assign wskd_r0 = apply_wstrb(r0, wskd_data, wskd_strb); + assign wskd_r1 = apply_wstrb(r1, wskd_data, wskd_strb); + assign wskd_r2 = apply_wstrb(r2, wskd_data, wskd_strb); + assign wskd_r3 = apply_wstrb(r3, wskd_data, wskd_strb); + + initial r0 = 0; + initial r1 = 0; + initial r2 = 0; + initial r3 = 0; + always @(posedge S_AXI_ACLK) + if (i_reset) + begin + r0 <= 0; + r1 <= 0; + r2 <= 0; + r3 <= 0; + end else if (axil_write_ready) + begin + case(awskd_addr) + 2'b00: r0 <= wskd_r0; + 2'b01: r1 <= wskd_r1; + 2'b10: r2 <= wskd_r2; + 2'b11: r3 <= wskd_r3; + endcase + end + + initial axil_read_data = 0; + always @(posedge S_AXI_ACLK) + if (OPT_LOWPOWER && !S_AXI_ARESETN) + axil_read_data <= 0; + else if (!S_AXI_RVALID || S_AXI_RREADY) + begin + case(arskd_addr) + 2'b00: axil_read_data <= r0; + 2'b01: axil_read_data <= r1; + 2'b10: axil_read_data <= r2; + 2'b11: axil_read_data <= r3; + endcase + + if (OPT_LOWPOWER && !axil_read_ready) + axil_read_data <= 0; + end + + function [C_AXI_DATA_WIDTH-1:0] apply_wstrb; + input [C_AXI_DATA_WIDTH-1:0] prior_data; + input [C_AXI_DATA_WIDTH-1:0] new_data; + input [C_AXI_DATA_WIDTH/8-1:0] wstrb; + + integer k; + for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1) + begin + apply_wstrb[k*8 +: 8] + = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8]; + end + endfunction + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT, + S_AXI_ARADDR[ADDRLSB-1:0], + S_AXI_AWADDR[ADDRLSB-1:0] }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // The AXI-lite control interface + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + localparam F_AXIL_LGDEPTH = 4; + wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding, + faxil_wr_outstanding, + faxil_awr_outstanding; + + faxil_slave #( + // {{{ + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(F_AXIL_LGDEPTH), + .F_AXI_MAXWAIT(3), + .F_AXI_MAXDELAY(3), + .F_AXI_MAXRSTALL(5), + .F_OPT_COVER_BURST(4) + // }}} + ) faxil( + // {{{ + .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN), + // + .i_axi_awvalid(S_AXI_AWVALID), + .i_axi_awready(S_AXI_AWREADY), + .i_axi_awaddr( S_AXI_AWADDR), + .i_axi_awprot( S_AXI_AWPROT), + // + .i_axi_wvalid(S_AXI_WVALID), + .i_axi_wready(S_AXI_WREADY), + .i_axi_wdata( S_AXI_WDATA), + .i_axi_wstrb( S_AXI_WSTRB), + // + .i_axi_bvalid(S_AXI_BVALID), + .i_axi_bready(S_AXI_BREADY), + .i_axi_bresp( S_AXI_BRESP), + // + .i_axi_arvalid(S_AXI_ARVALID), + .i_axi_arready(S_AXI_ARREADY), + .i_axi_araddr( S_AXI_ARADDR), + .i_axi_arprot( S_AXI_ARPROT), + // + .i_axi_rvalid(S_AXI_RVALID), + .i_axi_rready(S_AXI_RREADY), + .i_axi_rdata( S_AXI_RDATA), + .i_axi_rresp( S_AXI_RRESP), + // + .f_axi_rd_outstanding(faxil_rd_outstanding), + .f_axi_wr_outstanding(faxil_wr_outstanding), + .f_axi_awr_outstanding(faxil_awr_outstanding) + // }}} + ); + + always @(*) + if (OPT_SKIDBUFFER) + begin + assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0) + +(S_AXI_AWREADY ? 0:1)); + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0) + +(S_AXI_WREADY ? 0:1)); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0) + +(S_AXI_ARREADY ? 0:1)); + end else begin + assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0)); + assert(faxil_awr_outstanding == faxil_wr_outstanding); + + assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0)); + end + + // + // Check that our low-power only logic works by verifying that anytime + // S_AXI_RVALID is inactive, then the outgoing data is also zero. + // + always @(*) + if (OPT_LOWPOWER && !S_AXI_RVALID) + assert(S_AXI_RDATA == 0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Register return checking + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`define CHECK_REGISTERS +`ifdef CHECK_REGISTERS + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR(0) + // }}} + ) fr0 ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(r0) + // }}} + ); + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR(4) + // }}} + ) fr1 ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(r1) + // }}} + ); + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR(8) + // }}} + ) fr2 ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(r2) + // }}} + ); + + faxil_register #( + // {{{ + .AW(C_AXI_ADDR_WIDTH), + .DW(C_AXI_DATA_WIDTH), + .ADDR(12) + // }}} + ) fr3 ( + // {{{ + .S_AXI_ACLK(S_AXI_ACLK), + .S_AXI_ARESETN(S_AXI_ARESETN), + .S_AXIL_AWW(axil_write_ready), + .S_AXIL_AWADDR({ awskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_WDATA(wskd_data), + .S_AXIL_WSTRB(wskd_strb), + .S_AXIL_BVALID(S_AXI_BVALID), + .S_AXIL_AR(axil_read_ready), + .S_AXIL_ARADDR({ arskd_addr, {(ADDRLSB){1'b0}} }), + .S_AXIL_RVALID(S_AXI_RVALID), + .S_AXIL_RDATA(S_AXI_RDATA), + .i_register(r3) + // }}} + ); +`endif + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // + //////////////////////////////////////////////////////////////////////// + // + // {{{ + + // While there are already cover properties in the formal property + // set above, you'll probably still want to cover something + // application specific here + + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/migsdram.v b/rtl/wb2axip/migsdram.v new file mode 100644 index 0000000..c1671c8 --- /dev/null +++ b/rtl/wb2axip/migsdram.v @@ -0,0 +1,313 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: migsdram.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: This file isn't really a part of the synthesis implementation +// of the wb2axip project itself, but rather it is an example +// of how the wb2axip project can be used to connect a MIG generated +// IP component. +// +// This implementation depends upon the existence of a MIG generated +// core, named "mig_axis", and illustrates how such a core might be +// connected to the wbm2axip bridge. Specific options of the mig_axis +// setup include 6 identifier bits, and a full-sized bus width of 128 +// bits. These two settings are both appropriate for driving a DDR3 +// memory (whose minimum transfer size is 128 bits), but may need to be +// adjusted to support other memories. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2015-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module migsdram(i_clk, i_clk_200mhz, o_sys_clk, i_rst, o_sys_reset, + // Wishbone components + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel, + o_wb_ack, o_wb_stall, o_wb_data, o_wb_err, + // SDRAM connections + o_ddr_ck_p, o_ddr_ck_n, + o_ddr_reset_n, o_ddr_cke, + o_ddr_cs_n, o_ddr_ras_n, o_ddr_cas_n, o_ddr_we_n, + o_ddr_ba, o_ddr_addr, + o_ddr_odt, o_ddr_dm, + io_ddr_dqs_p, io_ddr_dqs_n, + io_ddr_data + ); + parameter DDRWIDTH = 16, WBDATAWIDTH=32; + parameter AXIDWIDTH = 6; + // The SDRAM address bits (RAMABITS) are a touch more difficult to work + // out. Here we leave them as a fixed parameter, but there are + // consequences to this. Specifically, the wishbone data width, the + // wishbone address width, and this number have interactions not + // well captured here. + parameter RAMABITS = 28; + // All DDR3 memories have 8 timeslots. This, if the DDR3 memory + // has 16 bits to it (as above), the entire transaction must take + // AXIWIDTH bits ... + localparam AXIWIDTH= DDRWIDTH *8; + localparam DW=WBDATAWIDTH; + localparam AW=(WBDATAWIDTH==32)? RAMABITS-2 + :((WBDATAWIDTH==64) ? RAMABITS-3 + :((WBDATAWIDTH==128) ? RAMABITS-4 + : RAMABITS-5)); // (WBDATAWIDTH==256) + localparam SELW= (WBDATAWIDTH/8); + // + input wire i_clk, i_clk_200mhz, i_rst; + output wire o_sys_clk; + output reg o_sys_reset; + // + input wire i_wb_cyc, i_wb_stb, i_wb_we; + input wire [(AW-1):0] i_wb_addr; + input wire [(DW-1):0] i_wb_data; + input wire [(SELW-1):0] i_wb_sel; + output wire o_wb_ack, o_wb_stall; + output wire [(DW-1):0] o_wb_data; + output wire o_wb_err; + // + output wire [0:0] o_ddr_ck_p, o_ddr_ck_n; + output wire [0:0] o_ddr_cke; + output wire o_ddr_reset_n, + o_ddr_ras_n, o_ddr_cas_n, o_ddr_we_n; + output wire [0:0] o_ddr_cs_n; + output wire [2:0] o_ddr_ba; + output wire [13:0] o_ddr_addr; + output wire [0:0] o_ddr_odt; + output wire [(DDRWIDTH/8-1):0] o_ddr_dm; + inout wire [1:0] io_ddr_dqs_p, io_ddr_dqs_n; + inout wire [(DDRWIDTH-1):0] io_ddr_data; + + +`define SDRAM_ACCESS +`ifdef SDRAM_ACCESS + + wire aresetn; + assign aresetn = 1'b1; // Never reset + + // Write address channel + wire [(AXIDWIDTH-1):0] s_axi_awid; + wire [(RAMABITS-1):0] s_axi_awaddr; + wire [7:0] s_axi_awlen; + wire [2:0] s_axi_awsize; + wire [1:0] s_axi_awburst; + wire [0:0] s_axi_awlock; + wire [3:0] s_axi_awcache; + wire [2:0] s_axi_awprot; + wire [3:0] s_axi_awqos; + wire s_axi_awvalid; + wire s_axi_awready; + // Writei data channel + wire [(AXIWIDTH-1):0] s_axi_wdata; + wire [(AXIWIDTH/8-1):0] s_axi_wstrb; + wire s_axi_wlast, s_axi_wvalid, s_axi_wready; + // Write response channel + wire s_axi_bready; + wire [(AXIDWIDTH-1):0] s_axi_bid; + wire [1:0] s_axi_bresp; + wire s_axi_bvalid; + + // Read address channel + wire [(AXIDWIDTH-1):0] s_axi_arid; + wire [(RAMABITS-1):0] s_axi_araddr; + wire [7:0] s_axi_arlen; + wire [2:0] s_axi_arsize; + wire [1:0] s_axi_arburst; + wire [0:0] s_axi_arlock; + wire [3:0] s_axi_arcache; + wire [2:0] s_axi_arprot; + wire [3:0] s_axi_arqos; + wire s_axi_arvalid; + wire s_axi_arready; + // Read response/data channel + wire [(AXIDWIDTH-1):0] s_axi_rid; + wire [(AXIWIDTH-1):0] s_axi_rdata; + wire [1:0] s_axi_rresp; + wire s_axi_rlast; + wire s_axi_rvalid; + wire s_axi_rready; + + // Other wires ... + wire init_calib_complete, mmcm_locked; + wire app_sr_active, app_ref_ack, app_zq_ack; + wire app_sr_req, app_ref_req, app_zq_req; + wire w_sys_reset; + wire [11:0] w_device_temp; + + + mig_axis mig_sdram( + .ddr3_ck_p(o_ddr_ck_p), .ddr3_ck_n(o_ddr_ck_n), + .ddr3_reset_n(o_ddr_reset_n), .ddr3_cke(o_ddr_cke), + .ddr3_cs_n(o_ddr_cs_n), .ddr3_ras_n(o_ddr_ras_n), + .ddr3_we_n(o_ddr_we_n), .ddr3_cas_n(o_ddr_cas_n), + .ddr3_ba(o_ddr_ba), .ddr3_addr(o_ddr_addr), + .ddr3_odt(o_ddr_odt), + .ddr3_dqs_p(io_ddr_dqs_p), .ddr3_dqs_n(io_ddr_dqs_n), + .ddr3_dq(io_ddr_data), .ddr3_dm(o_ddr_dm), + // + .sys_clk_i(i_clk), + .clk_ref_i(i_clk_200mhz), + .ui_clk(o_sys_clk), + .ui_clk_sync_rst(w_sys_reset), + .mmcm_locked(mmcm_locked), + .aresetn(aresetn), + .app_sr_req(1'b0), + .app_ref_req(1'b0), + .app_zq_req(1'b0), + .app_sr_active(app_sr_active), + .app_ref_ack(app_ref_ack), + .app_zq_ack(app_zq_ack), + // + .s_axi_awid(s_axi_awid), .s_axi_awaddr(s_axi_awaddr), + .s_axi_awlen(s_axi_awlen), .s_axi_awsize(s_axi_awsize), + .s_axi_awburst(s_axi_awburst), .s_axi_awlock(s_axi_awlock), + .s_axi_awcache(s_axi_awcache), .s_axi_awprot(s_axi_awprot), + .s_axi_awqos(s_axi_awqos), .s_axi_awvalid(s_axi_awvalid), + .s_axi_awready(s_axi_awready), + // + .s_axi_wready( s_axi_wready), + .s_axi_wdata( s_axi_wdata), + .s_axi_wstrb( s_axi_wstrb), + .s_axi_wlast( s_axi_wlast), + .s_axi_wvalid( s_axi_wvalid), + // + .s_axi_bready(s_axi_bready), .s_axi_bid(s_axi_bid), + .s_axi_bresp(s_axi_bresp), .s_axi_bvalid(s_axi_bvalid), + // + .s_axi_arid(s_axi_arid), .s_axi_araddr(s_axi_araddr), + .s_axi_arlen(s_axi_arlen), .s_axi_arsize(s_axi_arsize), + .s_axi_arburst(s_axi_arburst), .s_axi_arlock(s_axi_arlock), + .s_axi_arcache(s_axi_arcache), .s_axi_arprot(s_axi_arprot), + .s_axi_arqos(s_axi_arqos), .s_axi_arvalid(s_axi_arvalid), + .s_axi_arready(s_axi_arready), + // + .s_axi_rready(s_axi_rready), .s_axi_rid(s_axi_rid), + .s_axi_rdata(s_axi_rdata), .s_axi_rresp(s_axi_rresp), + .s_axi_rlast(s_axi_rlast), .s_axi_rvalid(s_axi_rvalid), + .init_calib_complete(init_calib_complete), + .sys_rst(i_rst), + .device_temp(w_device_temp) + ); + + wbm2axisp #( + .C_AXI_ID_WIDTH(AXIDWIDTH), + .C_AXI_DATA_WIDTH(AXIWIDTH), + .C_AXI_ADDR_WIDTH(RAMABITS), + .AW(AW), .DW(DW) + ) + bus_translator( + .i_clk(o_sys_clk), + // .i_reset(i_rst), // internally unused + // Write address channel signals + .o_axi_awvalid( s_axi_awvalid), + .i_axi_awready( s_axi_awready), + .o_axi_awid( s_axi_awid), + .o_axi_awaddr( s_axi_awaddr), + .o_axi_awlen( s_axi_awlen), + .o_axi_awsize( s_axi_awsize), + .o_axi_awburst( s_axi_awburst), + .o_axi_awlock( s_axi_awlock), + .o_axi_awcache( s_axi_awcache), + .o_axi_awprot( s_axi_awprot), // s_axi_awqos + .o_axi_awqos( s_axi_awqos), // s_axi_awqos + // + .o_axi_wvalid( s_axi_wvalid), + .i_axi_wready( s_axi_wready), + .o_axi_wdata( s_axi_wdata), + .o_axi_wstrb( s_axi_wstrb), + .o_axi_wlast( s_axi_wlast), + // + .i_axi_bvalid( s_axi_bvalid), + .o_axi_bready( s_axi_bready), + .i_axi_bid( s_axi_bid), + .i_axi_bresp( s_axi_bresp), + // + .o_axi_arvalid( s_axi_arvalid), + .i_axi_arready( s_axi_arready), + .o_axi_arid( s_axi_arid), + .o_axi_araddr( s_axi_araddr), + .o_axi_arlen( s_axi_arlen), + .o_axi_arsize( s_axi_arsize), + .o_axi_arburst( s_axi_arburst), + .o_axi_arlock( s_axi_arlock), + .o_axi_arcache( s_axi_arcache), + .o_axi_arprot( s_axi_arprot), + .o_axi_arqos( s_axi_arqos), + // + .i_axi_rvalid( s_axi_rvalid), + .o_axi_rready( s_axi_rready), + .i_axi_rid( s_axi_rid), + .i_axi_rdata( s_axi_rdata), + .i_axi_rresp( s_axi_rresp), + .i_axi_rlast( s_axi_rlast), + // + .i_wb_cyc( i_wb_cyc), + .i_wb_stb( i_wb_stb), + .i_wb_we( i_wb_we), + .i_wb_addr( i_wb_addr), + .i_wb_data( i_wb_data), + .i_wb_sel( i_wb_sel), + // + .o_wb_stall( o_wb_stall), + .o_wb_ack( o_wb_ack), + .o_wb_data( o_wb_data), + .o_wb_err( o_wb_err) + ); + + // Convert from active low to active high, *and* hold the system in + // reset until the memory comes up. + initial o_sys_reset = 1'b1; + always @(posedge o_sys_clk) + o_sys_reset <= (!w_sys_reset) + ||(!init_calib_complete) + ||(!mmcm_locked); +`else + BUFG sysclk(i_clk, o_sys_clk); + initial o_sys_reset <= 1'b1; + always @(posedge i_clk) + o_sys_reset <= 1'b1; + + OBUFDS ckobuf(.I(i_clk), .O(o_ddr_ck_p), .OB(o_ddr_ck_n)); + + assign o_ddr_reset_n = 1'b0; + assign o_ddr_cke[0] = 1'b0; + assign o_ddr_cs_n[0] = 1'b1; + assign o_ddr_cas_n = 1'b1; + assign o_ddr_ras_n = 1'b1; + assign o_ddr_we_n = 1'b1; + assign o_ddr_ba = 3'h0; + assign o_ddr_addr = 14'h00; + assign o_ddr_dm = 2'b00; + assign io_ddr_data = 16'h0; + + OBUFDS dqsbufa(.I(i_clk), .O(io_ddr_dqs_p[1]), .OB(io_ddr_dqs_n[1])); + OBUFDS dqsbufb(.I(i_clk), .O(io_ddr_dqs_p[0]), .OB(io_ddr_dqs_n[0])); + +`endif + +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/mod.mk b/rtl/wb2axip/mod.mk new file mode 100644 index 0000000..224f290 --- /dev/null +++ b/rtl/wb2axip/mod.mk @@ -0,0 +1,10 @@ +cores := axixbar + +define core + $(this)/rtl_dirs := . +endef + +define core/axixbar + $(this)/deps := wb2axip + $(this)/rtl_top := axixbar +endef diff --git a/rtl/wb2axip/sfifo.v b/rtl/wb2axip/sfifo.v new file mode 100644 index 0000000..c60dffe --- /dev/null +++ b/rtl/wb2axip/sfifo.v @@ -0,0 +1,482 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: sfifo.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A synchronous data FIFO. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// +// Written and distributed by Gisselquist Technology, LLC +// }}} +// This design is hereby granted to the public domain. +// {{{ +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module sfifo #( + // {{{ + parameter BW=8, // Byte/data width + parameter LGFLEN=4, + parameter [0:0] OPT_ASYNC_READ = 1'b1, + parameter [0:0] OPT_WRITE_ON_FULL = 1'b0, + parameter [0:0] OPT_READ_ON_EMPTY = 1'b0 + // }}} + ) ( + // {{{ + input wire i_clk, + input wire i_reset, + // + // Write interface + input wire i_wr, + input wire [(BW-1):0] i_data, + output wire o_full, + output reg [LGFLEN:0] o_fill, + // + // Read interface + input wire i_rd, + output reg [(BW-1):0] o_data, + output wire o_empty // True if FIFO is empty +`ifdef FORMAL +`ifdef F_PEEK + , output wire [LGFLEN:0] f_first_addr, + output wire [LGFLEN:0] f_second_addr, + output reg [BW-1:0] f_first_data, f_second_data, + + output reg f_first_in_fifo, + f_second_in_fifo, + output reg [LGFLEN:0] f_distance_to_first, + f_distance_to_second +`endif +`endif + // }}} + ); + + // Register/net declarations + // {{{ + localparam FLEN=(1<<LGFLEN); + reg r_full, r_empty; + reg [(BW-1):0] mem[0:(FLEN-1)]; + reg [LGFLEN:0] wr_addr, rd_addr; + + wire w_wr = (i_wr && !o_full); + wire w_rd = (i_rd && !o_empty); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write half + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_fill + // {{{ + initial o_fill = 0; + always @(posedge i_clk) + if (i_reset) + o_fill <= 0; + else case({ w_wr, w_rd }) + 2'b01: o_fill <= o_fill - 1; + 2'b10: o_fill <= o_fill + 1; + default: o_fill <= wr_addr - rd_addr; + endcase + // }}} + + // r_full, o_full + // {{{ + initial r_full = 0; + always @(posedge i_clk) + if (i_reset) + r_full <= 0; + else case({ w_wr, w_rd}) + 2'b01: r_full <= 1'b0; + 2'b10: r_full <= (o_fill == { 1'b0, {(LGFLEN){1'b1}} }); + default: r_full <= (o_fill == { 1'b1, {(LGFLEN){1'b0}} }); + endcase + + assign o_full = (i_rd && OPT_WRITE_ON_FULL) ? 1'b0 : r_full; + // }}} + + // wr_addr, the write address pointer + // {{{ + initial wr_addr = 0; + always @(posedge i_clk) + if (i_reset) + wr_addr <= 0; + else if (w_wr) + wr_addr <= wr_addr + 1'b1; + // }}} + + // Write to memory + // {{{ + always @(posedge i_clk) + if (w_wr) + mem[wr_addr[(LGFLEN-1):0]] <= i_data; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read half + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // rd_addr, the read address pointer + // {{{ + initial rd_addr = 0; + always @(posedge i_clk) + if (i_reset) + rd_addr <= 0; + else if (w_rd) + rd_addr <= rd_addr + 1; + // }}} + + // r_empty, o_empty + // {{{ + initial r_empty = 1'b1; + always @(posedge i_clk) + if (i_reset) + r_empty <= 1'b1; + else case ({ w_wr, w_rd }) + 2'b01: r_empty <= (o_fill <= 1); + 2'b10: r_empty <= 1'b0; + default: begin end + endcase + + assign o_empty = (OPT_READ_ON_EMPTY && i_wr) ? 1'b0 : r_empty; + // }}} + + // Read from the FIFO + // {{{ + generate if (OPT_ASYNC_READ && OPT_READ_ON_EMPTY) + begin : ASYNCHRONOUS_READ_ON_EMPTY + // o_data + // {{{ + always @(*) + begin + o_data = mem[rd_addr[LGFLEN-1:0]]; + if (r_empty) + o_data = i_data; + end + // }}} + end else if (OPT_ASYNC_READ) + begin : ASYNCHRONOUS_READ + // o_data + // {{{ + always @(*) + o_data = mem[rd_addr[LGFLEN-1:0]]; + // }}} + end else begin : REGISTERED_READ + // {{{ + reg bypass_valid; + reg [BW-1:0] bypass_data, rd_data; + reg [LGFLEN-1:0] rd_next; + + always @(*) + rd_next = rd_addr[LGFLEN-1:0] + 1; + + // Memory read, bypassing it if we must + // {{{ + initial bypass_valid = 0; + always @(posedge i_clk) + if (i_reset) + bypass_valid <= 0; + else if (r_empty || i_rd) + begin + if (!i_wr) + bypass_valid <= 1'b0; + else if (r_empty || (i_rd && (o_fill == 1))) + bypass_valid <= 1'b1; + else + bypass_valid <= 1'b0; + end + + always @(posedge i_clk) + if (r_empty || i_rd) + bypass_data <= i_data; + + initial mem[0] = 0; + initial rd_data = 0; + always @(posedge i_clk) + if (w_rd) + rd_data <= mem[rd_next]; + + always @(*) + if (OPT_READ_ON_EMPTY && r_empty) + o_data = i_data; + else if (bypass_valid) + o_data = bypass_data; + else + o_data = rd_data; + // }}} + // }}} + end endgenerate + // }}} + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// FORMAL METHODS +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +`ifdef FORMAL + +// +// Assumptions about our input(s) +// +// +`ifdef SFIFO +`define ASSUME assume +`else +`define ASSUME assert +`endif + + reg f_past_valid; + wire [LGFLEN:0] f_fill, f_next; + wire f_empty; + + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + //////////////////////////////////////////////////////////////////////// + // + // Assertions about our flags and counters + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + assign f_fill = wr_addr - rd_addr; + assign f_empty = (wr_addr == rd_addr); + assign f_next = rd_addr + 1'b1; + + always @(*) + begin + assert(f_fill <= { 1'b1, {(LGFLEN){1'b0}} }); + assert(o_fill == f_fill); + + assert(r_full == (f_fill == {1'b1, {(LGFLEN){1'b0}} })); + assert(r_empty == (f_fill == 0)); + + if (!OPT_WRITE_ON_FULL) + begin + assert(o_full == r_full); + end else begin + assert(o_full == (r_full && !i_rd)); + end + + if (!OPT_READ_ON_EMPTY) + begin + assert(o_empty == r_empty); + end else begin + assert(o_empty == (r_empty && !i_wr)); + end + end + + always @(posedge i_clk) + if (!OPT_ASYNC_READ && f_past_valid) + begin + if (f_fill == 0) + begin + assert(r_empty); + assert(o_empty || (OPT_READ_ON_EMPTY && i_wr)); + end else if ($past(f_fill)>1) + begin + assert(!r_empty); + end else if ($past(!i_rd && f_fill > 0)) + assert(!r_empty); + end + + always @(*) + if (!r_empty) + begin + // This also applies for the registered read case + assert(mem[rd_addr[LGFLEN-1:0]] == o_data); + end else if (OPT_READ_ON_EMPTY) + assert(o_data == i_data); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Formal contract: (Twin write test) + // {{{ + // If you write two values in succession, you should be able to read + // those same two values in succession some time later. + // + //////////////////////////////////////////////////////////////////////// + // + // + + // Verilator lint_off UNDRIVEN + (* anyconst *) reg [LGFLEN:0] fw_first_addr; + // Verilator lint_on UNDRIVEN +`ifndef F_PEEK + wire [LGFLEN:0] f_first_addr; + wire [LGFLEN:0] f_second_addr; + reg [BW-1:0] f_first_data, f_second_data; + + reg f_first_in_fifo, f_second_in_fifo; + reg [LGFLEN:0] f_distance_to_first, f_distance_to_second; +`endif + reg f_first_addr_in_fifo, f_second_addr_in_fifo; + + assign f_first_addr = fw_first_addr; + assign f_second_addr = f_first_addr + 1; + + always @(*) + begin + f_distance_to_first = (f_first_addr - rd_addr); + f_first_addr_in_fifo = 0; + if ((f_fill != 0) && (f_distance_to_first < f_fill)) + f_first_addr_in_fifo = 1; + end + + always @(*) + begin + f_distance_to_second = (f_second_addr - rd_addr); + f_second_addr_in_fifo = 0; + if ((f_fill != 0) && (f_distance_to_second < f_fill)) + f_second_addr_in_fifo = 1; + end + + always @(posedge i_clk) + if (w_wr && wr_addr == f_first_addr) + f_first_data <= i_data; + + always @(posedge i_clk) + if (w_wr && wr_addr == f_second_addr) + f_second_data <= i_data; + + always @(*) + if (f_first_addr_in_fifo) + assert(mem[f_first_addr[LGFLEN-1:0]] == f_first_data); + always @(*) + f_first_in_fifo = (f_first_addr_in_fifo && (mem[f_first_addr[LGFLEN-1:0]] == f_first_data)); + + always @(*) + if (f_second_addr_in_fifo) + assert(mem[f_second_addr[LGFLEN-1:0]] == f_second_data); + + always @(*) + f_second_in_fifo = (f_second_addr_in_fifo && (mem[f_second_addr[LGFLEN-1:0]] == f_second_data)); + + always @(*) + if (f_first_in_fifo && (o_fill == 1 || f_distance_to_first == 0)) + assert(o_data == f_first_data); + + always @(*) + if (f_second_in_fifo && (o_fill == 1 || f_distance_to_second == 0)) + assert(o_data == f_second_data); + + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset)) + begin + case({$past(f_first_in_fifo), $past(f_second_in_fifo)}) + 2'b00: begin + if ($past(w_wr && (!w_rd || !r_empty)) + &&($past(wr_addr == f_first_addr))) + begin + assert(f_first_in_fifo); + end else begin + assert(!f_first_in_fifo); + end + // + // The second could be in the FIFO, since + // one might write other data than f_first_data + // + // assert(!f_second_in_fifo); + end + 2'b01: begin + assert(!f_first_in_fifo); + if ($past(w_rd && (rd_addr==f_second_addr))) + begin + assert((o_empty&&!OPT_ASYNC_READ)||!f_second_in_fifo); + end else begin + assert(f_second_in_fifo); + end + end + 2'b10: begin + if ($past(w_wr) + &&($past(wr_addr == f_second_addr))) + begin + assert(f_second_in_fifo); + end else begin + assert(!f_second_in_fifo); + end + if ($past(!w_rd ||(rd_addr != f_first_addr))) + assert(f_first_in_fifo); + end + 2'b11: begin + assert(f_second_in_fifo); + if ($past(!w_rd ||(rd_addr != f_first_addr))) + begin + assert(f_first_in_fifo); + if (rd_addr == f_first_addr) + assert(o_data == f_first_data); + end else begin + assert(!f_first_in_fifo); + assert(o_data == f_second_data); + end + end + endcase + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`ifdef SFIFO + reg f_was_full; + initial f_was_full = 0; + always @(posedge i_clk) + if (o_full) + f_was_full <= 1; + + always @(posedge i_clk) + cover($fell(f_empty)); + + always @(posedge i_clk) + cover($fell(o_empty)); + + always @(posedge i_clk) + cover(f_was_full && f_empty); + + always @(posedge i_clk) + cover($past(o_full,2)&&(!$past(o_full))&&(o_full)); + + always @(posedge i_clk) + if (f_past_valid) + cover($past(o_empty,2)&&(!$past(o_empty))&& o_empty); +`endif + // }}} + + // Make Verilator happy + // Verilator lint_off UNUSED + wire unused_formal; + assign unused_formal = &{ 1'b0, f_next[LGFLEN], f_empty }; + // Verilator lint_on UNUSED +`endif // FORMAL +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/sfifothresh.v b/rtl/wb2axip/sfifothresh.v new file mode 100644 index 0000000..5d4a156 --- /dev/null +++ b/rtl/wb2axip/sfifothresh.v @@ -0,0 +1,100 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: sfifothresh.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A synchronous data FIFO, generated from sfifo.v. This +// particular version extends the FIFO interface with a threshold +// calculator, to create an interrupt/signal when the FIFO has greater +// than the threshold elements within it. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module sfifothresh(i_clk, i_reset, + i_wr, i_data, o_full, o_fill, + i_rd, o_data, o_empty, + i_threshold, o_int); + parameter BW=8; // Byte/data width + parameter LGFLEN=4; + parameter [0:0] OPT_ASYNC_READ = 1'b1; + localparam FLEN=(1<<LGFLEN); + + // + // + input wire i_clk; + input wire i_reset; + // + // Write interface + input wire i_wr; + input wire [(BW-1):0] i_data; + output wire o_full; + output wire [LGFLEN:0] o_fill; + // + // Read interface + input wire i_rd; + output wire [(BW-1):0] o_data; + output wire o_empty; // True if FIFO is empty + // + input wire [LGFLEN:0] i_threshold; + output reg o_int; + + wire w_wr = (i_wr && !o_full); + wire w_rd = (i_rd && !o_empty); + + sfifo #( + .BW(BW), .LGFLEN(LGFLEN), .OPT_ASYNC_READ(OPT_ASYNC_READ) + ) sfifoi( + i_clk, i_reset, i_wr, i_data, o_full, o_fill, i_rd, + o_data, o_empty + ); + + initial o_int = 0; + always @(posedge i_clk) + if (i_reset) + o_int <= 0; + else case({ w_wr, w_rd }) + 2'b01: o_int <= (o_fill-1 >= i_threshold); + 2'b10: o_int <= (o_fill+1 >= i_threshold); + default: o_int <= o_fill >= i_threshold; + endcase + +`ifdef FORMAL + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + always @(posedge i_clk) + if (!f_past_valid || $past(i_reset)) + assert(!o_int); + else + assert(o_int == (o_fill >= $past(i_threshold))); +`endif +endmodule diff --git a/rtl/wb2axip/skidbuffer.v b/rtl/wb2axip/skidbuffer.v new file mode 100644 index 0000000..3d19cbb --- /dev/null +++ b/rtl/wb2axip/skidbuffer.v @@ -0,0 +1,495 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: skidbuffer.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A basic SKID buffer. +// {{{ +// Skid buffers are required for high throughput AXI code, since the AXI +// specification requires that all outputs be registered. This means +// that, if there are any stall conditions calculated, it will take a clock +// cycle before the stall can be propagated up stream. This means that +// the data will need to be buffered for a cycle until the stall signal +// can make it to the output. +// +// Handling that buffer is the purpose of this core. +// +// On one end of this core, you have the i_valid and i_data inputs to +// connect to your bus interface. There's also a registered o_ready +// signal to signal stalls for the bus interface. +// +// The other end of the core has the same basic interface, but it isn't +// registered. This allows you to interact with the bus interfaces +// as though they were combinatorial logic, by interacting with this half +// of the core. +// +// If at any time the incoming !stall signal, i_ready, signals a stall, +// the incoming data is placed into a buffer. Internally, that buffer +// is held in r_data with the r_valid flag used to indicate that valid +// data is within it. +// }}} +// Parameters: +// {{{ +// DW or data width +// In order to make this core generic, the width of the data in the +// skid buffer is parameterized +// +// OPT_LOWPOWER +// Forces both o_data and r_data to zero if the respective *VALID +// signal is also low. While this costs extra logic, it can also +// be used to guarantee that any unused values aren't toggling and +// therefore unnecessarily using power. +// +// This excess toggling can be particularly problematic if the +// bus signals have a high fanout rate, or a long signal path +// across an FPGA. +// +// OPT_OUTREG +// Causes the outputs to be registered +// +// OPT_PASSTHROUGH +// Turns the skid buffer into a passthrough. Used for formal +// verification only. +// }}} +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module skidbuffer #( + // {{{ + parameter [0:0] OPT_LOWPOWER = 0, + parameter [0:0] OPT_OUTREG = 1, + // + parameter [0:0] OPT_PASSTHROUGH = 0, + parameter DW = 8, + parameter [0:0] OPT_INITIAL = 1'b1 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + input wire i_valid, + output wire o_ready, + input wire [DW-1:0] i_data, + output wire o_valid, + input wire i_ready, + output reg [DW-1:0] o_data + // }}} + ); + + wire [DW-1:0] w_data; + + generate if (OPT_PASSTHROUGH) + begin : PASSTHROUGH + // {{{ + assign { o_valid, o_ready } = { i_valid, i_ready }; + + always @(*) + if (!i_valid && OPT_LOWPOWER) + o_data = 0; + else + o_data = i_data; + + assign w_data = 0; + + // Keep Verilator happy + // Verilator lint_off UNUSED + // {{{ + wire unused_passthrough; + assign unused_passthrough = &{ 1'b0, i_clk, i_reset }; + // }}} + // Verilator lint_on UNUSED + // }}} + end else begin : LOGIC + // We'll start with skid buffer itself + // {{{ + reg r_valid; + reg [DW-1:0] r_data; + + // r_valid + // {{{ + initial if (OPT_INITIAL) r_valid = 0; + always @(posedge i_clk) + if (i_reset) + r_valid <= 0; + else if ((i_valid && o_ready) && (o_valid && !i_ready)) + // We have incoming data, but the output is stalled + r_valid <= 1; + else if (i_ready) + r_valid <= 0; + // }}} + + // r_data + // {{{ + initial if (OPT_INITIAL) r_data = 0; + always @(posedge i_clk) + if (OPT_LOWPOWER && i_reset) + r_data <= 0; + else if (OPT_LOWPOWER && (!o_valid || i_ready)) + r_data <= 0; + else if ((!OPT_LOWPOWER || !OPT_OUTREG || i_valid) && o_ready) + r_data <= i_data; + + assign w_data = r_data; + // }}} + + // o_ready + // {{{ + assign o_ready = !r_valid; + // }}} + + // + // And then move on to the output port + // + if (!OPT_OUTREG) + begin : NET_OUTPUT + // Outputs are combinatorially determined from inputs + // {{{ + // o_valid + // {{{ + assign o_valid = !i_reset && (i_valid || r_valid); + // }}} + + // o_data + // {{{ + always @(*) + if (r_valid) + o_data = r_data; + else if (!OPT_LOWPOWER || i_valid) + o_data = i_data; + else + o_data = 0; + // }}} + // }}} + end else begin : REG_OUTPUT + // Register our outputs + // {{{ + // o_valid + // {{{ + reg ro_valid; + + initial if (OPT_INITIAL) ro_valid = 0; + always @(posedge i_clk) + if (i_reset) + ro_valid <= 0; + else if (!o_valid || i_ready) + ro_valid <= (i_valid || r_valid); + + assign o_valid = ro_valid; + // }}} + + // o_data + // {{{ + initial if (OPT_INITIAL) o_data = 0; + always @(posedge i_clk) + if (OPT_LOWPOWER && i_reset) + o_data <= 0; + else if (!o_valid || i_ready) + begin + + if (r_valid) + o_data <= r_data; + else if (!OPT_LOWPOWER || i_valid) + o_data <= i_data; + else + o_data <= 0; + end + // }}} + + // }}} + end + // }}} + end endgenerate + + // Keep Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, w_data }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL +`ifdef SKIDBUFFER +`define ASSUME assume +`else +`define ASSUME assert +`endif + + reg f_past_valid; + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1; + + always @(*) + if (!f_past_valid) + assume(i_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Incoming stream properties / assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + always @(posedge i_clk) + if (!f_past_valid) + begin + `ASSUME(!i_valid || !OPT_INITIAL); + end else if ($past(i_valid && !o_ready && !i_reset) && !i_reset) + `ASSUME(i_valid && $stable(i_data)); + +`ifdef VERIFIC +`define FORMAL_VERIFIC + // Reset properties + property RESET_CLEARS_IVALID; + @(posedge i_clk) i_reset |=> !i_valid; + endproperty + + property IDATA_HELD_WHEN_NOT_READY; + @(posedge i_clk) disable iff (i_reset) + i_valid && !o_ready |=> i_valid && $stable(i_data); + endproperty + +`ifdef SKIDBUFFER + assume property (IDATA_HELD_WHEN_NOT_READY); +`else + assert property (IDATA_HELD_WHEN_NOT_READY); +`endif +`endif + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Outgoing stream properties / assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + generate if (!OPT_PASSTHROUGH) + begin + + always @(posedge i_clk) + if (!f_past_valid) // || $past(i_reset)) + begin + // Following any reset, valid must be deasserted + assert(!o_valid || !OPT_INITIAL); + end else if ($past(o_valid && !i_ready && !i_reset) && !i_reset) + // Following any stall, valid must remain high and + // data must be preserved + assert(o_valid && $stable(o_data)); + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Other properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (!OPT_PASSTHROUGH) + begin + // Rule #1: + // If registered, then following any reset we should be + // ready for a new request + // {{{ + always @(posedge i_clk) + if (f_past_valid && $past(OPT_OUTREG && i_reset)) + assert(o_ready); + // }}} + + // Rule #2: + // All incoming data must either go directly to the + // output port, or into the skid buffer + // {{{ +`ifndef VERIFIC + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && $past(i_valid && o_ready + && (!OPT_OUTREG || o_valid) && !i_ready)) + assert(!o_ready && w_data == $past(i_data)); +`else + assert property (@(posedge i_clk) + disable iff (i_reset) + (i_valid && o_ready + && (!OPT_OUTREG || o_valid) && !i_ready) + |=> (!o_ready && w_data == $past(i_data))); +`endif + // }}} + + // Rule #3: + // After the last transaction, o_valid should become idle + // {{{ + if (!OPT_OUTREG) + begin + // {{{ + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset) && !i_reset + && $past(i_ready)) + begin + assert(o_valid == i_valid); + assert(!i_valid || (o_data == i_data)); + end + // }}} + end else begin + // {{{ + always @(posedge i_clk) + if (f_past_valid && !$past(i_reset)) + begin + if ($past(i_valid && o_ready)) + assert(o_valid); + + if ($past(!i_valid && o_ready && i_ready)) + assert(!o_valid); + end + // }}} + end + // }}} + + // Rule #4 + // Same thing, but this time for o_ready + // {{{ + always @(posedge i_clk) + if (f_past_valid && $past(!o_ready && i_ready)) + assert(o_ready); + // }}} + + // If OPT_LOWPOWER is set, o_data and w_data both need to be + // zero any time !o_valid or !r_valid respectively + // {{{ + if (OPT_LOWPOWER) + begin + always @(*) + if ((OPT_OUTREG || !i_reset) && !o_valid) + assert(o_data == 0); + + always @(*) + if (o_ready) + assert(w_data == 0); + + end + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`ifdef SKIDBUFFER + generate if (!OPT_PASSTHROUGH) + begin + reg f_changed_data; + + initial f_changed_data = 0; + always @(posedge i_clk) + if (i_reset) + f_changed_data <= 1; + else if (i_valid && $past(!i_valid || o_ready)) + begin + if (i_data != $past(i_data + 1)) + f_changed_data <= 0; + end else if (!i_valid && i_data != 0) + f_changed_data <= 0; + + +`ifndef VERIFIC + reg [3:0] cvr_steps, cvr_hold; + + always @(posedge i_clk) + if (i_reset) + begin + cvr_steps <= 0; + cvr_hold <= 0; + end else begin + cvr_steps <= cvr_steps + 1; + cvr_hold <= cvr_hold + 1; + case(cvr_steps) + 0: if (o_valid || i_valid) + cvr_steps <= 0; + 1: if (!i_valid || !i_ready) + cvr_steps <= 0; + 2: if (!i_valid || !i_ready) + cvr_steps <= 0; + 3: if (!i_valid || !i_ready) + cvr_steps <= 0; + 4: if (!i_valid || i_ready) + cvr_steps <= 0; + 5: if (!i_valid || !i_ready) + cvr_steps <= 0; + 6: if (!i_valid || !i_ready) + cvr_steps <= 0; + 7: if (!i_valid || i_ready) + cvr_steps <= 0; + 8: if (!i_valid || i_ready) + cvr_steps <= 0; + 9: if (!i_valid || !i_ready) + cvr_steps <= 0; + 10: if (!i_valid || !i_ready) + cvr_steps <= 0; + 11: if (!i_valid || !i_ready) + cvr_steps <= 0; + 12: begin + cvr_steps <= cvr_steps; + cover(!o_valid && !i_valid && f_changed_data); + if (!o_valid || !i_ready) + cvr_steps <= 0; + else + cvr_hold <= cvr_hold + 1; + end + default: assert(0); + endcase + end + +`else + // Cover test + cover property (@(posedge i_clk) + disable iff (i_reset) + (!o_valid && !i_valid) + ##1 i_valid && i_ready [*3] + ##1 i_valid && !i_ready + ##1 i_valid && i_ready [*2] + ##1 i_valid && !i_ready [*2] + ##1 i_valid && i_ready [*3] + // Wait for the design to clear + ##1 o_valid && i_ready [*0:5] + ##1 (!o_valid && !i_valid && f_changed_data)); +`endif + end endgenerate +`endif // SKIDBUFFER + // }}} +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/wbarbiter.v b/rtl/wb2axip/wbarbiter.v new file mode 100644 index 0000000..e068278 --- /dev/null +++ b/rtl/wb2axip/wbarbiter.v @@ -0,0 +1,404 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbarbiter.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: This is a priority bus arbiter. It allows two separate wishbone +// masters to connect to the same bus, while also guaranteeing +// that the last master can have the bus with no delay any time it is +// idle. The goal is to minimize the combinatorial logic required in this +// process, while still minimizing access time. +// +// The core logic works like this: +// +// 1. If 'A' or 'B' asserts the o_cyc line, a bus cycle will begin, +// with acccess granted to whomever requested it. +// 2. If both 'A' and 'B' assert o_cyc at the same time, only 'A' +// will be granted the bus. (If the alternating parameter +// is set, A and B will alternate who gets the bus in +// this case.) +// 3. The bus will remain owned by whomever the bus was granted to +// until they deassert the o_cyc line. +// 4. At the end of a bus cycle, o_cyc is guaranteed to be +// deasserted (low) for one clock. +// 5. On the next clock, bus arbitration takes place again. If +// 'A' requests the bus, no matter how long 'B' was +// waiting, 'A' will then be granted the bus. (Unless +// again the alternating parameter is set, then the +// access is guaranteed to switch to B.) +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2015-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// +`define WBA_ALTERNATING +// }}} +module wbarbiter #( + // {{{ + parameter DW=32, AW=32, + parameter SCHEME="ALTERNATING", + parameter [0:0] OPT_ZERO_ON_IDLE = 1'b0, + parameter [31:0] F_MAX_STALL = 3, + parameter [31:0] F_MAX_ACK_DELAY = 3, + parameter [31:0] F_LGDEPTH=3 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // Bus A + // {{{ + input wire i_a_cyc, i_a_stb, i_a_we, + input wire [(AW-1):0] i_a_adr, + input wire [(DW-1):0] i_a_dat, + input wire [(DW/8-1):0] i_a_sel, + output wire o_a_ack, o_a_stall, o_a_err, + // }}} + // Bus B + // {{{ + input wire i_b_cyc, i_b_stb, i_b_we, + input wire [(AW-1):0] i_b_adr, + input wire [(DW-1):0] i_b_dat, + input wire [(DW/8-1):0] i_b_sel, + output wire o_b_ack, o_b_stall, o_b_err, + // }}} + // Combined/arbitrated bus + // {{{ + output wire o_cyc, o_stb, o_we, + output wire [(AW-1):0] o_adr, + output wire [(DW-1):0] o_dat, + output wire [(DW/8-1):0] o_sel, + input wire i_ack, i_stall, i_err + // }}} +`ifdef FORMAL + // {{{ + , + output wire [(F_LGDEPTH-1):0] + f_nreqs, f_nacks, f_outstanding, + f_a_nreqs, f_a_nacks, f_a_outstanding, + f_b_nreqs, f_b_nacks, f_b_outstanding + // }}} +`endif + // }}} + ); + // + + // Go high immediately (new cycle) if ... + // Previous cycle was low and *someone* is requesting a bus cycle + // Go low immadiately if ... + // We were just high and the owner no longer wants the bus + // WISHBONE Spec recommends no logic between a FF and the o_cyc + // This violates that spec. (Rec 3.15, p35) + reg r_a_owner; + + assign o_cyc = (r_a_owner) ? i_a_cyc : i_b_cyc; + initial r_a_owner = 1'b1; + + generate if (SCHEME == "PRIORITY") + begin : PRI + + always @(posedge i_clk) + if (!i_b_cyc) + r_a_owner <= 1'b1; + // Allow B to set its CYC line w/o activating this + // interface + else if ((i_b_stb)&&(!i_a_cyc)) + r_a_owner <= 1'b0; + + end else if (SCHEME == "ALTERNATING") + begin : ALT + + reg last_owner; + initial last_owner = 1'b0; + always @(posedge i_clk) + if ((i_a_cyc)&&(r_a_owner)) + last_owner <= 1'b1; + else if ((i_b_cyc)&&(!r_a_owner)) + last_owner <= 1'b0; + + always @(posedge i_clk) + if ((!i_a_cyc)&&(!i_b_cyc)) + r_a_owner <= !last_owner; + else if ((r_a_owner)&&(!i_a_cyc)) + begin + + if (i_b_stb) + r_a_owner <= 1'b0; + + end else if ((!r_a_owner)&&(!i_b_cyc)) + begin + + if (i_a_stb) + r_a_owner <= 1'b1; + + end + + end else // if (SCHEME == "LAST") + begin : LST + always @(posedge i_clk) + if ((!i_a_cyc)&&(i_b_stb)) + r_a_owner <= 1'b0; + else if ((!i_b_cyc)&&(i_a_stb)) + r_a_owner <= 1'b1; + end endgenerate + + + // Realistically, if neither master owns the bus, the output is a + // don't care. Thus we trigger off whether or not 'A' owns the bus. + // If 'B' owns it all we care is that 'A' does not. Likewise, if + // neither owns the bus than the values on the various lines are + // irrelevant. + assign o_we = (r_a_owner) ? i_a_we : i_b_we; + + generate if (OPT_ZERO_ON_IDLE) + begin : ZERO_IDLE + // {{{ + // + // OPT_ZERO_ON_IDLE will use up more logic and may even slow + // down the master clock if set. However, it may also reduce + // the power used by the FPGA by preventing things from toggling + // when the bus isn't in use. The option is here because it + // also makes it a lot easier to look for when things happen + // on the bus via VERILATOR when timing and logic counts + // don't matter. + // + assign o_stb = (o_cyc)? ((r_a_owner) ? i_a_stb : i_b_stb):0; + assign o_adr = (o_stb)? ((r_a_owner) ? i_a_adr : i_b_adr):0; + assign o_dat = (o_stb)? ((r_a_owner) ? i_a_dat : i_b_dat):0; + assign o_sel = (o_stb)? ((r_a_owner) ? i_a_sel : i_b_sel):0; + assign o_a_ack = (o_cyc)&&( r_a_owner) ? i_ack : 1'b0; + assign o_b_ack = (o_cyc)&&(!r_a_owner) ? i_ack : 1'b0; + assign o_a_stall = (o_cyc)&&( r_a_owner) ? i_stall : 1'b1; + assign o_b_stall = (o_cyc)&&(!r_a_owner) ? i_stall : 1'b1; + assign o_a_err = (o_cyc)&&( r_a_owner) ? i_err : 1'b0; + assign o_b_err = (o_cyc)&&(!r_a_owner) ? i_err : 1'b0; + // }}} + end else begin : LOW_LOGIC + // {{{ + assign o_stb = (r_a_owner) ? i_a_stb : i_b_stb; + assign o_adr = (r_a_owner) ? i_a_adr : i_b_adr; + assign o_dat = (r_a_owner) ? i_a_dat : i_b_dat; + assign o_sel = (r_a_owner) ? i_a_sel : i_b_sel; + + // We cannot allow the return acknowledgement to ever go high if + // the master in question does not own the bus. Hence we force + // it low if the particular master doesn't own the bus. + assign o_a_ack = ( r_a_owner) ? i_ack : 1'b0; + assign o_b_ack = (!r_a_owner) ? i_ack : 1'b0; + + // Stall must be asserted on the same cycle the input master + // asserts the bus, if the bus isn't granted to him. + assign o_a_stall = ( r_a_owner) ? i_stall : 1'b1; + assign o_b_stall = (!r_a_owner) ? i_stall : 1'b1; + + // + // + assign o_a_err = ( r_a_owner) ? i_err : 1'b0; + assign o_b_err = (!r_a_owner) ? i_err : 1'b0; + // }}} + end endgenerate + + // Make Verilator happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, i_reset, F_LGDEPTH, F_MAX_STALL, + F_MAX_ACK_DELAY }; + // verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + +`ifdef WBARBITER + +`define ASSUME assume +`else +`define ASSUME assert +`endif + reg f_prior_a_ack, f_prior_b_ack; + + + reg f_past_valid; + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + initial `ASSUME(!i_a_cyc); + initial `ASSUME(!i_a_stb); + + initial `ASSUME(!i_b_cyc); + initial `ASSUME(!i_b_stb); + + initial `ASSUME(!i_ack); + initial `ASSUME(!i_err); + + always @(*) + if (!f_past_valid) + `ASSUME(i_reset); + + always @(posedge i_clk) + begin + if (o_cyc) + assert((i_a_cyc)||(i_b_cyc)); + if ((f_past_valid)&&($past(o_cyc))&&(o_cyc)) + assert($past(r_a_owner) == r_a_owner); + end + + fwb_master #( + // {{{ + .DW(DW), .AW(AW), + .F_MAX_STALL(F_MAX_STALL), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_ACK_DELAY(F_MAX_ACK_DELAY), + .F_OPT_RMW_BUS_OPTION(1), + .F_OPT_DISCONTINUOUS(1) + // }}} + ) f_wbm( + // {{{ + i_clk, i_reset, + o_cyc, o_stb, o_we, o_adr, o_dat, o_sel, + i_ack, i_stall, 32'h0, i_err, + f_nreqs, f_nacks, f_outstanding + // }}} + ); + + fwb_slave #( + // {{{ + .DW(DW), .AW(AW), + .F_MAX_STALL(0), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_ACK_DELAY(0), + .F_OPT_RMW_BUS_OPTION(1), + .F_OPT_DISCONTINUOUS(1) + // }}} + ) f_wba( + // {{{ + i_clk, i_reset, + i_a_cyc, i_a_stb, i_a_we, i_a_adr, i_a_dat, i_a_sel, + o_a_ack, o_a_stall, 32'h0, o_a_err, + f_a_nreqs, f_a_nacks, f_a_outstanding + // }}} + ); + + fwb_slave #( + // {{{ + .DW(DW), .AW(AW), + .F_MAX_STALL(0), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_ACK_DELAY(0), + .F_OPT_RMW_BUS_OPTION(1), + .F_OPT_DISCONTINUOUS(1) + // }}} + ) f_wbb( + // {{{ + i_clk, i_reset, + i_b_cyc, i_b_stb, i_b_we, i_b_adr, i_b_dat, i_b_sel, + o_b_ack, o_b_stall, 32'h0, o_b_err, + f_b_nreqs, f_b_nacks, f_b_outstanding + // }}} + ); + + always @(posedge i_clk) + if (r_a_owner) + begin + assert(f_b_nreqs == 0); + assert(f_b_nacks == 0); + assert(f_a_outstanding == f_outstanding); + end else begin + assert(f_a_nreqs == 0); + assert(f_a_nacks == 0); + assert(f_b_outstanding == f_outstanding); + end + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past(i_a_stb))&&(!$past(i_b_cyc))) + assert(r_a_owner); + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&(!$past(i_a_cyc))&&($past(i_b_stb))) + assert(!r_a_owner); + + always @(posedge i_clk) + if ((f_past_valid)&&(r_a_owner != $past(r_a_owner))) + assert(!$past(o_cyc)); + + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + initial f_prior_a_ack = 1'b0; + always @(posedge i_clk) + if ((i_reset)||(o_a_err)||(o_b_err)) + f_prior_a_ack <= 1'b0; + else if ((o_cyc)&&(o_a_ack)) + f_prior_a_ack <= 1'b1; + + initial f_prior_b_ack = 1'b0; + always @(posedge i_clk) + if ((i_reset)||(o_a_err)||(o_b_err)) + f_prior_b_ack <= 1'b0; + else if ((o_cyc)&&(o_b_ack)) + f_prior_b_ack <= 1'b1; + + always @(posedge i_clk) + begin + cover(f_prior_b_ack && o_cyc && o_a_ack); + + cover((o_cyc && o_a_ack) + &&($past(o_cyc && o_a_ack)) + &&($past(o_cyc && o_a_ack,2))); + + + cover(f_prior_a_ack && o_cyc && o_b_ack); + + cover((o_cyc && o_b_ack) + &&($past(o_cyc && o_b_ack)) + &&($past(o_cyc && o_b_ack,2))); + end + + always @(*) + cover(o_cyc && o_b_ack); + // }}} +// }}} +`endif +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/wbc2pipeline.v b/rtl/wb2axip/wbc2pipeline.v new file mode 100644 index 0000000..ad6a11c --- /dev/null +++ b/rtl/wb2axip/wbc2pipeline.v @@ -0,0 +1,188 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbc2pipeline.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Takes a WB classic connection from a master, and converts it +// to WB pipelined for the slave. +// +// If you don't see an `ifdef FORMAL section below, then this core hasn't +// (yet) been formally verified. Use it at your own risk. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbc2pipeline #( + // {{{ + parameter AW = 12, + DW = 32 + // }}} + ) ( + // {{{ + // + input wire i_clk, i_reset, + // + // Incoming WB classic port + input wire i_scyc, i_sstb, i_swe, + input wire [AW-1:0] i_saddr, + input wire [DW-1:0] i_sdata, + input wire [DW/8-1:0] i_ssel, + output reg o_sack, + output reg [DW-1:0] o_sdata, + output reg o_serr, + input wire [3-1:0] i_scti, + input wire [2-1:0] i_sbte, + // + // Outgoing WB pipelined port + output reg o_mcyc, o_mstb, o_mwe, + output reg [AW-1:0] o_maddr, + output reg [DW-1:0] o_mdata, + output reg [DW/8-1:0] o_msel, + input wire i_mstall, + input wire i_mack, + input wire [DW-1:0] i_mdata, + input wire i_merr + // }}} + ); + + reg last_stb; + + // last_stb + // {{{ + initial last_stb = 0; + always @(posedge i_clk) + if (i_reset || !i_sstb || o_sack || o_serr) + last_stb <= 0; + else if (!i_mstall) + last_stb <= 1; + // }}} + + // Combinatorial assignments to the downstream port + // {{{ + always @(*) + begin + o_mcyc = i_scyc && (!o_serr); + o_mstb = i_sstb & !last_stb && (!o_serr); + o_mwe = i_swe; + o_maddr = i_saddr; + o_mdata = i_sdata; + o_msel = i_ssel; + end + // }}} + + // o_sack, o_serr + // {{{ + initial o_sack = 0; + initial o_serr = 0; + always @(posedge i_clk) + if (i_reset || !i_sstb || o_sack || o_serr) + begin + o_sack <= 0; + o_serr <= 0; + end else begin + if (i_mack) + o_sack <= 1; + if (i_merr) + o_serr <= 1; + end + // }}} + + // o_sdata + // {{{ + always @(posedge i_clk) + if (i_mack) + o_sdata <= i_mdata; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire [4:0] unused; + assign unused = { i_scti, i_sbte }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // + // + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_LGDEPTH = 1; + reg [F_LGDEPTH-1:0] f_nreqs, f_nacks, f_outstanding; + + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid = 1; + + always @(*) + if (!f_past_valid) + assume(i_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Upstream Wishbone classic slave properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + fwbc_slave #(.AW(AW), .DW(DW)) incoming (i_clk, i_reset, + i_scyc, i_sstb, i_swe, i_saddr, i_sdata, i_ssel, + i_scti, i_sbte, + o_sack, o_sdata, o_serr, 1'b0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Downstream Wishbone pipeline slave properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + fwb_master #(.AW(AW), .DW(DW), + .F_MAX_STALL(3), + .F_MAX_ACK_DELAY(3), + .F_LGDEPTH(1)) pipelined (i_clk, i_reset, + o_mcyc, o_mstb, o_mwe, o_maddr, o_mdata, o_msel, + i_mack, i_mstall, i_mdata, i_merr, + f_nreqs, f_nacks, f_outstanding); + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/wbm2axilite.v b/rtl/wb2axip/wbm2axilite.v new file mode 100644 index 0000000..6cda44d --- /dev/null +++ b/rtl/wb2axip/wbm2axilite.v @@ -0,0 +1,685 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbm2axilite.v (Wishbone master to AXI slave, pipelined) +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Convert from a wishbone master to an AXI lite interface. The +// big difference is that AXI lite doesn't support bursting, +// or transaction ID's. This actually makes the task a *LOT* easier. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2018-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbm2axilite #( + // {{{ + parameter C_AXI_ADDR_WIDTH = 28,// AXI Address width + localparam C_AXI_DATA_WIDTH = 32,// Width of the AXI R&W data + localparam DW = C_AXI_DATA_WIDTH,// Wishbone data width + localparam AW = C_AXI_ADDR_WIDTH-2// WB addr width (log wordsize) + // }}} + ) ( + // {{{ + // We'll share the clock and the reset + input wire i_clk, + input wire i_reset, + // Wishbone + // {{{ + input wire i_wb_cyc, + input wire i_wb_stb, + input wire i_wb_we, + input wire [(AW-1):0] i_wb_addr, + input wire [(DW-1):0] i_wb_data, + input wire [(DW/8-1):0] i_wb_sel, + output wire o_wb_stall, + output reg o_wb_ack, + output reg [(DW-1):0] o_wb_data, + output reg o_wb_err, + // }}} + // AXI-Lite + // {{{ + // AXI write address channel signals + output reg o_axi_awvalid, + input wire i_axi_awready, + output reg [C_AXI_ADDR_WIDTH-1:0] o_axi_awaddr, + output wire [2:0] o_axi_awprot, + // + // AXI write data channel signals + output reg o_axi_wvalid, + input wire i_axi_wready, + output reg [C_AXI_DATA_WIDTH-1:0] o_axi_wdata, + output reg [C_AXI_DATA_WIDTH/8-1:0] o_axi_wstrb, + // + // AXI write response channel signals + input wire i_axi_bvalid, + output wire o_axi_bready, + input wire [1:0] i_axi_bresp, + // + // AXI read address channel signals + output reg o_axi_arvalid, + input wire i_axi_arready, + output reg [C_AXI_ADDR_WIDTH-1:0] o_axi_araddr, + output wire [2:0] o_axi_arprot, + // + // AXI read data channel signals + input wire i_axi_rvalid, + output wire o_axi_rready, + input wire [C_AXI_DATA_WIDTH-1:0] i_axi_rdata, + input wire [1:0] i_axi_rresp + // }}} + // }}} + ); + + // Declarations + // {{{ +//***************************************************************************** +// Local Parameter declarations +//***************************************************************************** + + // + // LGIFOFLN: The log (based two) of the size of our FIFO. This is a + // localparam since 1) 32-bit distributed memories nearly come for + // free, and 2) because there is no performance gain to be had in larger + // memories. 2^32 entries is the perfect size for this application. + // Any smaller, and the core will not be able to maintain 100% + // throughput. + localparam LGFIFOLN = 5; + + +//***************************************************************************** +// Internal register and wire declarations +//***************************************************************************** + +// Things we're not changing ... + assign o_axi_awprot = 3'b000; // Unpriviledged, unsecure, data access + assign o_axi_arprot = 3'b000; // Unpriviledged, unsecure, data access + + reg full_fifo, err_state, axi_reset_state, wb_we; + reg [3:0] reset_count; + reg pending; + reg [LGFIFOLN-1:0] outstanding, err_pending; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Master bridge logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_wb_stall + // {{{ + assign o_wb_stall = (full_fifo) + ||((!i_wb_we)&&( wb_we)&&(pending)) + ||(( i_wb_we)&&(!wb_we)&&(pending)) + ||(err_state)||(axi_reset_state) + ||(o_axi_arvalid)&&(!i_axi_arready) + ||(o_axi_awvalid)&&(!i_axi_awready) + ||(o_axi_wvalid)&&(!i_axi_wready); + // }}} + + // reset_count, axi_reset_state + // {{{ + initial axi_reset_state = 1'b1; + initial reset_count = 4'hf; + always @(posedge i_clk) + if (i_reset) + begin + axi_reset_state <= 1'b1; + if (reset_count > 0) + reset_count <= reset_count - 1'b1; + end else if ((axi_reset_state)&&(reset_count > 0)) + reset_count <= reset_count - 1'b1; + else begin + axi_reset_state <= 1'b0; + reset_count <= 4'hf; + end + // }}} + + // pending, outstanding, full_fifo: Count outstanding transactions + // {{{ + initial pending = 0; + initial outstanding = 0; + always @(posedge i_clk) + if ((i_reset)||(axi_reset_state)) + begin + pending <= 0; + outstanding <= 0; + full_fifo <= 0; + end else if ((err_state)||(!i_wb_cyc)) + begin + pending <= 0; + outstanding <= 0; + full_fifo <= 0; + end else case({ ((i_wb_stb)&&(!o_wb_stall)), (o_wb_ack) }) + 2'b01: begin + outstanding <= outstanding - 1'b1; + pending <= (outstanding >= 2); + full_fifo <= 1'b0; + end + 2'b10: begin + outstanding <= outstanding + 1'b1; + pending <= 1'b1; + full_fifo <= (outstanding >= {{(LGFIFOLN-2){1'b1}},2'b01}); + end + default: begin end + endcase + // }}} + + always @(posedge i_clk) + if ((i_wb_stb)&&(!o_wb_stall)) + wb_we <= i_wb_we; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write address logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + + // o_axi_awvalid + // {{{ + initial o_axi_awvalid = 0; + always @(posedge i_clk) + if (i_reset) + o_axi_awvalid <= 0; + else + o_axi_awvalid <= (!o_wb_stall)&&(i_wb_stb)&&(i_wb_we) + ||(o_axi_awvalid)&&(!i_axi_awready); + // }}} + + + // o_axi_awaddr + // {{{ + always @(posedge i_clk) + if (!o_wb_stall) + o_axi_awaddr <= { i_wb_addr, 2'b00 }; + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Read address logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_axi_arvalid + // {{{ + initial o_axi_arvalid = 1'b0; + always @(posedge i_clk) + if (i_reset) + o_axi_arvalid <= 1'b0; + else + o_axi_arvalid <= (!o_wb_stall)&&(i_wb_stb)&&(!i_wb_we) + ||((o_axi_arvalid)&&(!i_axi_arready)); + // }}} + + // o_axi_araddr + // {{{ + always @(posedge i_clk) + if (!o_wb_stall) + o_axi_araddr <= { i_wb_addr, 2'b00 }; + // }}} + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Write data logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // o_axi_wdata, o_axi_wstrb + // {{{ + always @(posedge i_clk) + if (!o_wb_stall) + begin + o_axi_wdata <= i_wb_data; + o_axi_wstrb <= i_wb_sel; + end + // }}} + + // o_axi_wvalid + // {{{ + initial o_axi_wvalid = 0; + always @(posedge i_clk) + if (i_reset) + o_axi_wvalid <= 0; + else + o_axi_wvalid <= ((!o_wb_stall)&&(i_wb_stb)&&(i_wb_we)) + ||((o_axi_wvalid)&&(!i_axi_wready)); + // }}} + + // o_wb_ack + // {{{ + initial o_wb_ack = 1'b0; + always @(posedge i_clk) + if ((i_reset)||(!i_wb_cyc)||(err_state)) + o_wb_ack <= 1'b0; + else if (err_state) + o_wb_ack <= 1'b0; + else if ((i_axi_bvalid)&&(!i_axi_bresp[1])) + o_wb_ack <= 1'b1; + else if ((i_axi_rvalid)&&(!i_axi_rresp[1])) + o_wb_ack <= 1'b1; + else + o_wb_ack <= 1'b0; + // }}} + + // o_wb_data + // {{{ + always @(posedge i_clk) + o_wb_data <= i_axi_rdata; + // }}} + // }}} + // Read data channel / response logic + assign o_axi_rready = 1'b1; + assign o_axi_bready = 1'b1; + + // o_wb_err + // {{{ + initial o_wb_err = 1'b0; + always @(posedge i_clk) + if ((i_reset)||(!i_wb_cyc)||(err_state)) + o_wb_err <= 1'b0; + else if ((i_axi_bvalid)&&(i_axi_bresp[1])) + o_wb_err <= 1'b1; + else if ((i_axi_rvalid)&&(i_axi_rresp[1])) + o_wb_err <= 1'b1; + else + o_wb_err <= 1'b0; + // }}} + + // err_state + // {{{ + initial err_state = 1'b0; + always @(posedge i_clk) + if (i_reset) + err_state <= 0; + else if ((i_axi_bvalid)&&(i_axi_bresp[1])) + err_state <= 1'b1; + else if ((i_axi_rvalid)&&(i_axi_rresp[1])) + err_state <= 1'b1; + else if ((pending)&&(!i_wb_cyc)) + err_state <= 1'b1; + else if (err_pending == 0) + err_state <= 0; + // }}} + + // err_pending + // {{{ + initial err_pending = 0; + always @(posedge i_clk) + if (i_reset) + err_pending <= 0; + else case({ ((i_wb_stb)&&(!o_wb_stall)), + ((i_axi_bvalid)||(i_axi_rvalid)) }) + 2'b01: err_pending <= err_pending - 1'b1; + 2'b10: err_pending <= err_pending + 1'b1; + default: begin end + endcase + // }}} + + // Make verilator happy + // {{{ + // verilator lint_off UNUSED + wire [2:0] unused; + assign unused = { i_wb_cyc, i_axi_bresp[0], i_axi_rresp[0] }; + // verilator lint_on UNUSED + // }}} +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +// +// Formal methods section +// {{{ +// These are only relevant when *proving* that this translator works +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + localparam FIFOLN = (1<<LGFIFOLN); + reg f_past_valid; +// +`define ASSUME assume +`define ASSERT assert + + // Parameters + initial assert(DW == 32); + initial assert(C_AXI_ADDR_WIDTH == AW+2); + // + + // + // Setup + // + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + + always @(*) + if (!f_past_valid) + `ASSUME(i_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Bus properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + assume(f_past_valid || i_reset); + + wire [(LGFIFOLN-1):0] f_wb_nreqs, f_wb_nacks,f_wb_outstanding; + fwb_slave #( + // {{{ + .DW(DW),.AW(AW), + .F_MAX_STALL(0), + .F_MAX_ACK_DELAY(0), + .F_LGDEPTH(LGFIFOLN), + .F_MAX_REQUESTS(FIFOLN-2) + // }}} + ) f_wb( + // {{{ + i_clk, i_reset, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, + i_wb_data, i_wb_sel, + o_wb_ack, o_wb_stall, o_wb_data, o_wb_err, + f_wb_nreqs, f_wb_nacks, f_wb_outstanding + // }}} + ); + + wire [(LGFIFOLN-1):0] f_axi_rd_outstanding, + f_axi_wr_outstanding, + f_axi_awr_outstanding; + + faxil_master #( + // {{{ + // .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH), + .F_LGDEPTH(LGFIFOLN), + .F_AXI_MAXWAIT(3), + .F_AXI_MAXDELAY(3) + // }}} + ) f_axil( + // {{{ + .i_clk(i_clk), + .i_axi_reset_n((!i_reset)&&(!axi_reset_state)), + // Write address channel + .i_axi_awvalid(o_axi_awvalid), + .i_axi_awready(i_axi_awready), + .i_axi_awaddr( o_axi_awaddr), + .i_axi_awprot( o_axi_awprot), + // Write data channel + .i_axi_wvalid( o_axi_wvalid), + .i_axi_wready( i_axi_wready), + .i_axi_wdata( o_axi_wdata), + .i_axi_wstrb( o_axi_wstrb), + // Write response channel + .i_axi_bvalid( i_axi_bvalid), + .i_axi_bready( o_axi_bready), + .i_axi_bresp( i_axi_bresp), + // Read address channel + .i_axi_arvalid(o_axi_arvalid), + .i_axi_arready(i_axi_arready), + .i_axi_araddr( o_axi_araddr), + .i_axi_arprot( o_axi_arprot), + // Read data channel + .i_axi_rvalid( i_axi_rvalid), + .i_axi_rready( o_axi_rready), + .i_axi_rdata( i_axi_rdata), + .i_axi_rresp( i_axi_rresp), + // Counts + .f_axi_rd_outstanding( f_axi_rd_outstanding), + .f_axi_wr_outstanding( f_axi_wr_outstanding), + .f_axi_awr_outstanding( f_axi_awr_outstanding) + // }}} + ); + // }}} + + + ////////////////////////////////////////////// + // + // + // Assertions about the AXI4 ouputs + // + // + ////////////////////////////////////////////// + + // Write response channel + always @(posedge i_clk) + // We keep bready high, so the other condition doesn't + // need to be checked + assert(o_axi_bready); + + // AXI read data channel signals + always @(posedge i_clk) + // We keep o_axi_rready high, so the other condition's + // don't need to be checked here + assert(o_axi_rready); + + // + // Let's look into write requests + // + initial assert(!o_axi_awvalid); + initial assert(!o_axi_wvalid); + always @(posedge i_clk) + if ((!f_past_valid)||($past(i_reset))||($past(axi_reset_state))) + begin + assert(!o_axi_awvalid); + assert(!o_axi_wvalid); + end + + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past((i_wb_stb)&&(i_wb_we)&&(!o_wb_stall)))) + begin + // Following any write request that we accept, awvalid + // and wvalid should both be true + assert(o_axi_awvalid); + assert(o_axi_wvalid); + assert(wb_we); + end else if ((f_past_valid)&&($past(i_reset))) + begin + if ($past(i_axi_awready)) + assert(!o_axi_awvalid); + if ($past(i_axi_wready)) + assert(!o_axi_wvalid); + end + + // + // AXI write address channel + // + always @(posedge i_clk) + if ((f_past_valid)&&($past((i_wb_stb)&&(i_wb_we)&&(!o_wb_stall)))) + assert(o_axi_awaddr == { $past(i_wb_addr[AW-1:0]), 2'b00 }); + + // + // AXI write data channel + // + always @(posedge i_clk) + if ((f_past_valid)&&($past(i_wb_stb)&&(i_wb_we)&&(!$past(o_wb_stall)))) + begin + assert(o_axi_wdata == $past(i_wb_data)); + assert(o_axi_wstrb == $past(i_wb_sel)); + end + + // + // AXI read address channel + // + initial assert(!o_axi_arvalid); + always @(posedge i_clk) + if ((f_past_valid)&&(!$past(i_reset)) + &&($past((i_wb_stb)&&(!i_wb_we)&&(!o_wb_stall)))) + begin + assert(o_axi_arvalid); + assert(o_axi_araddr == { $past(i_wb_addr), 2'b00 }); + end + // + + // + // AXI write response channel + // + + // + // AXI read data channel signals + // + always @(posedge i_clk) + if ((f_past_valid)&&(($past(i_reset))||($past(axi_reset_state)))) + begin + // Relate err_pending to outstanding + assert(outstanding == 0); + assert(err_pending == 0); + end else if (!err_state) + assert(err_pending == outstanding - ((o_wb_ack)||(o_wb_err))); + + always @(posedge i_clk) + if ((f_past_valid)&&(($past(i_reset))||($past(axi_reset_state)))) + begin + assert(f_axi_awr_outstanding == 0); + assert(f_axi_wr_outstanding == 0); + assert(f_axi_rd_outstanding == 0); + + assert(f_wb_outstanding == 0); + assert(!pending); + assert(outstanding == 0); + assert(err_pending == 0); + end else if (wb_we) + begin + case({o_axi_awvalid,o_axi_wvalid}) + 2'b00: begin + `ASSERT(f_axi_awr_outstanding == err_pending); + `ASSERT(f_axi_wr_outstanding == err_pending); + end + 2'b01: begin + `ASSERT(f_axi_awr_outstanding == err_pending); + `ASSERT(f_axi_wr_outstanding +1 == err_pending); + end + 2'b10: begin + `ASSERT(f_axi_awr_outstanding+1 == err_pending); + `ASSERT(f_axi_wr_outstanding == err_pending); + end + 2'b11: begin + `ASSERT(f_axi_awr_outstanding+1 == err_pending); + `ASSERT(f_axi_wr_outstanding +1 == err_pending); + end + endcase + + // + `ASSERT(!o_axi_arvalid); + `ASSERT(f_axi_rd_outstanding == 0); + end else begin + if (!o_axi_arvalid) + `ASSERT(f_axi_rd_outstanding == err_pending); + else + `ASSERT(f_axi_rd_outstanding+1 == err_pending); + + `ASSERT(!o_axi_awvalid); + `ASSERT(!o_axi_wvalid); + `ASSERT(f_axi_awr_outstanding == 0); + `ASSERT(f_axi_wr_outstanding == 0); + end + + always @(*) + if ((!i_reset)&&(i_wb_cyc)&&(!err_state)) + `ASSERT(f_wb_outstanding == outstanding); + + always @(posedge i_clk) + if ((f_past_valid)&&(err_state)) + `ASSERT((o_wb_err)||(f_wb_outstanding == 0)); + + always @(posedge i_clk) + `ASSERT(pending == (outstanding != 0)); + // + // Make sure we only create one request at a time + always @(posedge i_clk) + `ASSERT((!o_axi_arvalid)||(!o_axi_wvalid)); + always @(posedge i_clk) + `ASSERT((!o_axi_arvalid)||(!o_axi_awvalid)); + always @(posedge i_clk) + if (wb_we) + `ASSERT(!o_axi_arvalid); + else + `ASSERT((!o_axi_awvalid)&&(!o_axi_wvalid)); + + always @(*) + if (&outstanding[LGFIFOLN-1:1]) + `ASSERT(full_fifo); + always @(*) + assert(outstanding < {(LGFIFOLN){1'b1}}); + + // AXI cover results + always @(*) + cover(i_axi_bvalid && o_axi_bready); + always @(*) + cover(i_axi_rvalid && o_axi_rready); + + always @(posedge i_clk) + cover(i_axi_bvalid && o_axi_bready + && $past(i_axi_bvalid && o_axi_bready) + && $past(i_axi_bvalid && o_axi_bready,2)); + + always @(posedge i_clk) + cover(i_axi_rvalid && o_axi_rready + && $past(i_axi_rvalid && o_axi_rready) + && $past(i_axi_rvalid && o_axi_rready,2)); + + // AXI cover requests + always @(posedge i_clk) + cover(o_axi_arvalid && i_axi_arready + && $past(o_axi_arvalid && i_axi_arready) + && $past(o_axi_arvalid && i_axi_arready,2)); + + always @(posedge i_clk) + cover(o_axi_awvalid && i_axi_awready + && $past(o_axi_awvalid && i_axi_awready) + && $past(o_axi_awvalid && i_axi_awready,2)); + + always @(posedge i_clk) + cover(o_axi_wvalid && i_axi_wready + && $past(o_axi_wvalid && i_axi_wready) + && $past(o_axi_wvalid && i_axi_wready,2)); + + always @(*) + cover(i_axi_rvalid && o_axi_rready); + + // Wishbone cover results + always @(*) + cover(i_wb_cyc && o_wb_ack); + + always @(posedge i_clk) + cover(i_wb_cyc && o_wb_ack + && $past(o_wb_ack)&&$past(o_wb_ack,2)); + +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/wbm2axisp.v b/rtl/wb2axip/wbm2axisp.v new file mode 100644 index 0000000..9c7909a --- /dev/null +++ b/rtl/wb2axip/wbm2axisp.v @@ -0,0 +1,1155 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbm2axisp.v (Wishbone master to AXI slave, pipelined) +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: The B4 Wishbone SPEC allows transactions at a speed as fast as +// one per clock. The AXI bus allows transactions at a speed of +// one read and one write transaction per clock. These capabilities work +// by allowing requests to take place prior to responses, such that the +// requests might go out at once per clock and take several clocks, and +// the responses may start coming back several clocks later. In other +// words, both protocols allow multiple transactions to be "in flight" at +// the same time. Current wishbone to AXI converters, however, handle only +// one transaction at a time: initiating the transaction, and then waiting +// for the transaction to complete before initiating the next. +// +// The purpose of this core is to maintain the speed of both buses, while +// transiting from the Wishbone (as master) to the AXI bus (as slave) and +// back again. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2016-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbm2axisp #( + // {{{ + parameter C_AXI_DATA_WIDTH = 128,// Width of the AXI R&W data + parameter C_AXI_ADDR_WIDTH = 28, // AXI Address width (log wordsize) + parameter C_AXI_ID_WIDTH = 1, + parameter DW = 32, // Wishbone data width + parameter AW = 26, // Wishbone address width (log wordsize) + parameter [C_AXI_ID_WIDTH-1:0] AXI_WRITE_ID = 1'b0, + parameter [C_AXI_ID_WIDTH-1:0] AXI_READ_ID = 1'b1, + // + // OPT_LITTLE_ENDIAN controls which word has the greatest address + // when the bus size is adjusted. If OPT_LITTLE_ENDIAN is true, + // the biggest address is in the most significant word(s), otherwise + // the least significant word(s). This parameter is ignored if + // C_AXI_DATA_WIDTH == DW. + parameter [0:0] OPT_LITTLE_ENDIAN = 1'b1, + parameter LGFIFO = 6 + // }}} + ) ( + // {{{ + input wire i_clk, // System clock + input wire i_reset,// Reset signal,drives AXI rst + + // AXI write address channel signals + output reg o_axi_awvalid, // Write address valid + input wire i_axi_awready, // Slave is ready to accept + output wire [C_AXI_ID_WIDTH-1:0] o_axi_awid, // Write ID + output reg [C_AXI_ADDR_WIDTH-1:0] o_axi_awaddr, // Write address + output wire [7:0] o_axi_awlen, // Write Burst Length + output wire [2:0] o_axi_awsize, // Write Burst size + output wire [1:0] o_axi_awburst, // Write Burst type + output wire [0:0] o_axi_awlock, // Write lock type + output wire [3:0] o_axi_awcache, // Write Cache type + output wire [2:0] o_axi_awprot, // Write Protection type + output wire [3:0] o_axi_awqos, // Write Quality of Svc + +// AXI write data channel signals + output reg o_axi_wvalid, // Write valid + input wire i_axi_wready, // Write data ready + output reg [C_AXI_DATA_WIDTH-1:0] o_axi_wdata, // Write data + output reg [C_AXI_DATA_WIDTH/8-1:0] o_axi_wstrb, // Write strobes + output wire o_axi_wlast, // Last write transaction + +// AXI write response channel signals + input wire i_axi_bvalid, // Write reponse valid + output wire o_axi_bready, // Response ready + input wire [C_AXI_ID_WIDTH-1:0] i_axi_bid, // Response ID + input wire [1:0] i_axi_bresp, // Write response + +// AXI read address channel signals + output reg o_axi_arvalid, // Read address valid + input wire i_axi_arready, // Read address ready + output wire [C_AXI_ID_WIDTH-1:0] o_axi_arid, // Read ID + output reg [C_AXI_ADDR_WIDTH-1:0] o_axi_araddr, // Read address + output wire [7:0] o_axi_arlen, // Read Burst Length + output wire [2:0] o_axi_arsize, // Read Burst size + output wire [1:0] o_axi_arburst, // Read Burst type + output wire [0:0] o_axi_arlock, // Read lock type + output wire [3:0] o_axi_arcache, // Read Cache type + output wire [2:0] o_axi_arprot, // Read Protection type + output wire [3:0] o_axi_arqos, // Read Protection type + +// AXI read data channel signals + input wire i_axi_rvalid, // Read reponse valid + output wire o_axi_rready, // Read Response ready + input wire [C_AXI_ID_WIDTH-1:0] i_axi_rid, // Response ID + input wire [C_AXI_DATA_WIDTH-1:0] i_axi_rdata, // Read data + input wire [1:0] i_axi_rresp, // Read response + input wire i_axi_rlast, // Read last + + // We'll share the clock and the reset + input wire i_wb_cyc, + input wire i_wb_stb, + input wire i_wb_we, + input wire [(AW-1):0] i_wb_addr, + input wire [(DW-1):0] i_wb_data, + input wire [(DW/8-1):0] i_wb_sel, + output reg o_wb_stall, + output reg o_wb_ack, + output reg [(DW-1):0] o_wb_data, + output reg o_wb_err + // }}} +); + //////////////////////////////////////////////////////////////////////// + // + // Localparameter declarations, initial parameter consistency check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + localparam LG_AXI_DW = $clog2(C_AXI_DATA_WIDTH); + localparam LG_WB_DW = $clog2(DW); + // localparam FIFOLN = (1<<LGFIFO); + localparam SUBW = LG_AXI_DW-LG_WB_DW; + + // The various address widths must be properly related. We'll insist + // upon that relationship here. + initial begin + // This design can't (currently) handle WB widths wider than + // the AXI width it is driving. It can only handle widths + // mismatches in the other direction + if (C_AXI_DATA_WIDTH < DW) + $stop; + if (DW == 8 && AW != C_AXI_ADDR_WIDTH) + $stop; + + // There must be a definitive relationship between the address + // widths of the AXI and WB, and that width is dependent upon + // the WB data width + if (C_AXI_ADDR_WIDTH != AW + $clog2(DW)-3) + $stop; + if ( (C_AXI_DATA_WIDTH / DW !=32) + &&(C_AXI_DATA_WIDTH / DW !=16) + &&(C_AXI_DATA_WIDTH / DW != 8) + &&(C_AXI_DATA_WIDTH / DW != 4) + &&(C_AXI_DATA_WIDTH / DW != 2) + &&(C_AXI_DATA_WIDTH != DW)) + $stop; + end + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Internal register and wire declarations + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Things we're not changing ... + localparam DWSIZE = $clog2(DW)-3; + assign o_axi_awid = AXI_WRITE_ID; + assign o_axi_awlen = 8'h0; // Burst length is one + assign o_axi_awsize = DWSIZE[2:0]; + assign o_axi_wlast = 1; + assign o_axi_awburst = 2'b01; // Incrementing address (ignored) + assign o_axi_awlock = 1'b0; // Normal signaling + assign o_axi_arlock = 1'b0; // Normal signaling + assign o_axi_awcache = 4'h3; // Normal: no cache, modifiable + // + assign o_axi_arid = AXI_READ_ID; + assign o_axi_arlen = 8'h0; // Burst length is one + assign o_axi_arsize = DWSIZE[2:0]; + assign o_axi_arburst = 2'b01; // Incrementing address (ignored) + assign o_axi_arcache = 4'h3; // Normal: no cache, modifiable + assign o_axi_awprot = 3'b010; // Unpriviledged, unsecure, data access + assign o_axi_arprot = 3'b010; // Unpriviledged, unsecure, data access + assign o_axi_awqos = 4'h0; // Lowest quality of service (unused) + assign o_axi_arqos = 4'h0; // Lowest quality of service (unused) + + reg direction, full, empty, flushing, nearfull; + reg [LGFIFO:0] npending; + // + wire skid_ready, m_valid, m_we; + reg m_ready; + wire [AW-1:0] m_addr; + wire [DW-1:0] m_data; + wire [DW/8-1:0] m_sel; + + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Overarching command logic + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + initial direction = 0; + always @(posedge i_clk) + if (empty) + direction <= m_we; + + initial npending = 0; + initial empty = 1; + initial full = 0; + initial nearfull = 0; + always @(posedge i_clk) + if (i_reset) + begin + npending <= 0; + empty <= 1; + full <= 0; + nearfull <= 0; + end else case ({m_valid && m_ready, i_axi_bvalid||i_axi_rvalid}) + 2'b10: begin + npending <= npending + 1; + empty <= 0; + nearfull <= &(npending[LGFIFO-1:1]); + full <= &(npending[LGFIFO-1:0]); + end + 2'b01: begin + nearfull <= full; + npending <= npending - 1; + empty <= (npending == 1); + full <= 0; + end + default: begin end + endcase + + initial flushing = 0; + always @(posedge i_clk) + if (i_reset) + flushing <= 0; + else if ((i_axi_rvalid && i_axi_rresp[1]) + ||(i_axi_bvalid && i_axi_bresp[1]) + ||(!i_wb_cyc && !empty)) + flushing <= 1'b1; + else if (empty) + flushing <= 1'b0; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Wishbone input skidbuffer + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + skidbuffer #( + // {{{ + .DW(1+AW+DW+(DW/8)), + .OPT_OUTREG(1'b0) + // }}} + ) skid ( + // {{{ + .i_clk(i_clk), .i_reset(i_reset || !i_wb_cyc), + .i_valid(i_wb_stb), .o_ready(skid_ready), + .i_data({ i_wb_we, i_wb_addr, i_wb_data, i_wb_sel }), + .o_valid(m_valid), .i_ready(m_ready), + .o_data({ m_we, m_addr, m_data, m_sel }) + // }}} + ); + + always @(*) + o_wb_stall = !skid_ready; + + always @(*) + begin + m_ready = 1; + + if (flushing || nearfull || ((m_we != direction)&&(!empty))) + m_ready = 1'b0; + if (o_axi_awvalid && !i_axi_awready) + m_ready = 1'b0; + if (o_axi_wvalid && !i_axi_wready) + m_ready = 1'b0; + if (o_axi_arvalid && !i_axi_arready) + m_ready = 1'b0; + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // AXI Signaling + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Write transactions + // + + // awvalid, wvalid + // {{{ + // Send write transactions + initial o_axi_awvalid = 0; + initial o_axi_wvalid = 0; + always @(posedge i_clk) + if (i_reset) + begin + o_axi_awvalid <= 0; + o_axi_wvalid <= 0; + end else if (m_valid && m_we && m_ready) + begin + o_axi_awvalid <= 1; + o_axi_wvalid <= 1; + end else begin + if (i_axi_awready) + o_axi_awvalid <= 0; + if (i_axi_wready) + o_axi_wvalid <= 0; + end + // }}} + + // wdata + // {{{ + always @(posedge i_clk) + if (!o_axi_wvalid || i_axi_wready) + o_axi_wdata <= {(C_AXI_DATA_WIDTH/DW){m_data}}; + // }}} + + // wstrb + // {{{ + generate if (DW == C_AXI_DATA_WIDTH) + begin : NO_WSTRB_ADJUSTMENT + // {{{ + always @(posedge i_clk) + if (!o_axi_wvalid || i_axi_wready) + o_axi_wstrb <= m_sel; + // }}} + end else if (OPT_LITTLE_ENDIAN) + begin : LITTLE_ENDIAN_WSTRB + // {{{ + always @(posedge i_clk) + if (!o_axi_wvalid || i_axi_wready) + // Verilator lint_off WIDTH + o_axi_wstrb <= m_sel << ((DW/8) * m_addr[SUBW-1:0]); + // Verilator lint_on WIDTH + // }}} + end else begin : BIG_ENDIAN_WSTRB + // {{{ + reg [SUBW-1:0] neg_addr; + + always @(*) + neg_addr = ~m_addr[SUBW-1:0]; + + always @(posedge i_clk) + if (!o_axi_wvalid || i_axi_wready) + // Verilator lint_off WIDTH + o_axi_wstrb <= m_sel << ((DW/8)* neg_addr); + // Verilator lint_on WIDTH + // }}} + end endgenerate + // }}} + + // + // Read transactions + // + + // arvalid + // {{{ + initial o_axi_arvalid = 0; + always @(posedge i_clk) + if (i_reset) + o_axi_arvalid <= 0; + else if (m_valid && !m_we && m_ready) + o_axi_arvalid <= 1; + else if (i_axi_arready) + begin + o_axi_arvalid <= 0; + end + // }}} + + // awaddr, araddr + // {{{ + generate if (OPT_LITTLE_ENDIAN || DW == C_AXI_DATA_WIDTH) + begin : GEN_ADDR_LSBS + // {{{ + always @(posedge i_clk) + if (!o_axi_awvalid || i_axi_awready) + o_axi_awaddr <= { m_addr, {($clog2(DW)-3){1'b0}} }; + + always @(posedge i_clk) + if (!o_axi_arvalid || i_axi_arready) + o_axi_araddr <= { m_addr, {($clog2(DW)-3){1'b0}} }; + // }}} + end else begin : OPT_BIG_ENDIAN + // {{{ + reg [SUBW-1:0] neg_addr; + + always @(*) + neg_addr = ~m_addr[SUBW-1:0]; + + always @(posedge i_clk) + if (!o_axi_awvalid || i_axi_awready) + begin + o_axi_awaddr <= 0; + o_axi_awaddr <= m_addr << ($clog2(DW)-3); + o_axi_awaddr[$clog2(DW)-3 +: SUBW] <= neg_addr; + end + + always @(posedge i_clk) + if (!o_axi_arvalid || i_axi_arready) + begin + o_axi_araddr <= 0; + o_axi_araddr <= m_addr << ($clog2(DW)-3); + o_axi_araddr[$clog2(DW)-3 +: SUBW] <= neg_addr; + end + // }}} + end endgenerate + // }}} + + // rdata, and returned o_wb_data, o_wb_ack, o_wb_err + // {{{ + generate if (DW == C_AXI_DATA_WIDTH) + begin : NO_READ_DATA_SELECT_NECESSARY + // {{{ + always @(*) + o_wb_data = i_axi_rdata; + + always @(*) + o_wb_ack = !flushing&&((i_axi_rvalid && !i_axi_rresp[1]) + ||(i_axi_bvalid && !i_axi_bresp[1])); + + always @(*) + o_wb_err = !flushing&&((i_axi_rvalid && i_axi_rresp[1]) + ||(i_axi_bvalid && i_axi_bresp[1])); + // }}} + end else begin : READ_FIFO_DATA_SELECT + // {{{ + + reg [SUBW-1:0] addr_fifo [0:(1<<LGFIFO)-1]; + reg [SUBW-1:0] fifo_value; + reg [LGFIFO:0] wr_addr, rd_addr; + wire [C_AXI_DATA_WIDTH-1:0] return_data; + + initial o_wb_ack = 0; + always @(posedge i_clk) + if (i_reset || !i_wb_cyc || flushing) + o_wb_ack <= 0; + else + o_wb_ack <= ((i_axi_rvalid && !i_axi_rresp[1]) + ||(i_axi_bvalid && !i_axi_bresp[1])); + + initial o_wb_err = 0; + always @(posedge i_clk) + if (i_reset || !i_wb_cyc || flushing) + o_wb_err <= 0; + else + o_wb_err <= ((i_axi_rvalid && i_axi_rresp[1]) + ||(i_axi_bvalid && i_axi_bresp[1])); + + + initial wr_addr = 0; + always @(posedge i_clk) + if (i_reset) + wr_addr <= 0; + else if (m_valid && m_ready) + wr_addr <= wr_addr + 1; + + always @(posedge i_clk) + if (m_valid && m_ready) + addr_fifo[wr_addr[LGFIFO-1:0]] <= m_addr[SUBW-1:0]; + + initial rd_addr = 0; + always @(posedge i_clk) + if (i_reset) + rd_addr <= 0; + else if (i_axi_bvalid || i_axi_rvalid) + rd_addr <= rd_addr + 1; + + always @(*) + fifo_value = addr_fifo[rd_addr[LGFIFO-1:0]]; + + if (OPT_LITTLE_ENDIAN) + begin : LITTLE_ENDIAN_RDATA + + assign return_data = i_axi_rdata >> (fifo_value * DW); + + end else begin : BIG_ENDIAN_RDATA + + reg [SUBW-1:0] neg_fifo_value; + + always @(*) + neg_fifo_value = ~fifo_value; + + assign return_data = i_axi_rdata + >> (neg_fifo_value * DW); + + end + + always @(posedge i_clk) + o_wb_data <= return_data[DW-1:0]; + + // Make Verilator happy here + // verilator lint_off UNUSED + if (C_AXI_DATA_WIDTH > DW) + begin : UNUSED_DATA + wire unused_data; + assign unused_data = &{ 1'b0, + return_data[C_AXI_DATA_WIDTH-1:DW] }; + end + // verilator lint_on UNUSED +`ifdef FORMAL + always @(*) + assert(wr_addr - rd_addr == npending); + + always @(*) + assert(empty == (wr_addr == rd_addr)); + + + // + // ... + // +`endif + // }}} + end endgenerate + // }}} + + // Read data channel / response logic + assign o_axi_rready = 1'b1; + assign o_axi_bready = 1'b1; + // }}} + + // Make verilator's -Wall happy + // {{{ + // verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, full, i_axi_bid, i_axi_bresp[0], i_axi_rid, i_axi_rresp[0], i_axi_rlast, m_data, m_sel }; + generate if (C_AXI_DATA_WIDTH > DW) + begin : GEN_UNUSED_DW + wire [C_AXI_DATA_WIDTH-1:DW] unused_data; + assign unused_data = i_axi_rdata[C_AXI_DATA_WIDTH-1:DW]; + end endgenerate + // verilator lint_on UNUSED + // }}} + +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +// +// Formal methods section +// {{{ +// Below are a scattering of the formal properties used. They are not the +// complete set of properties. Those are maintained elsewhere. +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +///////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // ... + // + + // Parameters + initial assert( (C_AXI_DATA_WIDTH / DW ==32) + ||(C_AXI_DATA_WIDTH / DW ==16) + ||(C_AXI_DATA_WIDTH / DW == 8) + ||(C_AXI_DATA_WIDTH / DW == 4) + ||(C_AXI_DATA_WIDTH / DW == 2) + ||(C_AXI_DATA_WIDTH == DW)); + // + initial assert( C_AXI_ADDR_WIDTH == AW + (LG_WB_DW-3)); + + + initial begin + assert(C_AXI_DATA_WIDTH >= DW); + assert((DW == 8) == (AW == C_AXI_ADDR_WIDTH)); + assert(C_AXI_ADDR_WIDTH == AW + $clog2(DW)-3); + end + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Setup / f_past_valid + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + initial f_past_valid = 1'b0; + always @(posedge i_clk) + f_past_valid <= 1'b1; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assumptions about the WISHBONE inputs + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + if (!f_past_valid) + assume(i_reset); + + fwb_slave #(.DW(DW),.AW(AW), + .F_MAX_STALL(0), + .F_MAX_ACK_DELAY(0), + .F_LGDEPTH(F_LGDEPTH), + .F_MAX_REQUESTS(0)) + f_wb(i_clk, i_reset, i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, + i_wb_data, i_wb_sel, + o_wb_ack, o_wb_stall, o_wb_data, o_wb_err, + f_wb_nreqs, f_wb_nacks, f_wb_outstanding); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assumptions about the AXI inputs + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + faxi_master #( + // {{{ + .C_AXI_ID_WIDTH(C_AXI_ID_WIDTH), + .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH), + .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH) + // ... + // }}} + ) f_axi(.i_clk(i_clk), .i_axi_reset_n(!i_reset), + // {{{ + // Write address channel + .i_axi_awready(i_axi_awready), + .i_axi_awid( o_axi_awid), + .i_axi_awaddr( o_axi_awaddr), + .i_axi_awlen( o_axi_awlen), + .i_axi_awsize( o_axi_awsize), + .i_axi_awburst(o_axi_awburst), + .i_axi_awlock( o_axi_awlock), + .i_axi_awcache(o_axi_awcache), + .i_axi_awprot( o_axi_awprot), + .i_axi_awqos( o_axi_awqos), + .i_axi_awvalid(o_axi_awvalid), + // Write data channel + .i_axi_wready( i_axi_wready), + .i_axi_wdata( o_axi_wdata), + .i_axi_wstrb( o_axi_wstrb), + .i_axi_wlast( o_axi_wlast), + .i_axi_wvalid( o_axi_wvalid), + // Write response channel + .i_axi_bid( i_axi_bid), + .i_axi_bresp( i_axi_bresp), + .i_axi_bvalid( i_axi_bvalid), + .i_axi_bready( o_axi_bready), + // Read address channel + .i_axi_arready(i_axi_arready), + .i_axi_arid( o_axi_arid), + .i_axi_araddr( o_axi_araddr), + .i_axi_arlen( o_axi_arlen), + .i_axi_arsize( o_axi_arsize), + .i_axi_arburst(o_axi_arburst), + .i_axi_arlock( o_axi_arlock), + .i_axi_arcache(o_axi_arcache), + .i_axi_arprot( o_axi_arprot), + .i_axi_arqos( o_axi_arqos), + .i_axi_arvalid(o_axi_arvalid), + // Read data channel + .i_axi_rid( i_axi_rid), + .i_axi_rresp( i_axi_rresp), + .i_axi_rvalid( i_axi_rvalid), + .i_axi_rdata( i_axi_rdata), + .i_axi_rlast( i_axi_rlast), + .i_axi_rready( o_axi_rready), + // Counts + .f_axi_awr_nbursts(f_axi_awr_nbursts), + .f_axi_wr_pending(f_axi_wr_pending), + .f_axi_rd_nbursts(f_axi_rd_nbursts), + .f_axi_rd_outstanding(f_axi_rd_outstanding) + // + // ... + // + // }}} + ); + + always @(*) + if (!flushing && i_wb_cyc) + assert(f_wb_outstanding == npending + (r_stb ? 1:0) + + ( ((C_AXI_DATA_WIDTH != DW) + && (o_wb_ack|o_wb_err))? 1:0)); + else if (flushing && i_wb_cyc && !o_wb_err) + assert(f_wb_outstanding == (r_stb ? 1:0)); + + always @(*) + if (f_axi_awr_nbursts > 0) + begin + assert(direction); + assert(f_axi_rd_nbursts == 0); + assert(f_axi_awr_nbursts + (o_axi_awvalid ? 1:0) == npending); + assert(f_axi_wr_pending == (o_axi_wvalid&&!o_axi_awvalid ? 1:0)); + + // + // ... + // + end + always @(*) + if (o_axi_awvalid) + assert(o_axi_wvalid); + + // Some quick read checks + always @(*) + if (f_axi_rd_nbursts > 0) + begin + assert(!direction); + assert(f_axi_rd_nbursts+(o_axi_arvalid ? 1:0) + == npending); + assert(f_axi_awr_nbursts == 0); + + // + // ... + // + end + + always @(*) + if (direction) + begin + assert(npending == (o_axi_awvalid ? 1:0) + f_axi_awr_nbursts); + assert(!o_axi_arvalid); + assert(f_axi_rd_nbursts == 0); + assert(!i_axi_rvalid); + end else begin + assert(npending == (o_axi_arvalid ? 1:0) + f_axi_rd_nbursts); + assert(!o_axi_awvalid); + assert(!o_axi_wvalid); + assert(f_axi_awr_nbursts == 0); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Pending counter properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + always @(*) + begin + assert(npending <= { 1'b1, {(LGFIFO){1'b0}} }); + assert(empty == (npending == 0)); + assert(full == (npending == {1'b1, {(LGFIFO){1'b0}} })); + assert(nearfull == (npending >= {1'b0, {(LGFIFO){1'b1}} })); + if (full) + assert(!m_ready); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assertions about the AXI4 ouputs + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // Write response channel + always @(posedge i_clk) + // We keep bready high, so the other condition doesn't + // need to be checked + assert(o_axi_bready); + + // AXI read data channel signals + always @(posedge i_clk) + // We keep o_axi_rready high, so the other condition's + // don't need to be checked here + assert(o_axi_rready); + + // + // AXI write address channel + // + // + always @(*) + begin + if (o_axi_awvalid || o_axi_wvalid || f_axi_awr_nbursts>0) + assert(direction); + // + // ... + // + end + // + // AXI read address channel + // + always @(*) + begin + if (o_axi_arvalid || i_axi_rvalid || f_axi_rd_nbursts > 0) + assert(!direction); + // + // ... + // + end + + // + // AXI write response channel + // + + + // + // AXI read data channel signals + // + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Formal contract check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // Prove that a write to this address will change this value + // + + // Some extra register declarations + // {{{ + (* anyconst *) reg [C_AXI_ADDR_WIDTH-1:0] f_const_addr; + reg [C_AXI_DATA_WIDTH-1:0] f_data; + // }}} + + // + // Assume a basic bus response to the given data and address + // + integer iN; + + // f_data + // {{{ + initial f_data = 0; + always @(posedge i_clk) + if (o_axi_wvalid && i_axi_wready && o_axi_awaddr == f_const_addr) + begin + for(iN=0; iN<C_AXI_DATA_WIDTH/8; iN=iN+1) + begin + if (o_axi_wstrb[iN]) + f_data[8*iN +: 8] <= o_axi_wdata[8*iN +: 8]; + end + end + // }}} + + // Assume RDATA == f_data if appropriate + // {{{ + always @(*) + if (i_axi_rvalid && o_axi_rready && f_axi_rd_ckvalid + && (f_axi_rd_ckaddr == f_const_addr)) + assume(i_axi_rdata == f_data); + // }}} + + // f_wb_addr -- A WB address designed to match f_const_addr (AXI addr) + // {{{ + always @(*) + begin + f_wb_addr = f_const_addr[C_AXI_ADDR_WIDTH-1:DWSIZE]; + if (!OPT_LITTLE_ENDIAN && SUBW > 0) + f_wb_addr[0 +: SUBW] = ~f_wb_addr[0 +: SUBW]; + end + // }}} + + // Assume the address is Wishbone word aligned + // {{{ + generate if (DW > 8) + begin + always @(*) + assume(f_const_addr[$clog2(DW)-4:0] == 0); + end endgenerate + // }}} + + // f_axi_data -- Replicate f_wb_data across the whole word + // {{{ + always @(*) + f_axi_data = {(C_AXI_DATA_WIDTH/DW){f_wb_data}}; + // }}} + + // + // ... + // + + always @(*) + begin + f_valid_wb_response = 1; + for(iN=0; iN<DW/8; iN=iN+1) + begin + if (f_wb_strb[iN] && (o_wb_data[iN*8 +: 8] != f_wb_data[iN*8 +: 8])) + f_valid_wb_response = 0; + end + end + // }}} + + // f_valid_axi_data + // {{{ + always @(*) + begin + f_valid_axi_data = 1; + for(iN=0; iN<C_AXI_DATA_WIDTH/8; iN=iN+1) + begin + if (f_axi_strb[iN] && (f_axi_data[iN*8 +: 8] != f_data[iN*8 +: 8])) + f_valid_axi_data = 0; + end + end + // }}} + + // f_valid_axi_response + // {{{ + always @(*) + begin + f_valid_axi_response = 1; + for(iN=0; iN<C_AXI_DATA_WIDTH/8; iN=iN+1) + begin + if (f_axi_strb[iN] && (i_axi_rdata[iN*8 +: 8] != f_data[iN*8 +: 8])) + f_valid_axi_response = 0; + end + end + // }}} + + // + // ... + // + + generate if (DW == C_AXI_DATA_WIDTH) + begin + + always @(*) + f_axi_strb = f_wb_strb; + + end else if (OPT_LITTLE_ENDIAN) + begin + + always @(*) + f_axi_strb <= f_wb_strb << ( (DW/8) * + f_wb_addr[SUBW-1:0]); + + end else // if (!OPT_LITTLE_ENDIAN) + begin + reg [SUBW-1:0] f_neg_addr; + + always @(*) + f_neg_addr = ~f_wb_addr[SUBW-1:0]; + + always @(*) + f_axi_strb <= f_wb_strb << ( (DW/8) * f_neg_addr ); + + end endgenerate + // }}} + + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Ad-hoc assertions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + generate if (DW > 8) + begin + always @(*) + if (o_axi_awvalid) + assert(o_axi_awaddr[$clog2(DW)-4:0] == 0); + + always @(*) + if (o_axi_arvalid) + assert(o_axi_araddr[$clog2(DW)-4:0] == 0); + + end endgenerate + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [F_LGDEPTH-1:0] r_hit_reads, r_hit_writes, + r_check_fault, check_fault, + cvr_nreads, cvr_nwrites; + reg cvr_flushed, cvr_read2write, cvr_write2read; + + initial r_hit_reads = 0; + always @(posedge i_clk) + if (i_reset) + r_hit_writes <= 0; + else if (f_axi_awr_nbursts > 3) + r_hit_writes <= 1; + + initial r_hit_reads = 0; + always @(posedge i_clk) + if (i_reset) + r_hit_reads <= 0; + else if (f_axi_rd_nbursts > 3) + r_hit_reads <= 1; + + always @(*) + begin + check_fault = 0; + if (!i_wb_cyc && o_axi_awvalid) + check_fault = 1; + if (!i_wb_cyc && o_axi_wvalid) + check_fault = 1; + if (!i_wb_cyc && f_axi_awr_nbursts > 0) + check_fault = 1; + if (!i_wb_cyc && i_axi_bvalid) + check_fault = 1; + // + if (!i_wb_cyc && o_axi_arvalid) + check_fault = 1; + if (!i_wb_cyc && f_axi_rd_outstanding > 0) + check_fault = 1; + if (!i_wb_cyc && i_axi_rvalid) + check_fault = 1; + if (!i_wb_cyc && (o_wb_ack | o_wb_err)) + check_fault = 1; + + if (flushing) + check_fault = 1; + end + + initial r_check_fault = 0; + always @(posedge i_clk) + if (i_reset) + r_check_fault <= 0; + else if (check_fault) + r_check_fault <= 1; + + always @(*) + cover(r_hit_writes && r_hit_reads && f_axi_rd_nbursts == 0 + && f_axi_awr_nbursts == 0 + && !o_axi_awvalid && !o_axi_arvalid && !o_axi_wvalid + && !i_axi_bvalid && !i_axi_rvalid + && !o_wb_ack && !o_wb_stall && !i_wb_stb + && !check_fault && !r_check_fault); + + // + // ... + // + + initial cvr_flushed = 1'b0; + always @(posedge i_clk) + if (i_reset) + cvr_flushed <= 1'b0; + else if (flushing) + cvr_flushed <= 1'b1; + + always @(*) + begin + cover(!i_reset && cvr_flushed && !flushing); + cover(!i_reset && cvr_flushed && !flushing && !o_wb_stall); + end + + // + // Let's cover our ability to turn around, from reads to writes or from + // writes to reads. + // + // Note that without the RMW option above, switching direction requires + // dropping i_wb_cyc. Let's just make certain here, that if we do so, + // we don't drop it until after all of the returns come back. + // + initial cvr_read2write = 0; + always @(posedge i_clk) + if (i_reset || (!i_wb_cyc && f_wb_nreqs != f_wb_nacks)) + cvr_read2write <= 0; + else if (!direction && !empty && m_we) + cvr_read2write <= 1; + + initial cvr_write2read = 0; + always @(posedge i_clk) + if (i_reset || (!i_wb_cyc && f_wb_nreqs != f_wb_nacks)) + cvr_write2read <= 0; + else if (direction && !empty && !m_we) + cvr_write2read <= 1; + + always @(*) + begin + cover(cvr_read2write && direction && o_wb_ack && f_wb_outstanding == 1); + cover(cvr_write2read && !direction && o_wb_ack && f_wb_outstanding == 1); + end + + reg [2:0] cvr_ack_after_abort; + + initial cvr_ack_after_abort = 0; + always @(posedge i_clk) + if (i_reset) + cvr_ack_after_abort <= 0; + else begin + if (!i_wb_cyc) + cvr_ack_after_abort[2:0] <= (empty) ? 0 : 3'b01; + if (cvr_ack_after_abort[0] && i_wb_cyc && r_stb && flushing) + cvr_ack_after_abort[1] <= 1; + if (o_wb_ack && &cvr_ack_after_abort[1:0]) + cvr_ack_after_abort[2] <= 1; + end + + always @(*) + cover(&cvr_ack_after_abort[1:0]); + always @(*) + cover(!flushing && (&cvr_ack_after_abort[1:0])); + always @(*) + cover(&cvr_ack_after_abort[2:0]); + always @(*) + cover(!i_wb_cyc && &cvr_ack_after_abort[2:0]); + + initial cvr_nwrites = 0; + always @(posedge i_clk) + if (i_reset || flushing || !i_wb_cyc || !i_wb_we || o_wb_err) + cvr_nwrites <= 0; + else if (i_axi_bvalid && o_axi_bready) + cvr_nwrites <= cvr_nwrites + 1; + + initial cvr_nreads = 0; + always @(posedge i_clk) + if (i_reset || flushing || !i_wb_cyc || i_wb_we || o_wb_err) + cvr_nreads <= 0; + else if (i_axi_rvalid && o_axi_rready) + cvr_nreads <= cvr_nreads + 1; + + always @(*) + cover(cvr_nwrites == 3 && !o_wb_ack && !o_wb_err && !i_wb_cyc); + + always @(*) + cover(cvr_nreads == 3 && !o_wb_ack && !o_wb_err && !i_wb_cyc); + + // + // Generate a cover that doesn't include an abort + // {{{ + (* anyconst *) reg f_never_abort; + + always @(*) + if (f_never_abort && f_wb_nacks != f_wb_nreqs) + assume(!i_reset && i_wb_cyc && !o_wb_err); + + always @(posedge i_clk) + if (f_never_abort && $past(o_wb_ack) && o_wb_ack) + assume($changed(o_wb_data)); + + always @(*) + cover(cvr_nreads == 3 && !o_wb_ack && !o_wb_err && !i_wb_cyc + && f_never_abort); + // }}} + + // }}} +`endif // FORMAL +// }}} +endmodule diff --git a/rtl/wb2axip/wbp2classic.v b/rtl/wb2axip/wbp2classic.v new file mode 100644 index 0000000..250489b --- /dev/null +++ b/rtl/wb2axip/wbp2classic.v @@ -0,0 +1,205 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbp2classic.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: Takes a WB pipelined connection from a master, and converts it +// to WB classic for the slave. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbp2classic #( + // {{{ + parameter AW = 12, + DW = 32 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // + // Incoming WB pipelined port + input wire i_scyc, i_sstb, i_swe, + input wire [AW-1:0] i_saddr, + input wire [DW-1:0] i_sdata, + input wire [DW/8-1:0] i_ssel, + output reg o_sstall, o_sack, + output reg [DW-1:0] o_sdata, + output reg o_serr, + // + // Outgoing WB classic port + output reg o_mcyc, o_mstb, o_mwe, + output reg [AW-1:0] o_maddr, + output reg [DW-1:0] o_mdata, + output reg [DW/8-1:0] o_msel, + input wire i_mack, + input wire [DW-1:0] i_mdata, + input wire i_merr, + // Extra wires, not necessarily necessary for WB/B3 + output reg [2:0] o_mcti, + output reg [1:0] o_mbte + // }}} + ); + + // + // returned = whether we've received our return value or not. + reg returned; + + // Combinatorial values forwarded downstream + // {{{ + always @(*) + begin + o_mcyc = i_scyc; + o_mstb = i_sstb && !returned; + o_mwe = i_swe; + o_maddr = i_saddr; + o_mdata = i_sdata; + o_msel = i_ssel; + + // Cycle type indicator: Classic + o_mcti = 3'b000; + // Burst type indicator--ignored for single transaction cycle + // types, such as the classic type above + o_mbte = 2'b00; // Linear burst + end + // }}} + + // returned + // {{{ + initial returned = 0; + always @(posedge i_clk) + if (i_reset) + returned <= 0; + else if (!i_sstb || returned) + returned <= 0; + else if (i_mack || i_merr) + returned <= 1; + // }}} + + // o_sstall + // {{{ + always @(*) + o_sstall = !returned; + // }}} + + // o_sack, o_serr + // {{{ + initial o_sack = 0; + initial o_serr = 0; + always @(posedge i_clk) + if (i_reset) + begin + o_sack <= 0; + o_serr <= 0; + end else begin + o_sack <= (i_scyc) && i_mack; + o_serr <= (i_scyc) && i_merr; + end + // }}} + + // o_mdata + // {{{ + always @(posedge i_clk) + if (i_mack || i_merr) + o_sdata <= i_mdata; + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + //////////////////////////////////////////////////////////////////////// + // + // + // + //////////////////////////////////////////////////////////////////////// + // + // + localparam F_LGDEPTH = 1; + reg f_past_valid; + reg f_ongoing; + reg [F_LGDEPTH-1:0] f_nreqs, f_nacks, f_outstanding; + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid = 1; + + always @(*) + if (!f_past_valid) + assume(i_reset); + + //////////////////////////////////////////////////////////////////////// + // + // Upstream Wishbone pipeline slave properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + fwb_slave #(.AW(AW), .DW(DW), + .F_MAX_STALL(4), + .F_MAX_ACK_DELAY(15), + .F_LGDEPTH(1)) incoming (i_clk, i_reset, + i_scyc, i_sstb, i_swe, i_saddr, i_sdata, i_ssel, + o_sack, o_sstall, o_sdata, o_serr, + f_nreqs, f_nacks, f_outstanding); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Downstream Wishbone classic master properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + fwbc_master #(.AW(AW), .DW(DW), .F_MAX_DELAY(3)) + classic (i_clk, i_reset, + o_mcyc, o_mstb, o_mwe, o_maddr, o_mdata, o_msel, o_mcti, o_mbte, + i_mack, i_mdata, i_merr, 1'b0); + + // }}} + + // + // Disallow bus aborts + always @(posedge i_clk) + f_ongoing <= (!i_reset && i_sstb && !(o_sack | o_serr)); + + always @(*) + if (f_ongoing) + assume(i_sstb); +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/wbsafety.v b/rtl/wb2axip/wbsafety.v new file mode 100644 index 0000000..2636f11 --- /dev/null +++ b/rtl/wb2axip/wbsafety.v @@ -0,0 +1,587 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbsafety.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A WB bus fault isolator. This core will isolate any downstream +// WB slave faults from the upstream channel. It sits as a bump +// in the wire between upstream and downstream channels, and so it will +// consume two clocks--slowing down the slave, but potentially allowing +// the developer to recover in case of a fault. +// +// This core is configured by a couple parameters, which are key to its +// functionality. +// +// OPT_TIMEOUT Set this to a number to be roughly the longest time +// period you expect the slave to stall the bus, or likewise +// the longest time period you expect it to wait for a response. +// If the slave takes longer for either task, a fault will be +// detected and reported. +// +// OPT_SELF_RESET If set, this will send a reset signal to the downstream +// core so that you can attempt to restart it without reloading +// the FPGA. If set, the o_reset signal will be used to reset +// the downstream core. +// +// A second key feature of this core is the outgoing fault detector, +// o_fault. If this signal is ever raised, the slave has (somehow) +// violated protocol. Such a violation may (or may not) return an +// error upstream. For example, if the slave returns a response +// following no requests from the master, then no error will be returned +// up stream (doing so would be a protocol violation), but a fault will +// be detected. Use this line to trigger any internal logic analyzers. +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbsafety #( + // {{{ + parameter AW = 28, DW = 32, + parameter OPT_TIMEOUT = 12, + parameter MAX_DEPTH = (OPT_TIMEOUT), + parameter [0:0] OPT_SELF_RESET = 1'b1, + parameter [0:0] F_OPT_FAULTLESS = 1'b1 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // + // The incoming WB interface from the (trusted) master + // {{{ + // This interface is guaranteed to follow the protocol. + input wire i_wb_cyc, i_wb_stb, i_wb_we, + input wire [AW-1:0] i_wb_addr, + input wire [DW-1:0] i_wb_data, + input wire [DW/8-1:0] i_wb_sel, + output reg o_wb_stall, o_wb_ack, + output reg [DW-1:0] o_wb_idata, + output reg o_wb_err, + // }}} + // + // The outgoing interface to the untrusted slave + // {{{ + // This interface may or may not follow the WB protocol + output reg o_reset, + output reg o_wb_cyc, o_wb_stb, o_wb_we, + output reg [AW-1:0] o_wb_addr, + output reg [DW-1:0] o_wb_data, + output reg [DW/8-1:0] o_wb_sel, + input wire i_wb_stall, i_wb_ack, + input wire [DW-1:0] i_wb_idata, + input wire i_wb_err, + // }}} + // + // The fault signal, indicating the downstream slave was + // misbehaving + output reg o_fault + // }}} + ); + + // Declarations + // {{{ + localparam LGTIMEOUT = $clog2(OPT_TIMEOUT+1); + localparam LGDEPTH = $clog2(MAX_DEPTH+1); + reg none_expected; + reg [LGDEPTH-1:0] expected_returns; + reg [LGTIMEOUT-1:0] stall_timer, wait_timer; + reg timeout; + reg faulty_return; + + wire skd_stb, skd_o_ready, skd_we; + reg skd_stall; + wire [AW-1:0] skd_addr; + wire [DW-1:0] skd_data; + wire [DW/8-1:0] skd_sel; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Start with a skid buffer on all incoming signals + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // +`ifdef FORMAL + // {{{ + // We know the skid buffer works. It's irrelevant to our proof. + // Therefore, remove it during formal testing, lest we need to + // check it as well. Further, we make this a parameter--but only + // when FORMAL is defined--so that it may be overridden in that case. + // + parameter [0:0] SKID_PASSTHROUGH = 1'b1; +`else + localparam [0:0] SKID_PASSTHROUGH = 1'b0; + // }}} +`endif + + skidbuffer #( + // {{{ + .DW(1+AW+DW+(DW/8)), + .OPT_PASSTHROUGH(SKID_PASSTHROUGH) + // }}} + ) skd( + // {{{ + .i_clk(i_clk), .i_reset(i_reset || !i_wb_cyc), + .i_valid(i_wb_stb), .o_ready(skd_o_ready), + .i_data({ i_wb_we, i_wb_addr, i_wb_data, i_wb_sel }), + .o_valid(skd_stb), .i_ready(!skd_stall), + .o_data({ skd_we, skd_addr, skd_data, skd_sel }) + // }}} + ); + + always @(*) + o_wb_stall = !skd_o_ready; + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Timeout checking + // + + + // + // Insist on a maximum number of downstream stalls + // + initial stall_timer = 0; + always @(posedge i_clk) + if (!i_reset && o_wb_stb && i_wb_stall) + begin + if (stall_timer <= OPT_TIMEOUT) + stall_timer <= stall_timer + 1; + end else + stall_timer <= 0; + + // + // Insist on a maximum number cyles waiting for an acknowledgment + // + initial wait_timer = 0; + always @(posedge i_clk) + if (!i_reset && o_wb_cyc && !o_wb_stb && !i_wb_ack && !i_wb_err + && !none_expected) + begin + if (wait_timer <= OPT_TIMEOUT) + wait_timer <= wait_timer + 1; + end else + wait_timer <= 0; + + // + // Generate a timeout signal on any error + // + initial timeout = 0; + always @(posedge i_clk) + if (timeout && o_wb_err) + timeout <= 0; + else + timeout <= (i_wb_stall)&&(stall_timer >= OPT_TIMEOUT) + || ((!i_wb_ack && !i_wb_err)&&(wait_timer >= OPT_TIMEOUT)); + + //////////////////////////////////////////////////////////////////////// + // + // Return counting + // {{{ + + initial none_expected = 1; + initial expected_returns = 0; + always @(posedge i_clk) + if (i_reset || o_reset || o_wb_err || !i_wb_cyc) + begin + expected_returns <= 0; + none_expected <= 1; + end else case({skd_stb && !skd_stall, o_wb_ack }) + 2'b10: begin + expected_returns <= expected_returns + 1; + none_expected <= 1'b0; + end + 2'b01: begin + expected_returns <= expected_returns - 1; + none_expected <= (expected_returns == 1); + end + default: begin end + endcase + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Downstream reset generation + // {{{ + generate if (OPT_SELF_RESET) + begin : SELF_RESET + + initial o_reset = 1; + always @(posedge i_clk) + if (i_reset || o_fault) + o_reset <= 1; + else begin + o_reset <= 0; + if (o_wb_cyc && none_expected + &&(i_wb_ack || i_wb_err)) + o_reset <= 1; + if (timeout) + o_reset <= 1; + end + end else begin : FORWARD_RESET + + always @(*) + o_reset = i_reset || o_fault; + + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Fault detection + // {{{ + + // faulty_return + // {{{ + // A faulty return is a response from the slave at a time, or in + // a fashion that it unexpected and violates protocol + // + always @(*) + begin + faulty_return = 0; + if (expected_returns <= ((o_wb_stb && i_wb_stall) ? 1:0) + + ((o_wb_ack || o_wb_err) ? 1:0)) + faulty_return = i_wb_ack || i_wb_err; + if (i_wb_ack && i_wb_err) + faulty_return = 1; + if (!i_wb_cyc || !o_wb_cyc) + faulty_return = 0; + end + // }}} + + // o_fault + // {{{ + initial o_fault = 0; + always @(posedge i_clk) + if (o_reset && !i_wb_cyc) + o_fault <= 0; + else begin + if (o_wb_cyc && faulty_return) + o_fault <= 1; + if (i_wb_cyc && timeout) + o_fault <= 1; + end + // }}} + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Downstream bus signal generation + // + + // o_wb_cyc + // {{{ + initial o_wb_cyc = 1'b0; + always @(posedge i_clk) + if (i_reset || (o_wb_cyc && i_wb_err) || o_reset || o_fault + || (i_wb_cyc && o_wb_err)) + o_wb_cyc <= 1'b0; + else + o_wb_cyc <= i_wb_cyc && (o_wb_cyc || i_wb_stb); + // }}} + + // o_wb_stb + // {{{ + initial o_wb_stb = 1'b0; + always @(posedge i_clk) + if (i_reset || (o_wb_cyc && i_wb_err) || o_reset || o_fault || !i_wb_cyc + || (i_wb_cyc && o_wb_err)) + o_wb_stb <= 1'b0; + else if (!o_wb_stb || !i_wb_stall) + o_wb_stb <= skd_stb; + // }}} + + // o_wb_we, o_wb_addr, o_wb_data, o_wb_sel + // {{{ + always @(posedge i_clk) + if (!o_wb_stb || !i_wb_stall) + begin + o_wb_we <= skd_we; + o_wb_addr <= skd_addr; + o_wb_data <= skd_data; + o_wb_sel <= skd_sel; + end + // }}} + + // o_wb_idata + // {{{ + always @(posedge i_clk) + o_wb_idata <= i_wb_idata; + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Return signal generation + // + + // skd_stall + // {{{ + always @(*) + begin + skd_stall = (o_wb_stb && i_wb_stall); + if (i_reset) + skd_stall = 1'b1; + if (o_fault) + skd_stall = 1'b0; + else if (o_reset) + skd_stall = 1'b1; + end + // }}} + + // o_wb_ack, o_wb_err + // {{{ + initial o_wb_ack = 0; + initial o_wb_err = 0; + always @(posedge i_clk) + if (i_reset || !i_wb_cyc) + begin + o_wb_ack <= 1'b0; + o_wb_err <= 1'b0; + end else if (!o_reset && !o_fault) + begin + if (timeout || faulty_return || i_wb_err) + begin + o_wb_ack <= 1'b0; + o_wb_err <= (expected_returns > ((o_wb_ack||o_wb_err) ? 1:0)); + end else begin + o_wb_ack <= o_wb_cyc && i_wb_ack && !i_wb_err; + o_wb_err <= 1'b0; + end + end else begin + o_wb_ack <= 1'b0; + o_wb_err <= (i_wb_stb && !skd_stall); + end + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, F_OPT_FAULTLESS }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal property section +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // + // The following proof comes in several parts. + // + // 1. PROVE that the upstream properties will hold independent of + // what the downstream slave ever does. + // + // 2. PROVE that if the downstream slave follows protocol, then + // o_fault will never get raised. + // + // We then repeat these proofs again with both OPT_SELF_RESET set and + // clear. Which of the four proofs is accomplished is dependent upon + // parameters set by the formal engine. + // + // + localparam DOWNSTREAM_ACK_DELAY = OPT_TIMEOUT/2-1; + localparam UPSTREAM_ACK_DELAY = OPT_TIMEOUT + 3; + wire [LGDEPTH-1:0] fwbs_nreqs, fwbs_nacks, fwbs_outstanding; + + reg f_past_valid; + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid <= 1; + + //////////////////////////////////////////////////////////////////////// + // + // Upstream master Bus properties + // + always @(*) + if (!f_past_valid) + assume(i_reset); + + fwb_slave #( + // {{{ + .AW(AW), .DW(DW), + // .F_MAX_ACK_DELAY(UPSTREAM_ACK_DELAY), + .F_LGDEPTH(LGDEPTH), + .F_OPT_DISCONTINUOUS(1), + .F_OPT_MINCLOCK_DELAY(0) + // }}} + ) wbs ( + // {{{ + i_clk, i_reset, + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel, + o_wb_ack, o_wb_stall, o_wb_idata, o_wb_err, + fwbs_nreqs, fwbs_nacks, fwbs_outstanding + // }}} + ); + + always @(*) + assert(none_expected == (expected_returns == 0)); + + // Just so we pass the skid buffer's assumptions ... + always @(posedge i_clk) + if (f_past_valid && $past(i_wb_stb && o_wb_stall)) + assume($stable(i_wb_data)); + + always @(*) + if (i_wb_cyc && !o_wb_err && !o_fault) + assert(expected_returns == fwbs_outstanding); + + generate if (F_OPT_FAULTLESS) + begin : FAULTLESS_PROPERTIES + // {{{ + //////////////////////////////////////////////////////////////// + // + // Assume the downstream core is protocol compliant, and + // prove that o_fault stays low. + // + wire [LGDEPTH-1:0] fwbm_nreqs, fwbm_nacks,fwbm_outstanding; + reg [LGDEPTH-1:0] mreqs, sacks; + + + fwb_master #( + // {{{ + .AW(AW), .DW(DW), + .F_MAX_ACK_DELAY(DOWNSTREAM_ACK_DELAY), + .F_MAX_STALL(DOWNSTREAM_ACK_DELAY), + .F_LGDEPTH(LGDEPTH), + .F_OPT_DISCONTINUOUS(1), + .F_OPT_MINCLOCK_DELAY(0) + // }}} + ) wbm ( + // {{{ + i_clk, o_reset, + o_wb_cyc, o_wb_stb, o_wb_we, o_wb_addr, o_wb_data, + o_wb_sel, + i_wb_ack, i_wb_stall, i_wb_idata, i_wb_err, + fwbm_nreqs, fwbm_nacks, fwbm_outstanding + // }}} + ); + + // + // Here's the big proof + always @(*) + assert(!o_fault); + + //////////////////////////////////////////////////////////////// + // + // The following properties are necessary for passing induction + // + always @(*) + if (!i_reset && i_wb_cyc && o_wb_cyc) + assert(expected_returns == fwbm_outstanding + + (o_wb_stb ? 1:0) + + ((o_wb_ack|o_wb_err) ? 1:0)); + + always @(*) + assert(!timeout); + + always @(*) + if (o_wb_err) + assert(!o_wb_cyc); + + always @(*) + sacks = fwbs_nacks + (o_wb_ack ? 1:0); + + always @(*) + if (!o_wb_err && i_wb_cyc && o_wb_cyc) + assert(sacks == fwbm_nacks); + + always @(posedge i_clk) + if (!i_reset && i_wb_cyc && expected_returns > 0) + assert(o_wb_cyc || o_wb_err); + + always @(*) + mreqs = fwbm_nreqs + (o_wb_stb ? 1:0); + + always @(*) + if (!o_wb_err && i_wb_cyc && o_wb_cyc) + assert(fwbs_nreqs == mreqs); + + always @(*) + if (i_wb_cyc && o_wb_cyc && fwbs_outstanding > 0) + assert(i_wb_we == o_wb_we); + + always @(*) + if (fwbs_nacks != 0 && i_wb_cyc) + assert(o_wb_cyc || o_wb_err); + // }}} + end else begin + // {{{ + //////////////////////////////////////////////////////////////// + // + // cover() checks, checks that only make sense if faults are + // possible + // + + always @(posedge i_clk) + cover(o_fault); + + always @(posedge i_clk) + if (f_past_valid && $past(faulty_return)) + cover(o_fault); + + always @(posedge i_clk) + if (f_past_valid && $past(timeout)) + cover(o_fault); + + if (OPT_SELF_RESET) + begin + //////////////////////////////////////////////////////// + // + // Prove that we can actually reset the downstream + // bus/core as desired + // + reg faulted; + + initial faulted = 0; + always @(posedge i_clk) + if (i_reset) + faulted <= 0; + else if (o_fault) + faulted <= 1; + + + always @(posedge i_clk) + cover(faulted && $fell(o_reset)); + + always @(posedge i_clk) + cover(faulted && !o_reset && o_wb_ack); + + end + // }}} + end endgenerate + + always @(*) + cover(!i_reset && fwbs_nacks > 4); + +`endif +// }}} +endmodule diff --git a/rtl/wb2axip/wbxbar.v b/rtl/wb2axip/wbxbar.v new file mode 100644 index 0000000..15d6587 --- /dev/null +++ b/rtl/wb2axip/wbxbar.v @@ -0,0 +1,1790 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: wbxbar.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: A Configurable wishbone cross-bar interconnect, conforming +// to the WB-B4 pipeline specification, as described on the +// ZipCPU blog. +// +// Performance: +// Throughput: One transaction per clock +// Latency: One clock to get access to an unused channel, another to +// place the results on the slave bus, and another to return, or a minimum +// of three clocks. +// +// Usage: To use, you'll need to set NM and NS to the number of masters +// (input ports) and the number of slaves respectively. You'll then +// want to set the addresses for the slaves in the SLAVE_ADDR array, +// together with the SLAVE_MASK array indicating which SLAVE_ADDRs +// are valid. Address and data widths should be adjusted at the same +// time. +// +// Voila, you are now set up! +// +// Now let's fine tune this: +// +// LGMAXBURST can be set to control the maximum number of outstanding +// transactions. An LGMAXBURST of 6 will allow 63 outstanding +// transactions. +// +// OPT_TIMEOUT, if set to a non-zero value, is a number of clock periods +// to wait for a slave to respond. Should the timeout expire and the +// slave not respond, a bus error will be returned and the slave will +// be issued a bus abort signal (CYC will be dropped). +// +// OPT_STARVATION_TIMEOUT, if set, applies the OPT_TIMEOUT counter to +// how long a particular master waits for arbitration. If the master is +// "starved", a bus error will be returned. +// +// OPT_DBLBUFFER is used to increase clock speed by registering all +// outputs. +// +// OPT_LOWPOWER is an experimental feature that, if set, will cause any +// unused FFs to be set to zero rather than flopping in the electronic +// wind, in an effort to minimize transitions over bus wires. This will +// cost some extra logic, for ... an uncertain power savings. +// +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2019-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +`default_nettype none +// }}} +module wbxbar #( + // {{{ + parameter NM = 4, NS=8, + parameter AW = 32, DW=32, + parameter [NS*AW-1:0] SLAVE_ADDR = { + { 3'b111, {(AW-3){1'b0}} }, + { 3'b110, {(AW-3){1'b0}} }, + { 3'b101, {(AW-3){1'b0}} }, + { 3'b100, {(AW-3){1'b0}} }, + { 3'b011, {(AW-3){1'b0}} }, + { 3'b010, {(AW-3){1'b0}} }, + { 4'b0010, {(AW-4){1'b0}} }, + { 4'b0000, {(AW-4){1'b0}} } }, + parameter [NS*AW-1:0] SLAVE_MASK = (NS <= 1) ? 0 + : { {(NS-2){ 3'b111, {(AW-3){1'b0}} }}, + {(2){ 4'b1111, {(AW-4){1'b0}} }} }, + // + // LGMAXBURST is the log_2 of the length of the longest burst + // that might be seen. It's used to set the size of the + // internal counters that are used to make certain that the + // cross bar doesn't switch while still waiting on a response. + parameter LGMAXBURST=6, + // + // OPT_TIMEOUT is used to help recover from a misbehaving slave. + // If set, this value will determine the number of clock cycles + // to wait for a misbehaving slave before returning a bus error. + // Alternatively, if set to zero, this functionality will be + // removed. + parameter OPT_TIMEOUT = 0, // 1023; + // + // If OPT_TIMEOUT is set, then OPT_STARVATION_TIMEOUT may also + // be set. The starvation timeout adds to the bus error timeout + // generation the possibility that a master will wait + // OPT_TIMEOUT counts without receiving the bus. This may be + // the case, for example, if one bus master is consuming a + // peripheral to such an extent that there's no time/room for + // another bus master to use it. In that case, when the timeout + // runs out, the waiting bus master will be given a bus error. + parameter [0:0] OPT_STARVATION_TIMEOUT = 1'b0 + && (OPT_TIMEOUT > 0), + // + // OPT_DBLBUFFER is used to register all of the outputs, and + // thus avoid adding additional combinational latency through + // the core that might require a slower clock speed. + parameter [0:0] OPT_DBLBUFFER = 1'b0, + // + // OPT_LOWPOWER adds logic to try to force unused values to + // zero, rather than to allow a variety of logic optimizations + // that could be used to reduce the logic count of the device. + // Hence, OPT_LOWPOWER will use more logic, but it won't drive + // bus wires unless there's a value to drive onto them. + parameter [0:0] OPT_LOWPOWER = 1'b1 + // }}} + ) ( + // {{{ + input wire i_clk, i_reset, + // + // Here are the bus inputs from each of the WB bus masters + input wire [NM-1:0] i_mcyc, i_mstb, i_mwe, + input wire [NM*AW-1:0] i_maddr, + input wire [NM*DW-1:0] i_mdata, + input wire [NM*DW/8-1:0] i_msel, + // + // .... and their return data + output wire [NM-1:0] o_mstall, + output wire [NM-1:0] o_mack, + output reg [NM*DW-1:0] o_mdata, + output wire [NM-1:0] o_merr, + // + // + // Here are the output ports, used to control each of the + // various slave ports that we are connected to + output reg [NS-1:0] o_scyc, o_sstb, o_swe, + output reg [NS*AW-1:0] o_saddr, + output reg [NS*DW-1:0] o_sdata, + output reg [NS*DW/8-1:0] o_ssel, + // + // ... and their return data back to us. + input wire [NS-1:0] i_sstall, i_sack, + input wire [NS*DW-1:0] i_sdata, + input wire [NS-1:0] i_serr + // }}} + ); + // + // + //////////////////////////////////////////////////////////////////////// + // + // Register declarations + // {{{ + // + // TIMEOUT_WIDTH is the number of bits in counter used to check + // on a timeout. + localparam TIMEOUT_WIDTH = $clog2(OPT_TIMEOUT); + // + // LGNM is the log (base two) of the number of bus masters + // connecting to this crossbar + localparam LGNM = (NM>1) ? $clog2(NM) : 1; + // + // LGNS is the log (base two) of the number of slaves plus one + // come out of the system. The extra "plus one" is used for a + // pseudo slave representing the case where the given address + // doesn't connect to any of the slaves. This address will + // generate a bus error. + localparam LGNS = $clog2(NS+1); + // At one time I used o_macc and o_sacc to put into the outgoing + // trace file, just enough logic to tell me if a transaction was + // taking place on the given clock. + // + // assign o_macc = (i_mstb & ~o_mstall); + // assign o_sacc = (o_sstb & ~i_sstall); + // + // These definitions work with Veri1ator, just not with Yosys + // reg [NM-1:0][NS:0] request; + // reg [NM-1:0][NS-1:0] requested; + // reg [NM-1:0][NS:0] grant; + // + // These definitions work with both + wire [NS:0] request [0:NM-1]; + reg [NS-1:0] requested [0:NM-1]; + reg [NS:0] grant [0:NM-1]; + reg [NM-1:0] mgrant; + reg [NS-1:0] sgrant; + + // Verilator lint_off UNUSED + wire [LGMAXBURST-1:0] w_mpending [0:NM-1]; + // Verilator lint_on UNUSED + reg [NM-1:0] mfull, mnearfull, mempty; + wire [NM-1:0] timed_out; + + localparam NMFULL = (NM > 1) ? (1<<LGNM) : 1; + localparam NSFULL = (1<<LGNS); + + wire [LGNS-1:0] mindex [0:NMFULL-1]; + wire [LGNM-1:0] sindex [0:NSFULL-1]; + + wire [NMFULL-1:0] m_cyc; + wire [NMFULL-1:0] m_stb; + wire [NMFULL-1:0] m_we; + wire [AW-1:0] m_addr [0:NMFULL-1]; + wire [DW-1:0] m_data [0:NMFULL-1]; + wire [DW/8-1:0] m_sel [0:NMFULL-1]; + reg [NM-1:0] m_stall; + // + wire [NSFULL-1:0] s_stall; + wire [DW-1:0] s_data [0:NSFULL-1]; + wire [NSFULL-1:0] s_ack; + wire [NSFULL-1:0] s_err; + wire [NM-1:0] dcd_stb; + + localparam [0:0] OPT_BUFFER_DECODER=(NS != 1 || SLAVE_MASK != 0); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Incoming signal arbitration + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + genvar N, M; + integer iN, iM; + generate for(N=0; N<NM; N=N+1) + begin : DECODE_REQUEST + // {{{ + // Register declarations + // {{{ + wire skd_stb, skd_stall; + wire skd_we; + wire [AW-1:0] skd_addr; + wire [DW-1:0] skd_data; + wire [DW/8-1:0] skd_sel; + wire [NS:0] decoded; + wire iskd_ready; + // }}} + + skidbuffer #( + // {{{ + // Can't run OPT_LOWPOWER here, less we mess up the + // consistency in skd_we following + // + // .OPT_LOWPOWER(OPT_LOWPOWER), + .DW(1+AW+DW+DW/8), +`ifdef FORMAL + .OPT_PASSTHROUGH(1), +`endif + .OPT_OUTREG(0) + // }}} + ) iskid ( + // {{{ + .i_clk(i_clk), + .i_reset(i_reset || !i_mcyc[N]), + .i_valid(i_mstb[N]), .o_ready(iskd_ready), + .i_data({ i_mwe[N], i_maddr[N*AW +: AW], + i_mdata[N*DW +: DW], + i_msel[N*DW/8 +: DW/8] }), + .o_valid(skd_stb), .i_ready(!skd_stall), + .o_data({ skd_we, skd_addr, skd_data, skd_sel }) + // }}} + ); + + assign o_mstall[N] = !iskd_ready; + + addrdecode #( + // {{{ + // Can't run OPT_LOWPOWER here, less we mess up the + // consistency in m_we following + // + // .OPT_LOWPOWER(OPT_LOWPOWER), + .NS(NS), .AW(AW), .DW(DW+DW/8+1), + .SLAVE_ADDR(SLAVE_ADDR), + .SLAVE_MASK(SLAVE_MASK), + .OPT_REGISTERED(OPT_BUFFER_DECODER) + // }}} + ) adcd( + // {{{ + .i_clk(i_clk), .i_reset(i_reset), + .i_valid(skd_stb && i_mcyc[N]), .o_stall(skd_stall), + .i_addr(skd_addr), + .i_data({ skd_we, skd_data, skd_sel }), + .o_valid(dcd_stb[N]), .i_stall(m_stall[N]&&i_mcyc[N]), + .o_decode(decoded), .o_addr(m_addr[N]), + .o_data({ m_we[N], m_data[N], m_sel[N] }) + // }}} + ); + + assign request[N] = (m_cyc[N] && dcd_stb[N]) ? decoded : 0; + + assign m_cyc[N] = i_mcyc[N]; + assign m_stb[N] = i_mcyc[N] && dcd_stb[N] && !mfull[N]; + // }}} + end for(N=NM; N<NMFULL; N=N+1) + begin : UNUSED_MASTER_SIGNALS + // {{{ + // in case NM isn't one less than a power of two, complete + // the set + assign m_cyc[N] = 0; + assign m_stb[N] = 0; + + assign m_we[N] = 0; + assign m_addr[N] = 0; + assign m_data[N] = 0; + assign m_sel[N] = 0; + // }}} + end endgenerate + + // requested + // {{{ + always @(*) + begin + for(iM=0; iM<NS; iM=iM+1) + begin + // For each slave + requested[0][iM] = 0; + for(iN=1; iN<NM; iN=iN+1) + begin + // This slave has been requested if a prior + // master has requested it + // + // This includes any master before the last one + requested[iN][iM] = requested[iN-1][iM]; + // + // As well as if the last master has requested + // this slave. Only count this request, though, + // if this master could act upon it. + if (request[iN-1][iM] && + (grant[iN-1][iM] + || (!mgrant[iN-1]||mempty[iN-1]))) + requested[iN][iM] = 1; + end + end + end + // }}} + + generate for(M=0; M<NS; M=M+1) + begin : SLAVE_GRANT + // {{{ +`define REGISTERED_SGRANT +`ifdef REGISTERED_SGRANT + // {{{ + reg drop_sgrant; + + // drop_sgrant + // {{{ + always @(*) + begin + drop_sgrant = !m_cyc[sindex[M]]; + if (!request[sindex[M]][M] && m_stb[sindex[M]] + && mempty[sindex[M]]) + drop_sgrant = 1; + if (!sgrant[M]) + drop_sgrant = 0; + if (i_reset) + drop_sgrant = 1; + end + // }}} + + // sgrant + // {{{ + initial sgrant[M] = 0; + always @(posedge i_clk) + begin + sgrant[M] <= sgrant[M]; + for(iN=0; iN<NM; iN=iN+1) + if (request[iN][M] && (!mgrant[iN] || mempty[iN])) + sgrant[M] <= 1; + if (drop_sgrant) + sgrant[M] <= 0; + end + // }}} + // }}} +`else + // {{{ + // sgrant + // {{{ + always @(*) + begin + sgrant[M] = 0; + for(iN=0; iN<NM; iN=iN+1) + if (grant[iN][M]) + sgrant[M] = 1; + end + // }}} + // }}} +`endif + + assign s_data[M] = i_sdata[M*DW +: DW]; + assign s_stall[M] = o_sstb[M] && i_sstall[M]; + assign s_ack[M] = o_scyc[M] && i_sack[M]; + assign s_err[M] = o_scyc[M] && i_serr[M]; + + // }}} + end for(M=NS; M<NSFULL; M=M+1) + begin : UNUSED_SLAVE_SIGNALS + // {{{ + assign s_data[M] = 0; + assign s_stall[M] = 1; + assign s_ack[M] = 0; + assign s_err[M] = 1; + // }}} + end endgenerate + + // + // Arbitrate among masters to determine who gets to access a given + // channel + generate for(N=0; N<NM; N=N+1) + begin : ARBITRATE_REQUESTS + // {{{ + + // Register declarations + // {{{ + wire [NS:0] regrant; + wire [LGNS-1:0] reindex; + + // This is done using a couple of variables. + // + // request[N][M] + // This is true if master N is requesting to access slave + // M. It is combinatorial, so it will be true if the + // request is being made on the current clock. + // + // requested[N][M] + // True if some other master, prior to N, has requested + // channel M. This creates a basic priority arbiter, + // such that lower numbered masters have access before + // a greater numbered master + // + // grant[N][M] + // True if a grant has been made for master N to access + // slave channel M + // + // mgrant[N] + // True if master N has been granted access to some slave + // channel, any channel. + // + // mindex[N] + // This is the number of the slave channel that master + // N has been given access to + // + // sgrant[M] + // True if there exists some master, N, that has been + // granted access to this slave, hence grant[N][M] must + // also be true + // + // sindex[M] + // This is the index of the master that has access to + // slave M, assuming sgrant[M]. Hence, if sgrant[M] + // then grant[sindex[M]][M] must be true + // + reg stay_on_channel; + reg requested_channel_is_available; + // }}} + + // stay_on_channel + // {{{ + always @(*) + begin + stay_on_channel = |(request[N] & grant[N]); + + if (mgrant[N] && !mempty[N]) + stay_on_channel = 1; + end + // }}} + + // requested_channel_is_available + // {{{ + always @(*) + begin + requested_channel_is_available = + |(request[N][NS-1:0]& ~sgrant & ~requested[N][NS-1:0]); + + if (request[N][NS]) + requested_channel_is_available = 1; + + if (NM < 2) + requested_channel_is_available = m_stb[N]; + end + // }}} + + // grant, mgrant + // {{{ + initial grant[N] = 0; + initial mgrant[N] = 0; + always @(posedge i_clk) + if (i_reset || !i_mcyc[N]) + begin + grant[N] <= 0; + mgrant[N] <= 0; + end else if (!stay_on_channel) + begin + if (requested_channel_is_available) + begin + mgrant[N] <= 1'b1; + grant[N] <= request[N]; + end else if (m_stb[N]) + begin + mgrant[N] <= 1'b0; + grant[N] <= 0; + end + end + // }}} + + if (NS == 1) + begin : MINDEX_ONE_SLAVE + // {{{ + assign mindex[N] = 0; + assign regrant = 0; + assign reindex = 0; + // }}} + end else begin : MINDEX_MULTIPLE_SLAVES + // {{{ + reg [LGNS-1:0] r_mindex; + +`define NEW_MINDEX_CODE +`ifdef NEW_MINDEX_CODE + // {{{ + reg [NS:0] r_regrant; + reg [LGNS-1:0] r_reindex; + + // r_regrant + // {{{ + always @(*) + begin + r_regrant = 0; + for(iM=0; iM<NS; iM=iM+1) + begin + if (grant[N][iM]) + // Maintain any open channels + r_regrant[iM] = 1'b1; + else if (!sgrant[iM]&&!requested[N][iM]) + r_regrant[iM] = 1'b1; + + if (!request[N][iM]) + r_regrant[iM] = 1'b0; + end + + if (grant[N][NS]) + r_regrant[NS] = 1; + if (!request[N][NS]) + r_regrant[NS] = 0; + + if (mgrant[N] && !mempty[N]) + r_regrant = 0; + end + // }}} + + // r_reindex + // {{{ + // Verilator lint_off BLKSEQ + always @(r_regrant, regrant) + begin + r_reindex = 0; + for(iM=0; iM<=NS; iM=iM+1) + if (r_regrant[iM]) + r_reindex = r_reindex | iM[LGNS-1:0]; + if (regrant == 0) + r_reindex = r_mindex; + end + // Verilator lint_on BLKSEQ + // }}} + + always @(posedge i_clk) + r_mindex <= reindex; + + assign reindex = r_reindex; + assign regrant = r_regrant; + // }}} +`else + // {{{ + always @(posedge i_clk) + if (!mgrant[N] || mempty[N]) + begin + + for(iM=0; iM<NS; iM=iM+1) + begin + if (request[N][iM] && grant[N][iM]) + begin + // Maintain any open channels + r_mindex <= iM; + end else if (request[N][iM] + && !sgrant[iM] + && !requested[N][iM]) + begin + // Open a new channel + // if necessary + r_mindex <= iM; + end + end + end + + // }}} +`endif // NEW_MINDEX_CODE + assign mindex[N] = r_mindex; + // }}} + end + // }}} + end for (N=NM; N<NMFULL; N=N+1) + begin : UNUSED_MINDEXES + // {{{ + assign mindex[N] = 0; + // }}} + end endgenerate + + // Calculate sindex. sindex[M] (indexed by slave ID) + // references the master controlling this slave. This makes for + // faster/cheaper logic on the return path, since we can now use + // a fully populated LUT rather than a priority based return scheme + generate for(M=0; M<NS; M=M+1) + begin : GEN_SINDEX + // {{{ + if (NM <= 1) + begin : SINDEX_SINGLE_MASTER + // {{{ + // If there will only ever be one master, then we + // can assume all slave indexes point to that master + assign sindex[M] = 0; + // }}} + end else begin : SINDEX_MORE_THAN_ONE_MASTER + // {{{ + reg [LGNM-1:0] r_sindex; +`define NEW_SINDEX_CODE +`ifdef NEW_SINDEX_CODE + // {{{ + reg [NM-1:0] regrant; + reg [LGNM-1:0] reindex; + + always @(*) + begin + regrant = 0; + for (iN=0; iN<NM; iN=iN+1) + begin + // Each bit depends upon 6 inputs, so + // one 6-LUT should be sufficient + if (grant[iN][M]) + regrant[iN] = 1; + else if (!sgrant[M]&& !requested[iN][M]) + regrant[iN] = 1; + + if (!request[iN][M]) + regrant[iN] = 0; + if (mgrant[iN] && !mempty[iN]) + regrant[iN] = 0; + end + end + + always @(*) + begin + reindex = 0; + // Each bit in reindex depends upon all of the + // bits in regrant--should still be one LUT + // per bit though + if (regrant == 0) + reindex = sindex[M]; + else for(iN=0; iN<NM; iN=iN+1) + if (regrant[iN]) + reindex = reindex | iN[LGNM-1:0]; + end + + always @(posedge i_clk) + r_sindex <= reindex; + + assign sindex[M] = r_sindex; + // }}} +`else + // {{{ + always @(posedge i_clk) + for (iN=0; iN<NM; iN=iN+1) + begin + if (!mgrant[iN] || mempty[iN]) + begin + if (request[iN][M] && grant[iN][M]) + r_sindex <= iN; + else if (request[iN][M] && !sgrant[M] + && !requested[iN][M]) + r_sindex <= iN; + end + end + + assign sindex[M] = r_sindex; + // }}} +`endif + // }}} + end + // }}} + end for(M=NS; M<NSFULL; M=M+1) + begin : UNUSED_SINDEXES + // {{{ + // Assign the unused slave indexes to zero + // + // Remember, to full out a full 2^something set of slaves, + // we may have more slave indexes than we actually have slaves + + assign sindex[M] = 0; + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assign outputs to the slaves + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Part one + // + // In this part, we assign the difficult outputs: o_scyc and o_sstb + generate for(M=0; M<NS; M=M+1) + begin : GEN_CYC_STB + // {{{ + initial o_scyc[M] = 0; + initial o_sstb[M] = 0; + always @(posedge i_clk) + begin + if (sgrant[M]) + begin + + if (!i_mcyc[sindex[M]]) + begin + o_scyc[M] <= 1'b0; + o_sstb[M] <= 1'b0; + end else begin + o_scyc[M] <= 1'b1; + + if (!o_sstb[M] || !s_stall[M]) + o_sstb[M]<=request[sindex[M]][M] + && !mfull[sindex[M]]; + end + end else begin + o_scyc[M] <= 1'b0; + o_sstb[M] <= 1'b0; + end + + if (i_reset || s_err[M]) + begin + o_scyc[M] <= 1'b0; + o_sstb[M] <= 1'b0; + end + end + // }}} + end endgenerate + + // + // Part two + // + // These are the easy(er) outputs, since there are fewer properties + // riding on them + generate if ((NM == 1) && (!OPT_LOWPOWER)) + begin : ONE_MASTER + // {{{ + reg r_swe; + reg [AW-1:0] r_saddr; + reg [DW-1:0] r_sdata; + reg [DW/8-1:0] r_ssel; + + // + // This is the low logic version of our bus data outputs. + // It only works if we only have one master. + // + // The basic idea here is that we share all of our bus outputs + // between all of the various slaves. Since we only have one + // bus master, this works. + // + always @(posedge i_clk) + begin + r_swe <= o_swe[0]; + r_saddr <= o_saddr[0+:AW]; + r_sdata <= o_sdata[0+:DW]; + r_ssel <=o_ssel[0+:DW/8]; + + // Verilator lint_off WIDTH + if (sgrant[mindex[0]] && !s_stall[mindex[0]]) + // Verilator lint_on WIDTH + begin + r_swe <= m_we[0]; + r_saddr <= m_addr[0]; + r_sdata <= m_data[0]; + r_ssel <= m_sel[0]; + end + end + + // + // The original version set o_s*[0] above, and then + // combinatorially the rest of o_s* here below. That broke + // Veri1ator. Hence, we're using r_s* and setting all of o_s* + // here. + for(M=0; M<NS; M=M+1) + begin : FOREACH_SLAVE_PORT + always @(*) + begin + o_swe[M] = r_swe; + o_saddr[M*AW +: AW] = r_saddr[AW-1:0]; + o_sdata[M*DW +: DW] = r_sdata[DW-1:0]; + o_ssel[M*DW/8+:DW/8]= r_ssel[DW/8-1:0]; + end + end + // }}} + end else begin : J + for(M=0; M<NS; M=M+1) + begin : GEN_DOWNSTREAM + // {{{ + always @(posedge i_clk) + begin + if (OPT_LOWPOWER && !sgrant[M]) + begin + o_swe[M] <= 1'b0; + o_saddr[M*AW +: AW] <= 0; + o_sdata[M*DW +: DW] <= 0; + o_ssel[M*(DW/8)+:DW/8]<= 0; + end else if (!s_stall[M]) begin + o_swe[M] <= m_we[sindex[M]]; + o_saddr[M*AW +: AW] <= m_addr[sindex[M]]; + if (OPT_LOWPOWER && !m_we[sindex[M]]) + o_sdata[M*DW +: DW] <= 0; + else + o_sdata[M*DW +: DW] <= m_data[sindex[M]]; + o_ssel[M*(DW/8)+:DW/8]<= m_sel[sindex[M]]; + end + + end + // }}} + end end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assign return values to the masters + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate if (OPT_DBLBUFFER) + begin : DOUBLE_BUFFERRED_STALL + // {{{ + reg [NM-1:0] r_mack, r_merr; + + for(N=0; N<NM; N=N+1) + begin : FOREACH_MASTER_PORT + // m_stall isn't buffered, since it depends upon + // the already existing buffer within the address + // decoder + always @(*) + begin + if (grant[N][NS]) + m_stall[N] = 1; + else if (mgrant[N] && request[N][mindex[N]]) + m_stall[N] = mfull[N] || s_stall[mindex[N]]; + else + m_stall[N] = m_stb[N]; + + if (o_merr[N]) + m_stall[N] = 0; + end + + initial r_mack[N] = 0; + initial r_merr[N] = 0; + always @(posedge i_clk) + begin + // Verilator lint_off WIDTH + iM = mindex[N]; + // Verilator lint_on WIDTH + r_mack[N] <= mgrant[N] && s_ack[mindex[N]]; + r_merr[N] <= mgrant[N] && s_err[mindex[N]]; + if (OPT_LOWPOWER && !mgrant[N]) + o_mdata[N*DW +: DW] <= 0; + else + o_mdata[N*DW +: DW] <= s_data[mindex[N]]; + + if (grant[N][NS]||(timed_out[N] && !o_mack[N])) + begin + r_mack[N] <= 1'b0; + r_merr[N] <= !o_merr[N]; + end + + if (i_reset || !i_mcyc[N] || o_merr[N]) + begin + r_mack[N] <= 1'b0; + r_merr[N] <= 1'b0; + end + end + + assign o_mack[N] = r_mack[N]; + + assign o_merr[N] = (!OPT_STARVATION_TIMEOUT || i_mcyc[N]) && r_merr[N]; + + end + // }}} + end else if (NS == 1) // && !OPT_DBLBUFFER + begin : SINGLE_SLAVE + // {{{ + for(N=0; N<NM; N=N+1) + begin : FOREACH_MASTER_PORT + reg r_mack, r_merr; + + always @(*) + begin + m_stall[N] = !mgrant[N] || s_stall[0] + || (m_stb[N] && !request[N][0]); + r_mack = mgrant[N] && i_sack[0]; + r_merr = mgrant[N] && i_serr[0]; + o_mdata[N*DW +: DW] = (!mgrant[N] && OPT_LOWPOWER) + ? 0 : i_sdata; + + if (mfull[N]) + m_stall[N] = 1'b1; + + if (timed_out[N] && !r_mack) + begin + m_stall[N] = 1'b0; + r_mack = 1'b0; + r_merr = 1'b1; + end + + if (grant[N][NS] && m_stb[N]) + begin + m_stall[N] = 1'b0; + r_mack = 1'b0; + r_merr = 1'b1; + end + + if (!m_cyc[N]) + begin + r_mack = 1'b0; + r_merr = 1'b0; + end + end + + assign o_mack[N] = r_mack; + assign o_merr[N] = r_merr; + end + // }}} + end else begin : SINGLE_BUFFER_STALL + // {{{ + for(N=0; N<NM; N=N+1) + begin : FOREACH_MASTER_PORT + // initial o_mstall[N] = 0; + // initial o_mack[N] = 0; + reg r_mack, r_merr; + + always @(*) + begin + m_stall[N] = 1; + r_mack = mgrant[N] && s_ack[mindex[N]]; + r_merr = mgrant[N] && s_err[mindex[N]]; + if (OPT_LOWPOWER && !mgrant[N]) + o_mdata[N*DW +: DW] = 0; + else + o_mdata[N*DW +: DW] = s_data[mindex[N]]; + + if (mgrant[N]) + // Possibly lower the stall signal + m_stall[N] = s_stall[mindex[N]] + || !request[N][mindex[N]]; + + if (mfull[N]) + m_stall[N] = 1'b1; + + if (grant[N][NS] ||(timed_out[N] && !r_mack)) + begin + m_stall[N] = 1'b0; + r_mack = 1'b0; + r_merr = 1'b1; + end + + if (!m_cyc[N]) + begin + r_mack = 1'b0; + r_merr = 1'b0; + end + end + + assign o_mack[N] = r_mack; + assign o_merr[N] = r_merr; + end + // }}} + end endgenerate + + // + // Count the pending transactions per master + generate for(N=0; N<NM; N=N+1) + begin : COUNT_PENDING_TRANSACTIONS + // {{{ + reg [LGMAXBURST-1:0] lclpending; + initial lclpending = 0; + initial mempty[N] = 1; + initial mnearfull[N] = 0; + initial mfull[N] = 0; + always @(posedge i_clk) + if (i_reset || !i_mcyc[N] || o_merr[N]) + begin + lclpending <= 0; + mfull[N] <= 0; + mempty[N] <= 1'b1; + mnearfull[N]<= 0; + end else case({ (m_stb[N] && !m_stall[N]), o_mack[N] }) + 2'b01: begin + lclpending <= lclpending - 1'b1; + mnearfull[N]<= mfull[N]; + mfull[N] <= 1'b0; + mempty[N] <= (lclpending == 1); + end + 2'b10: begin + lclpending <= lclpending + 1'b1; + mnearfull[N]<= (&lclpending[LGMAXBURST-1:2])&&(lclpending[1:0] != 0); + mfull[N] <= mnearfull[N]; + mempty[N] <= 1'b0; + end + default: begin end + endcase + + assign w_mpending[N] = lclpending; + // }}} + end endgenerate + + generate if (OPT_TIMEOUT > 0) + begin : CHECK_TIMEOUT + // {{{ + for(N=0; N<NM; N=N+1) + begin : FOREACH_MASTER_PORT + + reg [TIMEOUT_WIDTH-1:0] deadlock_timer; + reg r_timed_out; + + initial deadlock_timer = OPT_TIMEOUT; + initial r_timed_out = 0; + always @(posedge i_clk) + if (i_reset || !i_mcyc[N] + ||((w_mpending[N] == 0) && !m_stb[N]) + ||(m_stb[N] && !m_stall[N]) + ||(o_mack[N] || o_merr[N]) + ||(!OPT_STARVATION_TIMEOUT&&!mgrant[N])) + begin + deadlock_timer <= OPT_TIMEOUT; + r_timed_out <= 0; + end else if (deadlock_timer > 0) + begin + deadlock_timer <= deadlock_timer - 1; + r_timed_out <= (deadlock_timer <= 1); + end + + assign timed_out[N] = r_timed_out; + end + // }}} + end else begin : NO_TIMEOUT + // {{{ + assign timed_out = 0; + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Parameter consistency check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + initial begin : PARAMETER_CONSISTENCY_CHECK + // {{{ + if (NM == 0) + begin + $display("ERROR: At least one master must be defined"); + $stop; + end + + if (NS == 0) + begin + $display("ERROR: At least one slave must be defined"); + $stop; + end + + if (OPT_STARVATION_TIMEOUT != 0 && OPT_TIMEOUT == 0) + begin + $display("ERROR: The starvation timeout is implemented as part of the regular timeout"); + $display(" Without a timeout, the starvation timeout will not work"); + $stop; + end + // }}} + end + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties used to verify the core +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Register declarations + // {{{ + localparam F_MAX_DELAY = 4; + localparam F_LGDEPTH = LGMAXBURST; + // + reg f_past_valid; + // + // Our bus checker keeps track of the number of requests, + // acknowledgments, and the number of outstanding transactions on + // every channel, both the masters driving us + wire [F_LGDEPTH-1:0] f_mreqs [0:NM-1]; + wire [F_LGDEPTH-1:0] f_macks [0:NM-1]; + wire [F_LGDEPTH-1:0] f_moutstanding [0:NM-1]; + // + // as well as the slaves that we drive ourselves + wire [F_LGDEPTH-1:0] f_sreqs [0:NS-1]; + wire [F_LGDEPTH-1:0] f_sacks [0:NS-1]; + wire [F_LGDEPTH-1:0] f_soutstanding [0:NS-1]; + // }}} + + initial assert(!OPT_STARVATION_TIMEOUT || OPT_TIMEOUT > 0); + + initial f_past_valid = 0; + always @(posedge i_clk) + f_past_valid = 1'b1; + + always @(*) + if (!f_past_valid) + assume(i_reset); + + generate for(N=0; N<NM; N=N+1) + begin : GRANT_CHECKING + // {{{ + reg checkgrant; + + always @(*) + if (f_past_valid) + for(iN=N+1; iN<NM; iN=iN+1) + // Can't grant the same channel to two separate + // masters. This applies to all but the error or + // no-slave-selected channel + assert((grant[N][NS-1:0] & grant[iN][NS-1:0])==0); + + for(M=1; M<=NS; M=M+1) + begin + // Can't grant two channels to the same master + always @(*) + if (f_past_valid && grant[N][M]) + assert(grant[N][M-1:0] == 0); + end + + + always @(*) + if (&w_mpending[N]) + assert(o_merr[N] || m_stall[N]); + + always @(*) + if (f_past_valid) + begin + checkgrant = 0; + for(iM=0; iM<NS; iM=iM+1) + if (grant[N][iM]) + checkgrant = 1; + if (grant[N][NS]) + checkgrant = 1; + + assert(checkgrant == mgrant[N]); + end + // }}} + end endgenerate + + // Double check the grant mechanism and its dependent variables + generate for(N=0; N<NM; N=N+1) + begin : CHECK_GRANTS + // {{{ + for(M=0; M<NS; M=M+1) + begin + always @(*) + if ((f_past_valid)&&grant[N][M]) + begin + assert(mgrant[N]); + assert(mindex[N] == M); + assert(sgrant[M]); + assert(sindex[M] == N); + end + end + // }}} + end endgenerate + + generate for(M=0; M<NS; M=M+1) + begin : CHECK_SGRANT + // {{{ + reg f_sgrant; + + always @(*) + if (sgrant[M]) + assert(grant[sindex[M]][M]); + + always @(*) + begin + f_sgrant = 0; + for(iN=0; iN<NM; iN=iN+1) + if (grant[iN][M]) + f_sgrant = 1; + end + + always @(*) + assert(sgrant[M] == f_sgrant); + // }}} + end endgenerate + + // Double check the timeout flags for consistency + generate for(N=0; N<NM; N=N+1) + begin : F_CHECK_TIMEOUT + // {{{ + always @(*) + if (f_past_valid) + begin + assert(mempty[N] == (w_mpending[N] == 0)); + assert(mnearfull[N]==(&w_mpending[N][LGMAXBURST-1:1])); + assert(mfull[N] == (&w_mpending[N])); + end + // }}} + end endgenerate + +`ifdef VERIFIC + // {{{ + // The Verific parser is currently broken, and doesn't allow + // initial assumes or asserts. The following lines get us around that + // + always @(*) + if (!f_past_valid) + assume(sgrant == 0); + + generate for(M=0; M<NS; M=M+1) + begin + always @(*) + if (!f_past_valid) + begin + assume(o_scyc[M] == 0); + assume(o_sstb[M] == 0); + assume(sgrant[M] == 0); + end + end endgenerate + + generate for(N=0; N<NM; N=N+1) + begin + always @(*) + if (!f_past_valid) + begin + assume(grant[N] == 0); + assume(mgrant[N] == 0); + end + end endgenerate + // }}} +`endif + + //////////////////////////////////////////////////////////////////////// + // + // BUS CHECK + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // Verify that every channel, whether master or slave, follows the rules + // of the WB road. + // + generate for(N=0; N<NM; N=N+1) + begin : WB_SLAVE_CHECK + // {{{ + fwb_slave #( + .AW(AW), .DW(DW), + .F_LGDEPTH(LGMAXBURST), + .F_MAX_ACK_DELAY(0), + .F_MAX_STALL(0) + ) slvi(i_clk, i_reset, + i_mcyc[N], i_mstb[N], i_mwe[N], + i_maddr[N*AW +: AW], i_mdata[N*DW +: DW], + i_msel[N*(DW/8) +: (DW/8)], + o_mack[N], o_mstall[N], o_mdata[N*DW +: DW], o_merr[N], + f_mreqs[N], f_macks[N], f_moutstanding[N]); + + always @(*) + if ((f_past_valid)&&(grant[N][NS])) + assert(f_moutstanding[N] <= 1); + + always @(*) + if (f_past_valid && grant[N][NS] && i_mcyc[N]) + assert(m_stall[N] || o_merr[N]); + + always @(posedge i_clk) + if (f_past_valid && $past(!i_reset && i_mstb[N] && o_mstall[N])) + assume($stable(i_mdata[N*DW +: DW])); + // }}} + end endgenerate + + generate for(M=0; M<NS; M=M+1) + begin : WB_MASTER_CHECK + // {{{ + fwb_master #( + .AW(AW), .DW(DW), + .F_LGDEPTH(LGMAXBURST), + .F_MAX_ACK_DELAY(F_MAX_DELAY), + .F_MAX_STALL(2) + ) mstri(i_clk, i_reset, + o_scyc[M], o_sstb[M], o_swe[M], + o_saddr[M*AW +: AW], o_sdata[M*DW +: DW], + o_ssel[M*(DW/8) +: (DW/8)], + i_sack[M], i_sstall[M], s_data[M], i_serr[M], + f_sreqs[M], f_sacks[M], f_soutstanding[M]); + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Correlate outstanding numbers + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + generate for(N=0; N<NM; N=N+1) + begin : CHECK_OUTSTANDING + // {{{ + always @(*) + if (mfull[N]) + assert(m_stall[N]); + + always @(posedge i_clk) + if (i_mcyc[N]) + assert(f_moutstanding[N] == w_mpending[N] + +((OPT_BUFFER_DECODER & dcd_stb[N]) ? 1:0)); + + reg [LGMAXBURST:0] n_outstanding; + always @(*) + if (i_mcyc[N]) + assert(f_moutstanding[N] >= + ((OPT_BUFFER_DECODER && dcd_stb[N]) ? 1:0) + + (o_mack[N] && OPT_DBLBUFFER) ? 1:0); + + always @(*) + n_outstanding = f_moutstanding[N] + - ((OPT_BUFFER_DECODER && dcd_stb[N]) ? 1:0) + - ((o_mack[N] && OPT_DBLBUFFER) ? 1:0); + + always @(posedge i_clk) + if (i_mcyc[N] && !mgrant[N] && !o_merr[N]) + assert(f_moutstanding[N] + == ((OPT_BUFFER_DECODER & dcd_stb[N]) ? 1:0)); + + else if (i_mcyc[N] && mgrant[N] && !i_reset) + for(iM=0; iM<NS; iM=iM+1) + if (grant[N][iM] && o_scyc[iM] && !i_serr[iM] && !o_merr[N]) + assert(n_outstanding + == {1'b0,f_soutstanding[iM]} + +(o_sstb[iM] ? 1:0)); + + always @(*) + if (!i_reset) + begin + for(iM=0; iM<NS; iM=iM+1) + if (grant[N][iM] && i_mcyc[N]) + begin + if (f_soutstanding[iM] > 0) + assert(i_mwe[N] == o_swe[iM]); + if (o_sstb[iM]) + assert(i_mwe[N] == o_swe[iM]); + if (o_mack[N]) + assert(i_mwe[N] == o_swe[iM]); + if (o_scyc[iM] && i_sack[iM]) + assert(i_mwe[N] == o_swe[iM]); + if (o_merr[N] && !timed_out[N]) + assert(i_mwe[N] == o_swe[iM]); + if (o_scyc[iM] && i_serr[iM]) + assert(i_mwe[N] == o_swe[iM]); + end + end + + always @(*) + if (!i_reset && OPT_BUFFER_DECODER && i_mcyc[N]) + begin + if (dcd_stb[N]) + assert(i_mwe[N] == m_we[N]); + end + // }}} + end endgenerate + + generate for(M=0; M<NS; M=M+1) + begin : ASSERT_NOT_CYC_WO_GRANT + // {{{ + always @(posedge i_clk) + if (!$past(sgrant[M])) + assert(!o_scyc[M]); + // }}} + end endgenerate + // }}} + //////////////////////////////////////////////////////////////////////// + // + // CONTRACT SECTION + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + // Here's the contract, in two parts: + // {{{ + // 1. Should ever a master (any master) wish to read from a slave + // (any slave), he should be able to read a known value + // from that slave (any value) from any arbitrary address + // he might wish to read from (any address) + // + // 2. Should ever a master (any master) wish to write to a slave + // (any slave), he should be able to write the exact + // value he wants (any value) to the exact address he wants + // (any address) + // + // special_master is an arbitrary constant chosen by the solver, + // which can reference *any* possible master + // special_address is an arbitrary constant chosen by the solver, + // which can reference *any* possible address the master + // might wish to access + // special_value is an arbitrary value (at least during + // induction) representing the current value within the + // slave at the given address + // }}} + // + //////////////////////////////////////////////////////////////////////// + // + // Now let's pay attention to a special bus master and a special + // address referencing a special bus slave. We'd like to assert + // that we can access the values of every slave from every master. + (* anyconst *) reg [(NM<=1)?0:(LGNM-1):0] special_master; + reg [(NS<=1)?0:(LGNS-1):0] special_slave; + (* anyconst *) reg [AW-1:0] special_address; + reg [DW-1:0] special_value; + + always @(*) + if (NM <= 1) + assume(special_master == 0); + always @(*) + if (NS <= 1) + assume(special_slave == 0); + + // + // Decode the special address to discover the slave associated with it + always @(*) + begin + special_slave = NS; + for(iM=0; iM<NS; iM = iM+1) + begin + if (((special_address ^ SLAVE_ADDR[iM*AW +: AW]) + &SLAVE_MASK[iM*AW +: AW]) == 0) + special_slave = iM; + end + end + + generate if (NS > 1) + begin : DOUBLE_ADDRESS_CHECK + // {{{ + // + // Check that no slave address has been assigned twice. + // This check only needs to be done once at the beginning + // of the run, during the BMC section. + reg address_found; + + always @(*) + if (!f_past_valid) + begin + address_found = 0; + for(iM=0; iM<NS; iM = iM+1) + begin + if (((special_address ^ SLAVE_ADDR[iM*AW +: AW]) + &SLAVE_MASK[iM*AW +: AW]) == 0) + begin + assert(address_found == 0); + address_found = 1; + end + end + end + // }}} + end endgenerate + // + // Let's assume this slave will acknowledge any request on the next + // bus cycle after the stall goes low. Further, lets assume that + // it never creates an error, and that it always responds to our special + // address with the special data value given above. To do this, we'll + // also need to make certain that the special value will change + // following any write. + // + // These are the "assumptions" associated with our fictitious slave. +`ifdef VERIFIC + always @(*) + if (!f_past_valid) + assume(special_value == 0); +`else + initial assume(special_value == 0); +`endif + always @(posedge i_clk) + if (special_slave < NS) + begin + // {{{ + if ($past(o_sstb[special_slave] && !i_sstall[special_slave])) + begin + assume(i_sack[special_slave]); + + if ($past(!o_swe[special_slave]) + &&($past(o_saddr[special_slave*AW +: AW]) == special_address)) + assume(i_sdata[special_slave*DW+: DW] + == special_value); + end else + assume(!i_sack[special_slave]); + assume(!i_serr[special_slave]); + + if (o_scyc[special_slave]) + assert(f_soutstanding[special_slave] + == i_sack[special_slave]); + + if (o_sstb[special_slave] && !i_sstall[special_slave] + && o_swe[special_slave]) + begin + for(iM=0; iM < DW/8; iM=iM+1) + if (o_ssel[special_slave * DW/8 + iM]) + special_value[iM*8 +: 8] <= o_sdata[special_slave * DW + iM*8 +: 8]; + end + // }}} + end + + // + // Now its time to make some assertions. Specifically, we want to + // assert that any time we read from this special slave, the special + // value is returned. + reg [2:0] f_read_seq; + reg f_read_ack, f_read_sstall; + + initial f_read_sstall = 0; + always @(posedge i_clk) + f_read_sstall <= s_stall[special_slave]; + + always @(*) + f_read_ack = (f_read_seq[2] || ((!OPT_DBLBUFFER)&&f_read_seq[1] + && !f_read_sstall)); + initial f_read_seq = 0; + always @(posedge i_clk) + if ((special_master < NM)&&(special_slave < NS) + &&(i_mcyc[special_master]) + &&(!timed_out[special_master])) + begin + f_read_seq <= 0; + if ((grant[special_master][special_slave]) + &&(m_stb[special_master]) + &&(m_addr[special_master] == special_address) + &&(!m_we[special_master]) + ) + begin + f_read_seq[0] <= 1; + end + + if (|f_read_seq) + begin + assert(grant[special_master][special_slave]); + assert(mgrant[special_master]); + assert(sgrant[special_slave]); + assert(mindex[special_master] == special_slave); + assert(sindex[special_slave] == special_master); + assert(!o_merr[special_master]); + end + + if (f_read_seq[0] && !$past(s_stall[special_slave])) + begin + assert(o_scyc[special_slave]); + assert(o_sstb[special_slave]); + assert(!o_swe[special_slave]); + assert(o_saddr[special_slave*AW +: AW] == special_address); + + f_read_seq[1] <= 1; + + end else if (f_read_seq[0] && $past(s_stall[special_slave])) + begin + assert($stable(m_stb[special_master])); + assert(!m_we[special_master]); + assert(m_addr[special_master] == special_address); + + f_read_seq[0] <= 1; + end + + if (f_read_seq[1] && $past(s_stall[special_slave])) + begin + assert(o_scyc[special_slave]); + assert(o_sstb[special_slave]); + assert(!o_swe[special_slave]); + assert(o_saddr[special_slave*AW +: AW] == special_address); + f_read_seq[1] <= 1; + end else if (f_read_seq[1] && !$past(s_stall[special_slave])) + begin + assert(i_sack[special_slave]); + assert(i_sdata[special_slave*DW +: DW] == $past(special_value)); + if (OPT_DBLBUFFER) + f_read_seq[2] <= 1; + end + + if (f_read_ack) + begin + assert(o_mack[special_master]); + assert(o_mdata[special_master * DW +: DW] + == $past(special_value,2)); + end + end else + f_read_seq <= 0; + + always @(*) + cover(i_mcyc[special_master] && f_read_ack); + + // + // Let's try a write assertion now. Specifically, on any request to + // write to our special address, we want to assert that the special + // value at that address can be written. + reg [2:0] f_write_seq; + reg f_write_ack, f_write_sstall; + + initial f_write_sstall = 0; + always @(posedge i_clk) + f_write_sstall = s_stall[special_slave]; + + always @(*) + f_write_ack = (f_write_seq[2] + || ((!OPT_DBLBUFFER)&&f_write_seq[1] + && !f_write_sstall)); + initial f_write_seq = 0; + always @(posedge i_clk) + if ((special_master < NM)&&(special_slave < NS) + &&(i_mcyc[special_master]) + &&(!timed_out[special_master])) + begin + f_write_seq <= 0; + if ((grant[special_master][special_slave]) + &&(m_stb[special_master]) + &&(m_addr[special_master] == special_address) + &&(m_we[special_master])) + begin + // Our write sequence begins when our special master + // has access to the bus, *and* he is trying to write + // to our special address. + f_write_seq[0] <= 1; + end + + if (|f_write_seq) + begin + assert(grant[special_master][special_slave]); + assert(mgrant[special_master]); + assert(sgrant[special_slave]); + assert(mindex[special_master] == special_slave); + assert(sindex[special_slave] == special_master); + assert(!o_merr[special_master]); + end + + if (f_write_seq[0] && !$past(s_stall[special_slave])) + begin + assert(o_scyc[special_slave]); + assert(o_sstb[special_slave]); + assert(o_swe[special_slave]); + assert(o_saddr[special_slave*AW +: AW] == special_address); + assert(o_sdata[special_slave*DW +: DW] + == $past(m_data[special_master])); + assert(o_ssel[special_slave*DW/8 +: DW/8] + == $past(m_sel[special_master])); + + f_write_seq[1] <= 1; + + end else if (f_write_seq[0] && $past(s_stall[special_slave])) + begin + assert($stable(m_stb[special_master])); + assert(m_we[special_master]); + assert(m_addr[special_master] == special_address); + assert($stable(m_data[special_master])); + assert($stable(m_sel[special_master])); + + f_write_seq[0] <= 1; + end + + if (f_write_seq[1] && $past(s_stall[special_slave])) + begin + assert(o_scyc[special_slave]); + assert(o_sstb[special_slave]); + assert(o_swe[special_slave]); + assert(o_saddr[special_slave*AW +: AW] == special_address); + assert($stable(o_sdata[special_slave*DW +: DW])); + assert($stable(o_ssel[special_slave*DW/8 +: DW/8])); + f_write_seq[1] <= 1; + end else if (f_write_seq[1] && !$past(s_stall[special_slave])) + begin + for(iM=0; iM<DW/8; iM=iM+1) + begin + if ($past(o_ssel[special_slave * DW/8 + iM])) + assert(special_value[iM*8 +: 8] + == $past(o_sdata[special_slave*DW+iM*8 +: 8])); + end + + assert(i_sack[special_slave]); + if (OPT_DBLBUFFER) + f_write_seq[2] <= 1; + end + + if (f_write_seq[2] || ((!OPT_DBLBUFFER) && f_write_seq[1] + && !$past(s_stall[special_slave]))) + assert(o_mack[special_master]); + end else + f_write_seq <= 0; + + always @(*) + cover(i_mcyc[special_master] && f_write_ack); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // COVER: Full connectivity check + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg [NM-1:0] f_m_ackd; + reg [NS-1:0] f_s_ackd; + reg f_cvr_aborted; + + initial f_cvr_aborted = 0; + always @(posedge i_clk) + if (!f_past_valid || i_reset) + f_cvr_aborted <= 0; + else for(iN=0; iN<NM; iN=iN+1) + begin + if (request[iN][NS]) + f_cvr_aborted = 1; + if ($fell(i_mcyc[iN])) + begin + if (f_macks[iN] != f_mreqs[iN]) + f_cvr_aborted = 1; + end + end + + initial f_m_ackd = 0; + generate for (N=0; N<NM; N=N+1) + begin : GEN_FM_ACKD + + always @(posedge i_clk) + if (i_reset) + f_m_ackd[N] <= 0; + else if (o_mack[N]) + f_m_ackd[N] <= 1'b1; + + end endgenerate + + always @(posedge i_clk) + cover(!f_cvr_aborted && (&f_m_ackd)); + + generate if (NM > 1) + begin + + always @(posedge i_clk) + cover(!f_cvr_aborted && (&f_m_ackd[1:0])); + + end endgenerate + + initial f_s_ackd = 0; + generate for (M=0; M<NS; M=M+1) + begin : GEN_FS_ACKD + + always @(posedge i_clk) + if (i_reset) + f_s_ackd[M] <= 1'b0; + else if (sgrant[M] && o_mack[sindex[M]]) + f_s_ackd[M] <= 1'b1; + + end endgenerate + + always @(posedge i_clk) + cover(!f_cvr_aborted && (&f_s_ackd[NS-1:0])); + + generate if (NS > 1) + begin + + always @(posedge i_clk) + cover(!f_cvr_aborted && (&f_s_ackd[NS-1:0])); + + end endgenerate + // }}} +`endif +// }}} +endmodule +`ifndef YOSYS +`default_nettype wire +`endif diff --git a/rtl/wb2axip/wbxclk.v b/rtl/wb2axip/wbxclk.v new file mode 100644 index 0000000..5f404b0 --- /dev/null +++ b/rtl/wb2axip/wbxclk.v @@ -0,0 +1,854 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Filename: rtl/wbxclk.v +// {{{ +// Project: WB2AXIPSP: bus bridges and other odds and ends +// +// Purpose: To cross clock domains with a (pipelined) wishbone bus. +// +// Challenges: +// 1. Wishbone has no capacity for back pressure. That means that we'll +// need to be careful not to issue more STB requests than ACKs +// that will fit in the return buffer. +// +// Imagine, for example, a faster return clock but a slave that needs +// many clocks to get going. During that time, many requests +// might be issued. If they all suddenly get returned at once, +// flooding the return ACK FIFO, then we have a problem. +// +// 2. Bus aborts. If we ever have to abort a transaction, that's going +// to be a pain. The FIFOs will need to be reset and the +// downstream CYC line dropped. This needs to be done +// synchronously in both domains, but there's no real choice but +// to make the crossing asynchronous. +// +// 3. Synchronous CYC. Lowering CYC is a normal part of the protocol, as +// is raising CYC. CYC is used as a bus locking scheme, so we'll +// need to know when it is (properly) lowered downstream. This +// can be done by passing a synchronous CYC drop request through +// the pipeline in addition to the bus aborts above. +// +// Status: +// Formally verified against a set of bus properties, not yet +// used in any real or simulated designs +// +// Creator: Dan Gisselquist, Ph.D. +// Gisselquist Technology, LLC +// +//////////////////////////////////////////////////////////////////////////////// +// }}} +// Copyright (C) 2020-2024, Gisselquist Technology, LLC +// {{{ +// This file is part of the WB2AXIP project. +// +// The WB2AXIP project contains free software and gateware, licensed under the +// Apache License, Version 2.0 (the "License"). You may not use this project, +// or this file, except in compliance with the License. You may obtain a copy +// of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. +// +//////////////////////////////////////////////////////////////////////////////// +// +// +`default_nettype none +// }}} +module wbxclk #( + // {{{ + parameter AW=32, + DW=32, + LGFIFO = 5 + // }}} + ) ( + // {{{ + input wire i_wb_clk, i_reset, + // Input/master bus + input wire i_wb_cyc, i_wb_stb, i_wb_we, + input wire [(AW-1):0] i_wb_addr, + input wire [(DW-1):0] i_wb_data, + input wire [(DW/8-1):0] i_wb_sel, + output wire o_wb_stall, + output reg o_wb_ack, + output reg [(DW-1):0] o_wb_data, + output reg o_wb_err, + // Delayed bus + input wire i_xclk_clk, + output reg o_xclk_cyc, + output reg o_xclk_stb, + output reg o_xclk_we, + output reg [(AW-1):0] o_xclk_addr, + output reg [(DW-1):0] o_xclk_data, + output reg [(DW/8-1):0] o_xclk_sel, + input wire i_xclk_stall, + input wire i_xclk_ack, + input wire [(DW-1):0] i_xclk_data, + input wire i_xclk_err + // }}} + ); + + // + // Declare our signals + // {{{ + localparam NFF = 2; + reg wb_active; + reg [NFF-2:0] bus_abort_pipe; + reg [LGFIFO:0] acks_outstanding; + reg ackfifo_single, ackfifo_empty, ackfifo_full; + wire ack_stb, err_stb; + wire [DW-1:0] ret_wb_data; + wire req_fifo_stall, no_returns; + // + // Verilator lint_off SYNCASYNCNET + reg bus_abort; + // Verilator lint_on SYNCASYNCNET + // + wire req_stb, req_fifo_empty; + reg xclk_err_state, ign_ackfifo_stall; + reg xck_reset; + reg [NFF-2:0] xck_reset_pipe; + wire req_we; + wire [AW-1:0] req_addr; + wire [DW-1:0] req_data; + wire [DW/8-1:0] req_sel; +`ifdef FORMAL + wire [LGFIFO:0] ackfifo_prefill, reqfifo_prefill; +`endif + // }}} + + // + // + // On the original wishbone clock ... + // + // FIFO/queue up our requests + // {{{ + initial wb_active = 1'b0; + always @(posedge i_wb_clk) + if (i_reset || !i_wb_cyc) + wb_active <= 1'b0; + else if (i_wb_stb && !o_wb_stall) + wb_active <= 1'b1; + + initial { bus_abort, bus_abort_pipe } = -1; + always @(posedge i_wb_clk) + if (i_reset) + { bus_abort, bus_abort_pipe } <= -1; + else if (!i_wb_cyc && (!ackfifo_empty)) + { bus_abort, bus_abort_pipe } <= -1; + else if (o_wb_err) + { bus_abort, bus_abort_pipe } <= -1; + else if (ackfifo_empty) + { bus_abort, bus_abort_pipe } <= { bus_abort_pipe, 1'b0 }; +`ifdef FORMAL + always @(*) + if (bus_abort_pipe) + assert(bus_abort); +`endif + // }}} + + // + // The request FIFO itself + // {{{ + afifo #( +`ifdef FORMAL + .OPT_REGISTER_READS(0), + .F_OPT_DATA_STB(1'b0), +`endif + .NFF(NFF), .LGFIFO(LGFIFO), + .WIDTH(2+AW+DW+(DW/8)) + ) reqfifo(.i_wclk(i_wb_clk), .i_wr_reset_n(!bus_abort), + .i_wr((i_wb_stb&&!o_wb_stall) || (wb_active && !i_wb_cyc)), + .i_wr_data({ i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel }), + .o_wr_full(req_fifo_stall), + // + .i_rclk(i_xclk_clk), .i_rd_reset_n(!xck_reset), + .i_rd(!o_xclk_stb || !i_xclk_stall), + .o_rd_data({ req_stb, req_we, req_addr, req_data, req_sel }), + .o_rd_empty(req_fifo_empty) +`ifdef FORMAL + , .f_fill(reqfifo_prefill) +`endif + ); + // }}} + + // + // Downstream bus--issuing requests + // {{{ + initial { xck_reset, xck_reset_pipe } = -1; + always @(posedge i_xclk_clk or posedge bus_abort) + if (bus_abort) + { xck_reset, xck_reset_pipe } <= -1; + else + { xck_reset, xck_reset_pipe } <= { xck_reset_pipe, 1'b0 }; +`ifdef FORMAL + always @(*) + if (xck_reset_pipe) + assert(xck_reset); +`endif + + initial xclk_err_state = 1'b0; + always @(posedge i_xclk_clk) + if (xck_reset || (!req_fifo_empty && !req_stb)) + xclk_err_state <= 1'b0; + else if (o_xclk_cyc && i_xclk_err) + xclk_err_state <= 1'b1; + + initial o_xclk_cyc = 1'b0; + always @(posedge i_xclk_clk) + if (xck_reset || (o_xclk_cyc && i_xclk_err)) + o_xclk_cyc <= 1'b0; + else if (!req_fifo_empty && !req_stb) + o_xclk_cyc <= 1'b0; + else if (!req_fifo_empty && !xclk_err_state) + o_xclk_cyc <= req_stb; + + initial o_xclk_stb = 1'b0; + always @(posedge i_xclk_clk) + if (xck_reset || (o_xclk_cyc && i_xclk_err) || xclk_err_state) + o_xclk_stb <= 1'b0; + else if (!o_xclk_stb || !i_xclk_stall) + o_xclk_stb <= req_stb && !req_fifo_empty; + + always @(posedge i_xclk_clk) + if ((!o_xclk_stb || !i_xclk_stall) && req_stb && !req_fifo_empty) + o_xclk_we <= req_we; + + always @(posedge i_xclk_clk) + if (!o_xclk_stb || !i_xclk_stall) + begin + o_xclk_addr <= req_addr; + o_xclk_data <= req_data; + o_xclk_sel <= req_sel; + end + // }}} + + + // + // Request counting + // {{{ + initial acks_outstanding = 0; + initial ackfifo_single = 0; + initial ackfifo_empty = 1; + initial ackfifo_full = 0; + always @(posedge i_wb_clk) + if (i_reset || !i_wb_cyc || o_wb_err) + begin + acks_outstanding <= 0; + ackfifo_single <= 0; + ackfifo_empty <= 1; + ackfifo_full <= 0; + end else case({ (i_wb_stb && !o_wb_stall), o_wb_ack }) + 2'b10: begin + acks_outstanding <= acks_outstanding + 1; + ackfifo_empty <= 0; + ackfifo_single <= ackfifo_empty; + ackfifo_full <= (&acks_outstanding[LGFIFO-1:0]); + end + 2'b01: begin + acks_outstanding <= acks_outstanding - 1; + ackfifo_empty <= (acks_outstanding <= 1); + ackfifo_single <= (acks_outstanding == 2); + ackfifo_full <= 0; + end + default: begin end + endcase + +`ifdef FORMAL + always @(*) + begin + assert(ackfifo_single == (acks_outstanding == 1)); + assert(ackfifo_empty == (acks_outstanding == 0)); + assert(ackfifo_full == (acks_outstanding >= (1<<LGFIFO))); + assert(acks_outstanding <= (1<<LGFIFO)); + end +`endif + + assign o_wb_stall = ackfifo_full || bus_abort || req_fifo_stall; + // }}} + + // + // The return FIFO + // {{{ + afifo #( + .OPT_REGISTER_READS(0), + .NFF(NFF), .LGFIFO(LGFIFO), + .WIDTH(2+DW) +`ifdef FORMAL + , .F_OPT_DATA_STB(1'b0) +`endif + ) ackfifo(.i_wclk(i_xclk_clk), .i_wr_reset_n(!xck_reset), + .i_wr(o_xclk_cyc && ( i_xclk_ack || i_xclk_err )), + .i_wr_data({ i_xclk_ack, i_xclk_err, i_xclk_data }), + .o_wr_full(ign_ackfifo_stall), + // + .i_rclk(i_wb_clk), .i_rd_reset_n(!bus_abort), + .i_rd(!no_returns), + .o_rd_data({ ack_stb, err_stb, ret_wb_data }), + .o_rd_empty(no_returns) +`ifdef FORMAL + , .f_fill(ackfifo_prefill) +`endif + ); + // }}} + + // + // Final return processing + // {{{ + initial { o_wb_ack, o_wb_err } = 2'b00; + always @(posedge i_wb_clk) + if (i_reset || bus_abort || !i_wb_cyc || no_returns || o_wb_err) + { o_wb_ack, o_wb_err } <= 2'b00; + else + { o_wb_ack, o_wb_err } <= { ack_stb, err_stb }; + + always @(posedge i_wb_clk) + o_wb_data <= ret_wb_data; + // }}} + + // Make Verilator happy + // {{{ + // Verilator lint_off UNUSED + wire unused; + assign unused = &{ 1'b0, req_fifo_stall, ign_ackfifo_stall, + ackfifo_single }; + // Verilator lint_on UNUSED + // }}} +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// +// Formal properties +// {{{ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +`ifdef FORMAL + // Register/net/macro declarations + // {{{ +`ifdef BMC +`define BMC_ASSERT assert +`else +`define BMC_ASSERT assume +`endif + + (* gclk *) reg gbl_clk; + + wire [LGFIFO:0] fwb_nreqs, fwb_nacks, fwb_outstanding; + wire [LGFIFO:0] fxck_nreqs, fxck_nacks, fxck_outstanding; + reg [LGFIFO:0] ackfifo_fill, reqfifo_fill; + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Assume a clock + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + (* anyconst *) reg [3:0] fwb_step, fxck_step; + reg [3:0] fwb_count, fxck_count; + + always @(*) + begin + assume(fwb_step >= 2); + assume(fxck_step >= 2); + + assume(fwb_step <= 4'b1000); + assume(fxck_step <= 4'b1000); + + // assume(fwb_step == 4'b1000); + // assume(fxck_step == 4'b0111); + assume((fwb_step == 4'b0111) + || (fxck_step == 4'b0111)); + end + + always @(posedge gbl_clk) + begin + fwb_count <= fwb_count + fwb_step; + fxck_count <= fxck_count + fxck_step; + end + + always @(*) + begin + assume(i_wb_clk == fwb_count[3]); + assume(i_xclk_clk == fxck_count[3]); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // .... + // + //////////////////////////////////////////////////////////////////////// + // + // + + // + // Cross clock stability assumptions + // {{{ + always @(posedge gbl_clk) + if (!$rose(i_wb_clk)) + begin + assume($stable(i_reset)); + + assume($stable(i_wb_cyc)); + assume($stable(i_wb_stb)); + assume($stable(i_wb_we)); + assume($stable(i_wb_addr)); + assume($stable(i_wb_data)); + assume($stable(i_wb_sel)); + end + + always @(posedge gbl_clk) + if (!$rose(i_xclk_clk)) + begin + assume($stable(i_xclk_ack)); + assume($stable(i_xclk_err)); + assume($stable(i_xclk_data)); + assume($stable(i_xclk_stall)); + end + + reg past_wb_clk, past_wb_stb, past_wb_we, + past_wb_cyc, past_wb_reset, past_wb_err; + reg past_xclk, past_xclk_stall, past_xclk_ack, + past_xclk_err; + reg [DW-1:0] past_xclk_data; + reg f_drop_cyc_request; + + always @(posedge gbl_clk) + begin + past_wb_clk <= i_wb_clk; + past_wb_reset<= i_reset; + past_wb_cyc <= i_wb_cyc; + past_wb_stb <= i_wb_stb; + past_wb_we <= i_wb_we; + past_wb_err <= o_wb_err; + end + + always @(*) + if (!i_wb_clk || past_wb_clk) + begin + assume(past_wb_reset== i_reset); + assume(past_wb_cyc == i_wb_cyc); + assume(past_wb_stb == i_wb_stb); + assume(past_wb_we == i_wb_we); + assume(past_wb_err == o_wb_err); + end else begin + if (past_wb_err && past_wb_cyc) + assume(!i_wb_cyc); + if (fwb_outstanding > 0) + assume(past_wb_we == i_wb_we); + end + + always @(posedge gbl_clk) + begin + past_xclk <= i_xclk_clk; + past_xclk_stall <= i_xclk_stall; + past_xclk_data <= i_xclk_data; + past_xclk_ack <= i_xclk_ack; + past_xclk_err <= i_xclk_err; + end + + always @(*) + if (!i_xclk_clk || past_xclk) + begin + assume(past_xclk_stall == i_xclk_stall); + assume(past_xclk_data == i_xclk_data); + assume(past_xclk_ack == i_xclk_ack); + assume(past_xclk_err == i_xclk_err); + end + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Wishbone bus property checks + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + fwb_slave #( + // {{{ + .AW(AW), .DW(DW), + .F_LGDEPTH(LGFIFO+1), .F_OPT_DISCONTINUOUS(1) + // }}} + ) slv( + // {{{ + i_wb_clk, i_reset, + i_wb_cyc, i_wb_stb, i_wb_we, i_wb_addr, i_wb_data, i_wb_sel, + o_wb_ack, o_wb_stall, o_wb_data, o_wb_err, + fwb_nreqs, fwb_nacks, fwb_outstanding + // }}} + ); + + fwb_master #( + // {{{ + .AW(AW), .DW(DW), + .F_LGDEPTH(LGFIFO+1), .F_OPT_DISCONTINUOUS(1), + .F_MAX_STALL(4), + .F_MAX_ACK_DELAY(7) + // }}} + ) xclkwb( + // {{{ + i_xclk_clk, xck_reset, + o_xclk_cyc, o_xclk_stb, o_xclk_we, o_xclk_addr, o_xclk_data, o_xclk_sel, + i_xclk_ack, i_xclk_stall, i_xclk_data, i_xclk_err, + fxck_nreqs, fxck_nacks, fxck_outstanding + // }}} + ); + // }}} + //////////////////////////////////////////////////////////////////////// + // + // (Random/unsorted) properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + always @(*) + if (reqfifo_fill != (o_xclk_stb ? 1:0) && !req_stb) + assert(ackfifo_fill == 0 || xclk_err_state); + + always @(*) + reqfifo_fill = reqfifo_prefill + (o_xclk_stb ? 1:0); + + always @(*) + ackfifo_fill = ackfifo_prefill // + (no_returns ? 0:1) + + ((o_wb_ack || o_wb_err) ? 1:0); + + always @(*) + if (fwb_outstanding > 0) + assert(wb_active); + + always @(*) + if (f_drop_cyc_request && !bus_abort && !xclk_err_state) + begin + // req_stb is low, so cycle line has dropped normally + + // If the cycle line has since risen, there may be requests + // within the request FIFO in addition to the drop-CYC message. + if (i_wb_cyc && wb_active) + assert(reqfifo_fill == fwb_outstanding + 1); + + // We wouldn't place a drop CYC message into the FIFO + // unless XCLK-CYC was already high + assert(o_xclk_cyc && !o_xclk_stb); + assert(ackfifo_fill == 0); + assert(fxck_outstanding == 0); + + if (reqfifo_fill > 1) + assert(wb_active); + else + assert(!wb_active); + end else if (!bus_abort && xclk_err_state) + begin + // + // Bus error downstream causes an abort + assert(fxck_outstanding == 0); + assert(xck_reset || wb_active || !i_wb_cyc); + assert(!o_xclk_stb); + if (ackfifo_fill == 1) + assert(no_returns || err_stb); + else if (!xck_reset && ackfifo_fill == 1) + assert(o_wb_err); + end else if (!bus_abort && i_wb_cyc && !xck_reset && !xclk_err_state) + begin + // + // Normal operation in operation + // + assert(reqfifo_fill <= fwb_outstanding + 1); + assert(ackfifo_fill <= fwb_outstanding); + assert(fxck_outstanding <= fwb_outstanding); + if (o_xclk_cyc) + assert(wb_active || f_drop_cyc_request); + if (reqfifo_fill == (o_xclk_stb ? 1:0) || req_stb) + // Either first request is for strobe, or other + // request counters are valid + assert(reqfifo_fill + ackfifo_fill + + fxck_outstanding == fwb_outstanding); + else begin + // First request is to drop CYC + assert(reqfifo_fill== fwb_outstanding + 1); + assert(ackfifo_fill == 0); + assert(fxck_outstanding == 0); + assert(!o_xclk_stb); + assert(o_xclk_cyc); + end + if (acks_outstanding == 0) + assert((reqfifo_fill == 0)||!req_stb); + end + + always @(*) + if (o_wb_ack && wb_active) + begin + assert(o_xclk_cyc || xclk_err_state); + assert(!f_drop_cyc_request); + assert(!xck_reset || bus_abort); + end + + always @(*) + if (!bus_abort && acks_outstanding == 0) + assert(reqfifo_fill <= (f_drop_cyc_request ? 1:0) + + ((o_xclk_stb && xck_reset) ? 1:0)); + + always @(*) + if (ackfifo_fill != 0) + assert(o_xclk_cyc || xck_reset || xclk_err_state); + + always @(*) + if (fxck_outstanding > fwb_outstanding) + assert((!i_wb_cyc && wb_active) + || i_reset || bus_abort || xck_reset); + + always @(*) + if (!i_reset && xck_reset && !o_xclk_cyc) + assert(!i_wb_cyc || fwb_outstanding == reqfifo_fill); + + always @(*) + if (bus_abort && i_wb_cyc) + assert(!wb_active); + + always @(*) + if (acks_outstanding < (1<<LGFIFO)) + begin + // assert(!reqfifo_full); + assert(!ackfifo_full); + end + + always @(*) + if (!i_reset && !xck_reset && (fwb_outstanding > 0) + && ((fxck_outstanding > 0) || o_xclk_stb)) + assert(i_wb_we == o_xclk_we); + + always @(*) + if (!i_reset && i_wb_cyc) + assert(acks_outstanding == fwb_outstanding); + + always @(*) + if (xclk_err_state) + assert(!o_xclk_cyc); + + always @(*) + if (!i_reset && !bus_abort && !i_wb_cyc) + begin + if (ackfifo_empty) + begin + if (reqfifo_fill > (o_xclk_stb ? 1:0)) + assert(!req_stb || xck_reset); + assert(reqfifo_fill <= 1); + if (xck_reset && !xck_reset_pipe) + assert(!o_xclk_cyc); + end else begin + // ??? + end + end + + always @(*) + f_drop_cyc_request = !req_stb + && (reqfifo_fill > (o_xclk_stb ? 1:0)); + + always @(*) + if (!i_reset && !xck_reset && !bus_abort && i_wb_cyc) + begin + if (!o_xclk_cyc && !xclk_err_state) + assert(acks_outstanding == reqfifo_fill + - (f_drop_cyc_request ? 1:0)); + else if (!o_xclk_cyc && xclk_err_state) + assert(acks_outstanding >= reqfifo_fill + + ackfifo_fill); + else if (o_xclk_cyc && !xclk_err_state) + begin + assert(acks_outstanding >= reqfifo_fill + - (f_drop_cyc_request ? 1:0)); + assert(acks_outstanding >= ackfifo_fill); + assert(acks_outstanding >= fxck_outstanding); + assert(acks_outstanding == + reqfifo_fill - (((reqfifo_fill > (o_xclk_stb ? 1:0))&&(!req_stb)) ? 1:0) + + ackfifo_fill + + fxck_outstanding); + end + if (f_drop_cyc_request) + assert(acks_outstanding +1 == reqfifo_fill); + else if (reqfifo_fill == 0) + assert(!wb_active || o_xclk_cyc || xclk_err_state); + end else if (!i_reset && !xck_reset && !bus_abort && f_drop_cyc_request) + begin + assert(acks_outstanding +1 == reqfifo_fill); + assert(ackfifo_fill == 0); + assert(fxck_outstanding == 0); + assert(!o_xclk_stb); + assert(o_xclk_cyc); + end + + + always @(*) + if (!bus_abort && wb_active && reqfifo_fill == 0 && !xclk_err_state) + assert(o_xclk_cyc); + + always @(*) + if (f_drop_cyc_request && !bus_abort) + assert(!xck_reset); + + always @(*) + assert(!xclk_err_state || acks_outstanding != 0 || xck_reset); + + always @(*) + if (o_xclk_cyc && !i_wb_cyc) + begin + // assert(bus_abort || !xclk_err_state); + if (!wb_active && !bus_abort && !xck_reset) + assert(f_drop_cyc_request); + end else if (i_wb_cyc) + begin + if (wb_active && !xck_reset) + assert(o_xclk_cyc || xclk_err_state + ||(reqfifo_fill >= acks_outstanding)); + end + + // always @(*) + // if (acks_outstanding >= (1<<LGFIFO)) + // assert(o_xclk_cyc || xclk_err_state || xck_reset); // !!!! + + // + // !!!!!!!!!!! + // + // Fig me here + always @(*) + if (wb_active && !bus_abort && !xck_reset && i_wb_cyc && !xclk_err_state) + begin + if (reqfifo_fill == 0) + assert(o_xclk_cyc); + end + + always @(*) + if (fxck_outstanding > 0 || o_xclk_stb) + assert(!ign_ackfifo_stall); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Sub properties for the REQ FIFO + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + if ((acks_outstanding > 0)&&(reqfifo_fill != (o_xclk_stb ? 1:0))) + begin + // Something valuable is in the request FIFO + if (i_wb_cyc && !f_drop_cyc_request) + `BMC_ASSERT(req_we == i_wb_we); + else if (req_stb && o_xclk_stb) + `BMC_ASSERT(o_xclk_we == req_we); + + // if (acks_outstanding > 0) + if (!o_xclk_cyc || o_xclk_stb || + fxck_outstanding > 0 || ackfifo_fill > 0) + `BMC_ASSERT(req_stb); + end // else the request FIFO is empty, nothing is in it + // No assumptions therefore need be made + + always @(*) + if (!bus_abort && reqfifo_fill == acks_outstanding) + `BMC_ASSERT(!req_fifo_stall || !f_drop_cyc_request); + + always @(*) + if (!i_reset && o_xclk_cyc && (reqfifo_fill != (o_xclk_stb ? 1:0))) + `BMC_ASSERT(!req_stb || req_we == o_xclk_we + || fxck_outstanding == 0); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Sub properties for the ACK FIFO + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + always @(*) + if (!no_returns) + begin + `BMC_ASSERT(ack_stb ^ err_stb); + + if ((ackfifo_fill ==(o_wb_ack ? 2:1)) && xclk_err_state) + `BMC_ASSERT(err_stb); + else if (ackfifo_fill > (o_wb_ack ? 2:1)) + `BMC_ASSERT(!err_stb); + end + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Cover properties + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + reg cvr_abort; + reg [3:0] cvr_replies, cvr_post_abort; + + initial cvr_abort = 0; + always @(posedge i_wb_clk) + if (i_reset) + cvr_abort <= 0; + else if (o_wb_err && acks_outstanding > 2) + cvr_abort <= 1; + + initial cvr_replies = 0; + always @(posedge i_wb_clk) + if (i_reset) + cvr_replies <= 0; + else if (o_wb_ack || o_wb_err) + cvr_replies <= cvr_replies + 1; + + initial cvr_post_abort = 0; + always @(posedge i_wb_clk) + if (i_reset) + cvr_post_abort <= 0; + else if (cvr_abort && o_wb_ack) + cvr_post_abort <= cvr_post_abort + 1; + + always @(*) + begin + cover(cvr_replies > 1); // 33 + cover(cvr_replies > 3); // 38 + cover(cvr_replies > 9); + + cover(cvr_abort); // 31 + cover(cvr_post_abort > 1 && cvr_replies > 1); // 63 + cover(cvr_post_abort > 1 && cvr_replies > 2); // 63 + cover(cvr_post_abort > 1 && cvr_replies > 3); // 65 + cover(cvr_post_abort > 2 && cvr_replies > 3); // 65 + cover(cvr_post_abort > 3 && cvr_replies > 3); // 68 + cover(cvr_post_abort > 4 && cvr_replies > 3); // 70 + cover(cvr_post_abort > 3 && cvr_replies > 6); // 72 + end + + always @(posedge gbl_clk) + if (!i_reset) + cover(cvr_replies > 9 && !i_wb_clk && acks_outstanding == 0 + && fwb_nreqs == fwb_nacks && fwb_nreqs == cvr_replies + && !bus_abort && fwb_count != fxck_count); + + always @(posedge gbl_clk) + if (!i_reset) + cover(cvr_replies > 9 && !i_wb_clk && acks_outstanding == 0 + && fwb_nreqs == fwb_nacks && fwb_nreqs == cvr_replies + && !bus_abort && fwb_count != fxck_count + && fwb_step != fxck_step); + + // }}} + //////////////////////////////////////////////////////////////////////// + // + // Simplifying (careless) assumptions + // {{{ + //////////////////////////////////////////////////////////////////////// + // + // + + // None (at present) + + // }}} +`endif +// }}} +endmodule |
