From 3038edc09a2eb15762f2e58533f429489107520b Mon Sep 17 00:00:00 2001 From: Alejandro Soto Date: Wed, 6 Mar 2024 02:38:24 -0600 Subject: rtl/wb2axip: add to version control --- rtl/mod.mk | 2 +- rtl/wb2axip/.gitignore | 2 + rtl/wb2axip/Makefile | 344 +++++ rtl/wb2axip/README.md | 175 +++ rtl/wb2axip/addrdecode.v | 461 +++++++ rtl/wb2axip/afifo.v | 801 +++++++++++ rtl/wb2axip/apbslave.v | 229 ++++ rtl/wb2axip/apbxclk.v | 610 ++++++++ rtl/wb2axip/axi2axi3.v | 897 ++++++++++++ rtl/wb2axip/axi2axilite.v | 1173 ++++++++++++++++ rtl/wb2axip/axi2axilsub.v | 2157 +++++++++++++++++++++++++++++ rtl/wb2axip/axi32axi.v | 376 +++++ rtl/wb2axip/axi3reorder.v | 743 ++++++++++ rtl/wb2axip/axi_addr.v | 235 ++++ rtl/wb2axip/axidma.v | 2977 ++++++++++++++++++++++++++++++++++++++++ rtl/wb2axip/axidouble.v | 1406 +++++++++++++++++++ rtl/wb2axip/axiempty.v | 490 +++++++ rtl/wb2axip/axil2apb.v | 717 ++++++++++ rtl/wb2axip/axil2axis.v | 883 ++++++++++++ rtl/wb2axip/axildouble.v | 734 ++++++++++ rtl/wb2axip/axilempty.v | 371 +++++ rtl/wb2axip/axilfetch.v | 469 +++++++ rtl/wb2axip/axilgpio.v | 808 +++++++++++ rtl/wb2axip/axilite2axi.v | 374 +++++ rtl/wb2axip/axilrd2wbsp.v | 601 ++++++++ rtl/wb2axip/axilsafety.v | 1449 +++++++++++++++++++ rtl/wb2axip/axilsingle.v | 714 ++++++++++ rtl/wb2axip/axilupsz.v | 603 ++++++++ rtl/wb2axip/axilwr2wbsp.v | 675 +++++++++ rtl/wb2axip/axilxbar.v | 2421 ++++++++++++++++++++++++++++++++ rtl/wb2axip/axim2wbsp.v | 317 +++++ rtl/wb2axip/aximm2s.v | 2158 +++++++++++++++++++++++++++++ rtl/wb2axip/aximrd2wbsp.v | 740 ++++++++++ rtl/wb2axip/aximwr2wbsp.v | 751 ++++++++++ rtl/wb2axip/axiperf.v | 1603 ++++++++++++++++++++++ rtl/wb2axip/axis2mm.v | 2234 ++++++++++++++++++++++++++++++ rtl/wb2axip/axisafety.v | 2339 +++++++++++++++++++++++++++++++ rtl/wb2axip/axisbroadcast.v | 192 +++ rtl/wb2axip/axisgdma.v | 1120 +++++++++++++++ rtl/wb2axip/axisgfsm.v | 1221 ++++++++++++++++ rtl/wb2axip/axispacker.v | 890 ++++++++++++ rtl/wb2axip/axisrandom.v | 136 ++ rtl/wb2axip/axissafety.v | 677 +++++++++ rtl/wb2axip/axisswitch.v | 631 +++++++++ rtl/wb2axip/axivcamera.v | 1219 ++++++++++++++++ rtl/wb2axip/axivdisplay.v | 1438 +++++++++++++++++++ rtl/wb2axip/axivfifo.v | 1405 +++++++++++++++++++ rtl/wb2axip/axixbar.v | 2585 ++++++++++++++++++++++++++++++++++ rtl/wb2axip/axixclk.v | 312 +++++ rtl/wb2axip/axlite2wbsp.v | 568 ++++++++ rtl/wb2axip/axlite_wrapper.vhd | 136 ++ rtl/wb2axip/demoaxi.v | 720 ++++++++++ rtl/wb2axip/demofull.v | 1285 +++++++++++++++++ rtl/wb2axip/easyaxil.v | 524 +++++++ rtl/wb2axip/migsdram.v | 313 +++++ rtl/wb2axip/mod.mk | 10 + rtl/wb2axip/sfifo.v | 482 +++++++ rtl/wb2axip/sfifothresh.v | 100 ++ rtl/wb2axip/skidbuffer.v | 495 +++++++ rtl/wb2axip/wbarbiter.v | 404 ++++++ rtl/wb2axip/wbc2pipeline.v | 188 +++ rtl/wb2axip/wbm2axilite.v | 685 +++++++++ rtl/wb2axip/wbm2axisp.v | 1155 ++++++++++++++++ rtl/wb2axip/wbp2classic.v | 205 +++ rtl/wb2axip/wbsafety.v | 587 ++++++++ rtl/wb2axip/wbxbar.v | 1790 ++++++++++++++++++++++++ rtl/wb2axip/wbxclk.v | 854 ++++++++++++ 67 files changed, 56365 insertions(+), 1 deletion(-) create mode 100644 rtl/wb2axip/.gitignore create mode 100644 rtl/wb2axip/Makefile create mode 100644 rtl/wb2axip/README.md create mode 100644 rtl/wb2axip/addrdecode.v create mode 100644 rtl/wb2axip/afifo.v create mode 100644 rtl/wb2axip/apbslave.v create mode 100644 rtl/wb2axip/apbxclk.v create mode 100644 rtl/wb2axip/axi2axi3.v create mode 100644 rtl/wb2axip/axi2axilite.v create mode 100644 rtl/wb2axip/axi2axilsub.v create mode 100644 rtl/wb2axip/axi32axi.v create mode 100644 rtl/wb2axip/axi3reorder.v create mode 100644 rtl/wb2axip/axi_addr.v create mode 100644 rtl/wb2axip/axidma.v create mode 100644 rtl/wb2axip/axidouble.v create mode 100644 rtl/wb2axip/axiempty.v create mode 100644 rtl/wb2axip/axil2apb.v create mode 100644 rtl/wb2axip/axil2axis.v create mode 100644 rtl/wb2axip/axildouble.v create mode 100644 rtl/wb2axip/axilempty.v create mode 100644 rtl/wb2axip/axilfetch.v create mode 100644 rtl/wb2axip/axilgpio.v create mode 100644 rtl/wb2axip/axilite2axi.v create mode 100644 rtl/wb2axip/axilrd2wbsp.v create mode 100644 rtl/wb2axip/axilsafety.v create mode 100644 rtl/wb2axip/axilsingle.v create mode 100644 rtl/wb2axip/axilupsz.v create mode 100644 rtl/wb2axip/axilwr2wbsp.v create mode 100644 rtl/wb2axip/axilxbar.v create mode 100644 rtl/wb2axip/axim2wbsp.v create mode 100644 rtl/wb2axip/aximm2s.v create mode 100644 rtl/wb2axip/aximrd2wbsp.v create mode 100644 rtl/wb2axip/aximwr2wbsp.v create mode 100644 rtl/wb2axip/axiperf.v create mode 100644 rtl/wb2axip/axis2mm.v create mode 100644 rtl/wb2axip/axisafety.v create mode 100644 rtl/wb2axip/axisbroadcast.v create mode 100644 rtl/wb2axip/axisgdma.v create mode 100644 rtl/wb2axip/axisgfsm.v create mode 100644 rtl/wb2axip/axispacker.v create mode 100644 rtl/wb2axip/axisrandom.v create mode 100644 rtl/wb2axip/axissafety.v create mode 100644 rtl/wb2axip/axisswitch.v create mode 100644 rtl/wb2axip/axivcamera.v create mode 100644 rtl/wb2axip/axivdisplay.v create mode 100644 rtl/wb2axip/axivfifo.v create mode 100644 rtl/wb2axip/axixbar.v create mode 100644 rtl/wb2axip/axixclk.v create mode 100644 rtl/wb2axip/axlite2wbsp.v create mode 100644 rtl/wb2axip/axlite_wrapper.vhd create mode 100644 rtl/wb2axip/demoaxi.v create mode 100644 rtl/wb2axip/demofull.v create mode 100644 rtl/wb2axip/easyaxil.v create mode 100644 rtl/wb2axip/migsdram.v create mode 100644 rtl/wb2axip/mod.mk create mode 100644 rtl/wb2axip/sfifo.v create mode 100644 rtl/wb2axip/sfifothresh.v create mode 100644 rtl/wb2axip/skidbuffer.v create mode 100644 rtl/wb2axip/wbarbiter.v create mode 100644 rtl/wb2axip/wbc2pipeline.v create mode 100644 rtl/wb2axip/wbm2axilite.v create mode 100644 rtl/wb2axip/wbm2axisp.v create mode 100644 rtl/wb2axip/wbp2classic.v create mode 100644 rtl/wb2axip/wbsafety.v create mode 100644 rtl/wb2axip/wbxbar.v create mode 100644 rtl/wb2axip/wbxclk.v diff --git a/rtl/mod.mk b/rtl/mod.mk index b4dfee3..d431b8c 100644 --- a/rtl/mod.mk +++ b/rtl/mod.mk @@ -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 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 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> 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>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>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= 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= 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< 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< 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< 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< 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 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<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< 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 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< 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< ((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< 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<= 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)< 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<= (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)< 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< (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< 0) + assert(readlen_w != 0); + if (readlen_w != (1< 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< 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< 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< 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 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= 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 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< 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 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; ik0)&& 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 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<= 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< 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<= 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 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< 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<= (1< 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<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<1) ? (1< 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 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 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 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= + (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= + (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 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 8) ? 8 : LGFIFO-1; + localparam LGMAXBURST = ((4096/(C_AXI_DATA_WIDTH/8)) + > (1< 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<> (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<= 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<= 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< 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< 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= (1<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<= (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 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< 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)< 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< 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< 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< 4) ? 4 : LGMAXBURST; + localparam MAX_FIXED_BURST = (1<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 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<= 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<= (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< 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< 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< 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<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< 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 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< 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 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 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 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 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= 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<= (1< 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< 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 0) + begin : GEN_SPACE_AVAILBLE + // Here's where we'll put the actual outgoing FIFO + // {{{ + initial fifo_space_available = (1<= 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<= (1< 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<= 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<= (1< 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<= 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)); + 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< 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<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<1) ? (1< 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 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 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 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= 1); + initial assert(NM >= 1); + // }}} + + //////////////////////////////////////////////////////////////////////// + // + // Check the arbiter signals for consistency + // {{{ + generate for(N=0; N 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= 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<= 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; k1) + 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<= 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<> (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 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 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< 0) + begin : CHECK_TIMEOUT + // {{{ + for(N=0; N 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= + ((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 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 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 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 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<= 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< 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< 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 -- cgit v1.2.3